Skip to content
Back to Academy

Your First Entangled Circuit on the Kipu Quantum Hub

Go from two independent random bits to one entangled pair on live Kipu Quantum Hub infrastructure, then use that one circuit to map the whole stack it runs on.

TutorialBeginner~50 minCompanion primer

Educational disclaimer. This is a hands-on starter tutorial in quantum computing, meant for learning. The circuit is the smallest interesting one there is, and the platform tour around it is deliberately shallow. Backend counts, marketplace counts and measurement integers are snapshots from the day this was written, and they will drift. Treat the live pages and the API references as the single source of truth. With AI tools every solution is one prompt away; to get the most value, try before you click.

1. Build

Turn two independent coin flips into one entangled pair. Two qubits, two gates, one shared state.

2. Run it free

Transpile and submit to kipu.sim.qsim, the free Kipu simulator, then read the counts back.

3. Map the stack

Enumerate the hardware layer, find your job in the dashboard, and place L0 to L3 around your circuit.

Wondering what superposition and entanglement actually are, or why quantum is having its moment? Read the primer first, then come back.

Every task below carries collapsed hints: Hint 1 says where to look, Hint 2 gives the shape of the answer, the Solution is full verified code. Think first, then click.


Step 0: Set up the environment

To run your first quantum program, you need a Python project folder with the Hub SDK, and your personal access token.

Build from scratch with uv, qhub-quantum and qhubctl

We teach this approach with uv, use your favorite Python package manager at your own discretion. Either way, the code requires Python 3.11 or newer.

shell
uv init kipu-hub-academy
cd kipu-hub-academy
uv venv
uv add qhub-quantum

You will also want the CLI (optional, recommended), which is a robust authorization route for your code. It ships on npm and needs Node.js 20 or higher:

shell
npm install -g @quantum-hub/qhubctl

After completion

qhub-quantum brings Qiskit (the most popular and widely adopted programming SDK for quantum) with it, and does not require a separate install. To run your qhub python scripts you can now use:

shell
uv run python example.py

Step 1: Connect

On Kipu's Quantum Hub, you authenticate with a personal access token. Think of them as unique identifiers to authorize actions inside your profile and organizations you are part of. Keep it safe like you would protect a password.

Navigation: You can find it on the home page to copy and regenerate if you suspect your secrets have been leaked. To generate and manage additional, context-specific and time-limited access tokens use the options in your profile settings.

The Kipu Quantum Hub home page with the Personal Access Token panel, its value masked, next to reveal, copy and regenerate controls.
Your personal access token lives on the Hub home page.
The Hub Settings page open on Access Tokens, with the New personal access token dialog showing name and expiration controls.
Scoped, time-limited tokens are created under Settings, Access Tokens.

Reference: Manage access tokens.

How do I authenticate with my token?

To authenticate, the qhub-SDK provides three options.

  1. CLI login (recommended). Run qhubctl login -t <your access token>. That writes ~/.config/qhubctl/config.json, where the SDK reads it.
  2. Environment variable. Export KQH_PERSONAL_ACCESS_TOKEN=<your access token> in the environment you run the tasks from. Make sure to exclude it from uploading to git or docker containers (developer option).
  3. Plain text in code, some objects allow you to pass your access_token directly inline. This risks exposure and leaks to third parties. Be extra careful and rotate your token frequently.

Task 1. Write a Python script that connects to a Quantum Hub backend using your Personal Access Token.

Hint 1, where to look

The connect object lives in qhub.quantum.sdk. The Quickstart shows exemplary implementations. For a detailed backend-specific description check Quantum SDK reference with further documentation-links.

To prove everything works, ask the Hub for something. kipu.sim.qsim is a free Quantum simulator backend for testing the connection. Authenticate with your personal access token.

Hint 2, what to test

Reread Quickstart (Coin Toss). You can omit circuit construction and running a simulation, declare provider = … and backend = … with the appropriate qhub.quantum.sdk import for this exercise.

Solution
python
# task1_connect.py
from qhub.quantum.sdk import HubQiskitProvider

try:
    provider = HubQiskitProvider()
    check = provider.get_backend('kipu.sim.qsim')
    print(f'Connected. You are connected to {check.name}.')
except Exception as e:
    print(f'Connection failed: {e}\n')
    print('There are multiple options to complete this task:')
    print('  Route 1 (CLI, recommended): qhubctl login -t <your access token>')
    print('  Route 2 (ENV): export KQH_PERSONAL_ACCESS_TOKEN=<your access token> and set HubQiskitProvider(access_token=os.getenv("KQH_PERSONAL_ACCESS_TOKEN"))')
    print('  Route 3 (CODE): declare HubQiskitProvider(access_token=<your access token>) explicitly in the code. Take extra precautions and replace your token after exposing it.')

