Step 06 of 12 · 25 min
How Qiskit Works: One Bell State, End to End
Map the Bell pair, choose a primitive, transpile only when a device requires it, execute, then post-process the counts. Sampler is the hands-on primitive. Estimator is conceptual.
Logical circuit and ISA circuit
The logical circuit is the one you wrote: H, CX, and measurement, with no device constraints.
An ISA circuit is that program rewritten for one backend’s basis gates and qubit connections. A local statevector sampler can run the logical circuit. A QPU cannot, until a pass manager has produced the ISA circuit.
IBM’s hello-world guide uses this same shape and then continues into a much larger example. That larger example is not a second lab here.
Map
Write the problem as a circuit. In this workshop the problem is the Bell pair.
from qiskit import QuantumCircuit
bell_circuit = QuantumCircuit(2)
bell_circuit.h(0)
bell_circuit.cx(0, 1)- Input
- Two qubits, both starting in |0⟩, and the gates you intend.
- Output
- A QuantumCircuit object. Nothing has been run.
- Vocabulary
- Circuit, gate, qubit. H and CX are the gates in this map.
- Why it matters
- Every later step uses this object. If the gates land on different qubits, the run still finishes, and it shows a different state.
- If the result looks unexpected
- What may have happened: H is on qubit 1, or the CX control and target are swapped. Check those two wires and run the simulator again. A facilitator can look with you, and you can keep learning from the histogram while you adjust.
Choose Sampler or Estimator
A primitive is the interface that runs the circuit. A sampler returns a distribution of bitstrings. An estimator returns an expectation value of an observable.
from qiskit.primitives import StatevectorSampler
# Sampler returns counts. This is the hands-on primitive.
sampler = StatevectorSampler()
# Estimator estimates an expectation value.
# This workshop explains it and does not assign an Estimator lab.- Input
- The question you are asking: counts, or an expectation value.
- Output
- A primitive object. The Bell lab chooses StatevectorSampler.
- Vocabulary
- Sampler, Estimator, primitive. Counts belong to the sampler.
- Why it matters
- The lab reads a histogram. That is sampler data. Estimator answers a different question.
- If the result looks unexpected
- What may have happened: an estimator was asked for a histogram, or a sampler was asked for an expectation value. This lab uses a sampler because it wants counts. Switch the primitive and run again. The glossary defines both, and a facilitator can point at the line.
Transpile
Rewrite the logical circuit into the instructions a chosen backend can run. This ideal lab does not transpile.
# The local lab runs the logical circuit on StatevectorSampler.
# A QPU needs an ISA circuit: the device's gates and connections.
# from qiskit.transpiler import generate_preset_pass_manager
# isa_circuit = generate_preset_pass_manager(
# backend=backend, optimization_level=1
# ).run(measured_circuit)- Input
- The logical circuit and, on hardware, a backend.
- Output
- An ISA circuit when a backend is present. On the local sampler, you keep the logical circuit.
- Vocabulary
- Transpile, backend, ISA circuit, basis gates.
- Why it matters
- A QPU rejects gates and connections it does not have. The simulator used here does not need that rewrite.
- If the result looks unexpected
- What may have happened: the circuit was transpiled before the logical gates were checked, so a wiring slip and hardware noise showed up together. Confirm the logical circuit on the simulator first. That saves QPU time and makes the next step easier to read.
Execute
Hand the measured circuit to the primitive for a number of shots. The job is the handle for that run.
measured_circuit = bell_circuit.copy()
measured_circuit.measure_all()
job = sampler.run([measured_circuit], shots=1024)- Input
- The measured circuit and shots=1024.
- Output
- A job. On the local sampler it finishes on your computer.
- Vocabulary
- Job, shot, backend. A shot is one execution of the measured circuit.
- Why it matters
- Quantum outcomes are samples. One shot is one bitstring, not the distribution.
- If the result looks unexpected
- What may have happened: shots was 1, so one bitstring was read as the whole state. Raise the shot count and run the simulator again. One shot is one sample, and that is a normal thing to discover.
Post-process
Turn the finished job into counts and read them. Qubit 0 is the rightmost bit.
result = job.result()
counts = result[0].data["meas"].get_counts()
print(counts)- Input
- The finished job.
- Output
- A dictionary of bitstrings and tallies. Ideal mass sits on 00 and 11.
- Vocabulary
- Result, counts, histogram. Counts are tallies, not amplitudes.
- Why it matters
- The histogram is the observation. It is not, by itself, a complete proof of entanglement. You are not expected to prove that a quantum method is better than a classical one from this picture alone.
- If the result looks unexpected
- What may have happened: the left bit was read as qubit 0, or hardware noise was read as a new Bell state. Qubit 0 is the rightmost bit. Noise on 01 and 10 can appear on a QPU. Compare with the simulator, and ask a facilitator if the picture still looks surprising. You can keep learning from the ideal counts.