output
uv run python task1_connect.py
# Connected. You are connected to kipu.sim.qsim.

Step 2: The hardware layer

The ground layer of the Hub is hardware: QPUs and simulators from several vendors, all reachable with your credentials.

  • provider.backends() returns the backend ids, as strings in a list.
  • provider.backends(detailed=True) returns backends with their specifications.

Check the Backend Documentation for a full reference.


Task 2. Write task2_backends.py to a. print the number of all available backends on the qhub-SDK. Then, b. from the detailed list, keep only the simulators, and print one line each with id, and qubit count.

Hint 1, where to look

provider.backends() gives you a list of all available backends.

python
from qhub.quantum.sdk import HubQiskitProvider
provider = HubQiskitProvider()
print(provider.backends())

For names, do not go by memory. The following code snippet prints all properties of a backend. Check the reference: Backend Documentation for explanations, too.

python
from qhub.quantum.sdk import HubQiskitProvider
provider = HubQiskitProvider()
detailed = provider.backends(detailed=True)
print(sorted(t for t in dir(detailed[0]) if not t.startswith('_')))
print(sorted(c for c in dir(detailed[0].configuration) if not c.startswith('_')))
Hint 2, which patterns to use

You are interested in the id, type, and configuration.

  • id is the backend identifier (e.g. kipu.sim.qsim)
  • type indicates whether a backend is a QPU, SIMULATOR, ANNEALER, or UNKNOWN.
  • configuration.qubit_count holds the qubit count of the backend.

print(len(your_list)) prints the length of a list.

filtered_list = list(i for i in unfiltered_list if i.property == 'VALUE') let's you filter a list based on a property value.

Solution
python
# task2_backends.py
from qhub.quantum.sdk import HubQiskitProvider

provider = HubQiskitProvider()

detailed = provider.backends(detailed=True)
print(f'Backends the qhub-SDK offers: {len(detailed)}')

simulators = list(b for b in detailed if b.type == 'SIMULATOR')

print(f'\nSimulators ({len(simulators)} of {len(detailed)}):')
for b in simulators:
    print(f'  {b.id:<24} {b.configuration.qubit_count} qubits')
output
Backends the qhub-SDK offers: 22

Simulators (6 of 22):
  aws.sim.dm1              17 qubits
  aws.sim.sv1              34 qubits
  azure.ionq.simulator     29 qubits
  kipu.sim.qsim            30 qubits
  quandela.sim.belenos     12 qubits
  qudora.sim.xg1           32 qubits

# -> your count may differ: the catalogue grows, and access is per account. (Tutorial updated on Aug-04 2026)

The identifier is b.id. b.display_name holds the human label ("Kipu QSim Simulator") you might know from the backend page on Kipu's Quantum Hub. The specifications live under b.configuration, which also carries connectivity, gates, shots_range and supported_input_formats, all worth a look once you start choosing hardware for quantum-centric applications.

Hub / Quantum-Backends shows all available backends and their IDs with surface-level info for you to browse on the Hub. Beware, the catalogue is growing: the numbers and availabilities for your account will change over time. Access to Kipu quantum simulator kipu.sim.qsim and Azure IonQ Simulator azure.ionq.simulator is free of charge. Other backends/devices require an account with active payment information.

You can check for details under Pricing. For information about the full service offering and Enterprise accounts, contact sales@kipu-quantum.com.


Step 3: From coin flip to entanglement

Think back to the Quickstart coin toss circuit. A Hadamard operation on each qubit, then measure all. For two coins, both qubit measurements are independent of each other, because nothing in that circuit connects them. With 50-50 probability each for 0 (heads) and 1 (tails), there exist four outcomes, 00, 01, 10 and 11, with equal probability of 25% each.

You enter a bet with a rich consul, that you find to be a suspicious quantum wizard. He suggests both of you bet a gold coin on the outcome of a coin flip. If both coins land on the same symbol, you get to keep it, if they disagree, you concede your coin. A seemingly fair game. Would you take that bet?

Before you answer, you are presented with the ability to think and run quantum experiments.

3a. Build the circuit

Your task. Write task3_circuit.py. Build a two-qubit circuit. Put qubit 0 in superposition with a Hadamard (h) gate. Then, apply a connecting controlled-X (cx) gate, that is controlled by qubit 0 and targets qubit 1. Add the measure_all operation, and print the circuit diagram with print(circuit.draw()).

Hint 1, where to look

Find the gate from the method list.

  • Qiskit's QuantumCircuit API reference lists all methods on that page that you need for this exercise. Look among the controlled gates.

  • Or browse locally, without leaving your editor: print([m for m in dir(qc) if not m.startswith('_')]).

QuantumCircuit(2) gives you a two qubit setup to build your circuit.

Hint 2, the shape of the answer

circuit = QuantumCircuit(2): creates the two-qubit setup

circuit.h(0): The h-gate applies an equal superposition to qubit 0. A fair coin.

circuit.cx(0,1): The cx-gate takes two qubit arguments. The first is the control qubit, the second is the target. It flips the target (qubit 1) exactly when the control (qubit 0) is 1. On classical input it behaves like an ordinary if-statement. Here the control is in superposition, so the rule applies to all states at once: in the branch where qubit 0 is 0, the target stays 0 (00); in the branch where it is 1, the target flips (11). Neither qubit has a decided value yet; however, it is encoded in the state that they will match.

circuit.measure_all(): Measurements read the qubits' states simultaneously. A circuit with no measurements transpiles and submits fine, then fails when running on a backend. Without measurements, no results to obtain.

Solution
python
# task3_circuit.py
from qiskit import QuantumCircuit

circuit = QuantumCircuit(2)

circuit.h(0)
circuit.cx(0,1)
circuit.measure_all()

print(circuit.draw())
output
        ┌───┐      ░ ┌─┐   
   q_0: ┤ H ├──■───░─┤M├───
        └───┘┌─┴─┐ ░ └╥┘┌─┐
   q_1: ─────┤ X ├─░──╫─┤M├
             └───┘ ░  ║ └╥┘
meas: 2/══════════════╩══╩═

You just applied a controlled-NOT! This maximally entangles the control qubit 0 and target qubit 1: measured simultaneously, the results always match. As the purest form of combining superposition and entanglement we call it the Bell state. A similar classical circuit must read the control bit before deciding to flip the target. The cx gate never reads anything; it acts on both branches of the superposition and eliminates some outcomes, very similar to destructive interference in waves. To investigate this state further you would need a Bell test; check IBM's comprehensive tutorial for more.

3b. Stop and reflect, what do you expect to happen?

What do you expect to measure when running the circuit?

Your coin flip gave four outcomes at roughly a quarter each. With the circuit you just built, which states of 00, 01, 10 and 11 do you expect to survive, and with what probability? Write it down, or discuss with a partner. Check it after the experiment.


Step 4: Run the experiment

transpile(circuit, backend) rewrites your circuit into instructions the target hardware chip can actually use. Each chip can use different gate sets and has a different qubit layout. This is an essential translation step.

Pin the backend to kipu.sim.qsim as introduced in Step 2. It is one of the free simulators on Kipu's Quantum Hub.

Two two-qubit circuit diagrams side by side. Both start with a Hadamard gate on the first wire. The left one repeats it on the second wire; the right one instead connects the wires with a single two-qubit cx gate, highlighted with a ring.
One gate, two qubits: the only difference between independent coins and an entangled pair.

4a. Transpile, submit, read the counts

Your task. Write task4_run.py. Create and transpile the two-qubit bell circuit for kipu.sim.qsim, submit 1024 shots, print the job id, wait for the job to finish, and print the measurement counts.

Hint 1, where to look

The Quickstart shows both the transpile and readout calls and Job Documentation lists all properties of the job object.

python
results = job.result()
print(type(results))
print([m for m in dir(results) if not m.startswith('_')])

That two-line habit is the transferable skill in this step, and it is worth more than the answer it gives you here.

Hint 2, the shape of the answer

job.id holds the job ID; print it so you can find the job again later.

job.result() hands you a stock Qiskit result object, that means you need to call job.result().get_counts() for the measurement results.

Submitting is asynchronous. That means you can submit multiple jobs in parallel. At the same time, results take a while to compute on the cloud before the results are returned to your program.

Solution
python
# task4_run.py
from qhub.quantum.sdk import HubQiskitProvider
from qiskit import QuantumCircuit, transpile

BACKEND_ID = 'kipu.sim.qsim'   # free for testing
SHOTS = 1024

circuit = QuantumCircuit(2)
circuit.h(0)
circuit.cx(0, 1)
circuit.measure_all()

# Guard, not a formality. Without measurements this transpiles and submits fine, then
# fails on the backend, and the error surfaces at the readout where it looks unrelated.
if 'measure' not in circuit.count_ops():
    raise RuntimeError('This circuit has no measurements, so there is nothing to sample.')

provider = HubQiskitProvider()
backend = provider.get_backend(BACKEND_ID)
transpiled = transpile(circuit, backend)

job = backend.run(transpiled, shots=SHOTS)
print(f'Submitted to {BACKEND_ID}, {SHOTS} shots.')
print(f'Job id: {job.id}')
print(f'Status: {job.status()}')

job.wait_for_final_state()
result = job.result()
print(result.get_counts())
# -> {'00': 525, '11': 499}
#    Your specific counts will likely differ. Both states should be measured at roughly the same probability.

result.get_counts() returns the experiment results.

Sometimes the transpiled and original circuits differ from each other. You can monitor them with transpiled.draw() and circuit.draw(). In this case, the circuit is already in its most efficient form.

4b. Did it work?

It worked if only two outcomes appear, 00 and 11, splitting the shots roughly evenly, with 01 and 10 absent or negligible. Every shot, both coins agree. That agreement is the thing two classical coins cannot do.

Two bar charts over outcomes 00, 01, 10 and 11. The left chart has four bars near 25 percent each. The right chart has bars only on 00 and 11, near 50 percent each.
Left: illustrative independent coins. Right: the measured entangled run, 525 and 499 of 1024 shots on kipu.sim.qsim.

Now go back to your Step 3b prediction and check it. If it matched, think about what you now think the cx gate does.

Check your answer

00 and 11 survive, at roughly half each. 01 and 10 vanish.

Half the shots the control reads 0, the target is left alone, and you get 00. Half the shots the control reads 1, the target flips, and you get 11. Neither qubit has a determined value before measurement; what is determined is that they will match.

By measuring one qubit, you know the other. The randomness is still there, all of it, but it is now shared instead of independent.


Step 5: Fixing an error message

Error messages are documentation, and the good ones are better than the docs. This step is designed to exercise a troubleshooting session, with a real error.

5a. Trigger it

Connect to an IBM QPU through the provider you have been using all along. Save this as task5_error.py and run it.

python
# task5_error.py, first version. Expected to raise an error.
from qhub.quantum.sdk import HubQiskitProvider

provider = HubQiskitProvider()
provider.get_backend('ibm.qpu.fez')

Read the traceback. It does not merely tell you that you are wrong. It prints a labelled migration guide, with the replacement lines written out.

5b. Repair it

Your task. Rewrite task5_error.py so it obtains a working handle on ibm.qpu.fez, using what the error message and documentation tells you. Prove the handle is live by printing the backend name. Do not submit to it yet.

Hint 1, where to look

Read the traceback. You can find the whole instruction there. Read the message body, not just its first line: the useful part is below the sentence that tells you what went wrong.

Hint 2, the shape of the answer

IBM's backends are not reachable through the provider you have been using, because they speak IBM's own runtime protocol rather than the Hub's native one.

There is a second entry point for exactly that case, and it is a sibling in the same qhub.quantum.sdk module. You construct it the same way and authenticate with your same credential.

backend.name holds the string you are looking for.

Solution
python
# task5_error.py, repaired.
from qhub.quantum.sdk import HubQiskitRuntimeService

service = HubQiskitRuntimeService()
backend = service.backend('ibm.qpu.fez')
print(backend.name)

# -> ibm.qpu.fez
# Nothing below this line. IBM hardware bills.

That is a live handle on a 156-qubit IBM machine, repaired by reading the error message. Resolving a handle costs nothing, because nothing was submitted.

The exception you triggered was BackendNotSupportedError. It reveals both the class and the method scoped to your exact wrong call and gave you the replacement lines with a migration guide.

The habit worth taking from this: read tracebacks to the last line before you open a browser tab. On a well-built SDK the answer is usually already in front of you.


Step 6: Find your job

Jobs outlive your process. You can see all your submissions under Quantum Workloads on the Quantum Hub. To find a specific one, filter by Job ID.

6a. From the SDK

Your task. Write task6_jobs.py. List your quantum jobs and print their id, the status, the backend and the shot count. Find the job id from Step 4 in the output. Use the HubQiskitProvider and provider.jobs() to list recent jobs.

Hint 1, where to look

The provider has one method for this, and it takes no arguments. The dir() pattern from Step 2's first hint surfaces it, and Managing quantum jobs covers the surface.

Print one listing entry's field list before you write the loop. The objects are not identical to the job you got back from backend.run().

Solution
python
# task6_jobs.py
from qhub.quantum.sdk import HubQiskitProvider

provider = HubQiskitProvider()
jobs = provider.jobs()

# jobs() returns one unsorted page of up to 50 jobs; order it newest first
jobs.sort(key=lambda j: j.created_at or '', reverse=True)

print(f'Recent Jobs: {len(jobs)}.')
for j in jobs[:5]:
    print(f'  {j.id}  {str(j.status):<12} {str(j.backend_id):<24} shots={j.shots}')
output
Getting your jobs from Kipu Quantum Hub, this may take a few seconds...
Recent Jobs: 50.
  XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX  COMPLETED    kipu.sim.qsim            shots=1024
# -> your ids, count and history will differ.

6b. From the dashboard

Open the quantum workloads and find the job id from Step 4. You can Retrieve Inputs & Results and even Cancel Jobs from the interface.

The Kipu Quantum Hub quantum workloads list, showing a completed simulator job with its id, status, backend and creation time.
Every submission appears under Quantum Workloads, newest first.
The job detail page for a completed kipu.sim.qsim run: a two-bar measurement histogram and a summary panel showing backend, SDK and 1024 shots.
The same job id, status, backend and shot count the SDK printed, now in the dashboard.

In addition to the Web-UI, you can retrieve results of previous jobs using the Job ID and the HubQuantumClient (constructed with an api_key argument): Job Retrieval Reference.


Step 7: Kipu's Quantum Hub four layers

So far, you have been working in the bottom two layers of the four-layer Quantum Hub.

A four-layer diagram of the Kipu Quantum Hub: hardware at the bottom, orchestration above it, services above that, and customer applications at the top. A callout ringing the bottom two layers reads "you worked here".
The four layers of the Kipu Quantum Hub. This tutorial lived in L0 and L1.

Read bottom up. You have already worked in two of them.

L0 Hardware. QPUs and simulators from several vendors. You worked with it in Step 2 and submitted to it in Step 4.

L1 Orchestration. Everything that carries a workload down to L0 and results back up: the SDK, the MCP server, qhubctl, the REST API, data pools, classical compute, observability. HubQiskitProvider is L1. Steps 1, 4, 5 and 6 were all L1 work.

L2 Services. Kipu's own algorithms, plus an open marketplace around them. Miray solves combinatorial optimization problems (a BF-DCQO solver), on simulator and hardware. Rimay does quantum machine learning (DQFE feature mapping), on simulator and hardware.

Each shows a price label of Free, On Request or Commercial. Miray Advanced Quantum Optimizer - Simulator is Free. Rimay - Quantum Feature Extraction - Simulator is Free. Illay Base Quantum Optimizer is Free. That means you can use them to test your quantum-centric workflows without being billed extra on top of your license. Later Tutorials will use them to teach how to use services.

L3 Applications. Actual products and demos you would ship to a customer. Komatsu predictive maintenance, KPMG satellite image recognition, DB Systel anomaly detection in network traffic. Browse the Marketplace use cases for more examples.


Optional, wire up the MCP server

L1 also exposes the Hub to AI coding agents over MCP. It is a remote HTTP server, so there is nothing to install. Check the MCP Server Reference for more details.

The server authenticates via OAuth. The first time your AI client connects, it opens a browser window where you log in to the Kipu Quantum Hub and authorize access. No tokens or credentials are stored in your config files.


Further exploration

Think about one candidate application from your own work. A real problem, in one sentence. At which layer would you build it?

  • L3: there might be a L2 service you could build it on.
  • L2: or lower, think about what is missing from the marketplace today.
  • L1: you are building infrastructure, and we would like to offer you a job.
  • L0: you are building hardware, and we would like to partner with you.

Business aside, we hope you enjoy building with the Kipu Hub. The team is super receptive to feedback. Please reach out and let us figure out a solution for your (quantum-centric) ideas.

The map is only worth something if your own problem fits on it.


Documentation: Kipu Quantum Hub | Hub docs | Quickstart | Quantum SDK reference | Access tokens | Managing quantum jobs | CLI reference | MCP server setup | Backends catalogue | Dashboard

Happy Optimizing 🚀

Last tested on the Kipu Quantum Hub · 4 August 2026

Ready to build?

Run a finance or energy-trading book and want to assess practical fit on current hardware? Get in touch, or tell us if a step did not run for you.