Twenty-five chapters have produced a substantial collection of bugs. Chapter 14's bit-order confusion.
Prerequisites
- 11
- 25
Learning Objectives
- Inspect intermediate quantum state with Statevector.from_instruction.
- Bisect a circuit to localize a divergence between intent and implementation.
- Verify a transpiled circuit against its source with Operator.from_circuit.
- Recognize when a debugging technique is blind to the bug being sought.
In This Chapter
- 26.1 You cannot printf a qubit
- 26.2 What the simulator gives back
- 26.3 Four equality tests, four different questions
- 26.4 Circuit bisection
- 26.5 A gallery of real bugs
- 26.6 Assertions inside circuits
- 26.7 Debugging the circuit that actually runs
- 26.8 What you lose on hardware
- 26.9 A protocol
- What we measured
Chapter 26: Debugging Quantum Programs
Twenty-five chapters have produced a substantial collection of bugs. Chapter 14's bit-order confusion. Chapter 16's beautiful exponential fit to an artifact. Chapter 19's ancilla that looked like decoherence. Chapter 22's QFT in the wrong convention. Chapter 23's factor-of-160 arithmetic error. Chapter 25's transpiler quietly deleting the noise.
Every one was found by running something and noticing a number that could not be right. That works, and it is not a method.
This chapter is the method. It is the first of Part V because the rest of quantum software engineering — testing, optimizing, benchmarking — assumes you can tell a working program from a broken one, and that assumption is much harder to satisfy here than in classical programming.
Along the way this chapter's own worked example produced a fresh bug, in the debugging technique itself. It is in §26.4, and it is the best illustration in the book of why this is hard.
26.1 You cannot printf a qubit
The classical debugging loop is: run, print, look, narrow. Every step of it is unavailable.
You cannot print an intermediate state. Reading a qubit collapses it. A print statement in the
middle of a quantum circuit is a measurement, and a measurement changes the answer — so the act of
observing the bug destroys the thing you were observing.
You cannot step through execution. There is no breakpoint that pauses a superposition for inspection. Hardware runs the circuit or it does not.
You cannot look at the output and see what went wrong. The output is a probability distribution over bit strings, sampled a few thousand times. A subtly wrong circuit and a correct one produce distributions that differ by amounts comparable to shot noise — which Chapter 24 measured at $0.5/\sqrt N$, and which is why 10,000 shots was not enough for chemical accuracy.
And you cannot always tell a bug from noise. Chapter 19's dirty ancilla degraded the result in a way indistinguishable from decoherence. That is the characteristic quantum debugging experience: the symptom of a logic error and the symptom of a hardware limitation are the same symptom.
So the discipline is different. Not "run it and look," but:
Debug on a simulator, where you can see everything. Validate on hardware, where you cannot see anything. These are separate activities with separate tools, and conflating them is the most common mistake in the field.
The three facts underneath all four
Those four restrictions are not four independent inconveniences. They are three physical facts and one consequence, and separating them matters because each one is bought back differently.
Fact one: reading is destructive, and you cannot take a copy. Classical debugging rests on an assumption so basic nobody states it — that inspecting a variable is free and has no side effect. In quantum mechanics inspection is the most expensive operation available: it projects. And no-cloning closes the obvious escape route, which would be to duplicate the register and measure the copy.
Fact two: the state is exponentially large. Even granted a magic non-destructive read, $n$ qubits carry $2^n$ complex amplitudes. At 50 qubits there is no medium on which to write down the answer, so "look at the state" stops being a debugging strategy well before it stops being physics.
Fact three: hardware returns a histogram. Not a state, not an amplitude, not a phase — a table of bit strings and how often each appeared, with $1/\sqrt N$ error bars on every entry.
The fourth item in the list above is the consequence of these three, not a fourth fact: because the only observable is a noisy histogram, a logic error and a decoherence process are two causes competing to explain one blurred distribution, and the distribution does not carry a label saying which.
That framing makes the rest of the chapter easy to organize, because every technique here buys back one of the three facts by giving something else up:
technique buys back pays with
statevector inspection facts 1 and 2 works only on a simulator, to ~30 qubits
circuit bisection localization needs a reference circuit
assertions in-circuit fact 1 simulator-only; you must know the invariant
tomography fact 1 3^n measurement settings
distribution testing nothing it IS fact 3; Chapter 27's whole subject
classical verification fact 3 only for problems with a checkable answer
Nothing on that list buys back all three, and a tool that claims to has almost certainly substituted a question it can answer for the one you asked. That sentence is the chapter in one line, and §26.4 is the worked example of it happening.
📐 Math Aside: how big a bug has to be before sampling can see it.
§26.1's third claim — that a subtly wrong circuit and a correct one differ by about shot noise — can be made exact in the simplest case, and the arithmetic is worth doing once.
Take Chapter 27 §27.6's planted defect: a GHZ preparation whose rotation angle is $\pi/2 + \varepsilon$ instead of $\pi/2$. The prepared state is $\cos(\theta/2)|000\rangle + \sin(\theta/2)|111\rangle$ with $\theta = \pi/2 + \varepsilon$, so the total variation distance from the correct 50/50 distribution is
$$\text{TVD}(\varepsilon) = \left|\cos^2\!\left(\frac{\theta}{2}\right) - \frac{1}{2}\right| > = \frac{|\cos\theta|}{2} = \frac{\sin\varepsilon}{2}.$$
Chapter 27 measured that curve. This is where it comes from, and the two agree to four decimals at every point on the table:
text eps measured TVD sin(eps)/2 0.02 0.0100 0.0100 0.05 0.0250 0.0250 0.10 0.0499 0.0499 0.20 0.0993 0.0993 0.40 0.1947 0.1947Now put the signal against the noise. Chapter 27's shot-noise floor is $\approx 3/\sqrt{N}$, so a sampled test can separate this bug from a correct circuit only when $\sin(\varepsilon)/2 \gtrsim 3/\sqrt{N}$, which rearranges to
$$N \gtrsim \frac{36}{\sin^2\varepsilon} \approx \frac{36}{\varepsilon^2}.$$
text eps true TVD shots needed 0.40 0.1947 237 0.20 0.0993 912 0.10 0.0499 3,612 0.05 0.0250 14,412 0.02 0.0100 90,012 0.01 0.0050 360,012A one-percent rotation error needs about 360,000 shots before a distribution can see it. The same defect is caught by a single operator comparison, exactly, in one call. That gap — five orders of magnitude in cost, and a certain answer instead of a statistical one — is the whole argument for doing this work on a simulator, and the $\varepsilon^{-2}$ makes it worse the subtler the bug gets.
26.2 What the simulator gives back
On a simulator, the restrictions lift entirely. You have the full state vector, exactly, for free.
from qiskit.quantum_info import Statevector
sv = Statevector.from_instruction(qc)
That single call is the most useful debugging tool in quantum programming. It runs the circuit symbolically — no shots, no sampling, no noise — and hands back every amplitude.
Prefix inspection is the closest thing to a breakpoint. Build the circuit up to instruction $k$ and look:
def prefix(qc, k):
out = QuantumCircuit(qc.num_qubits)
for inst in qc.data[:k]:
out.append(inst)
return out
Now Statevector.from_instruction(prefix(qc, k)) is the state after $k$ operations, for any $k$. That
is a stepping debugger, built in four lines, and §26.4 turns it into something much sharper.
Aer offers the same thing from inside a running circuit:
qc.h(0); qc.save_statevector(label="after_h")
qc.cx(0, 1); qc.save_statevector(label="after_cx")
after_h: [0.7071+0.j 0.7071+0.j 0. +0.j 0. +0.j]
after_cx: [0.7071+0.j 0. +0.j 0. +0.j 0.7071+0.j]
Both approaches have the same limit, and it is worth stating now: the state vector has $2^n$ complex amplitudes.
20 qubits: 1,048,576 amplitudes = 0.02 GB
28 qubits: 268,435,456 amplitudes = 4.0 GB
30 qubits: 1,073,741,824 amplitudes = 16.0 GB
40 qubits: 1,099,511,627,776 amplitudes = 16,384 GB
Statevector debugging dies somewhere around 30–35 qubits, on a large machine. §26.8 covers what to do past that.
📐 Math Aside: the operator costs the square of the state, so its wall arrives at half the qubits.
A state vector is $2^n$ complex numbers. A unitary is $2^n \times 2^n$ of them. At
complex128, 16 bytes each:$$\text{state} = 16 \cdot 2^{n} \text{ bytes}, \qquad > \text{operator} = 16 \cdot 4^{n} = 16 \cdot 2^{2n} \text{ bytes}.$$
The second expression is the first with $n$ replaced by $2n$, which gives an exact and useful identity: an $n$-qubit operator costs precisely what a $2n$-qubit state vector costs.
text operator(14) = 4,294,967,296 B == statevector(28) = 4,294,967,296 B operator(15) = 17.18 GB == statevector(30) = 17.18 GB operator(17) = 274.9 GB == statevector(34) = 274.9 GBSo every rule of thumb about state vectors transfers to operators by halving the qubit count. State vectors die around 30; operators die around 15, and
vqelab.debuggingsetsOPERATOR_QUBIT_LIMIT = 14for a machine with 4 GB to spare. That constant is not a guess — it is the largest $n$ with $16 \cdot 4^n \le 4\,\text{GiB}$.This is the single most important number to internalize about §26.4's bisection, because the input-independent test that cannot be blind is exactly the one that runs out of memory first.
Past the wall: what still works at 40 qubits
Statevector inspection is not the only simulation method. Chapter 11 measured four of them on a GHZ chain, and two of them do not care about width at all:
n=5 n=10 n=15 n=20 n=25
statevector 101 ms 64 ms 66 ms 71 ms 351 ms
density_matrix 37 ms 49 ms 9,148 ms FAILED —
stabilizer 269 ms 332 ms 300 ms 294 ms 297 ms
matrix_product_state 38 ms 43 ms 45 ms 50 ms 52 ms
Stabilizer is flat because of Gottesman–Knill: a Clifford circuit's state is specified by the Pauli operators that stabilize it, which is $O(n^2)$ bits rather than $2^n$ amplitudes. Chapter 7 §7.3 simulated a thousand-qubit GHZ state exactly in about four seconds on that representation. If your circuit is Clifford — and error-correction circuits from Chapter 25 largely are — the wall in §26.2's memory table simply does not exist for you.
MPS is flat when entanglement is bounded, independent of qubit count. Shallow variational ansätze, the Chapter 32 and 33 kind, often qualify.
What you should not do is assume this chapter's tools transfer with the method. Most do not — but the
most important one does, for Clifford circuits, and by a spectacular margin. Clifford(qc) builds the
same $O(n^2)$ tableau the stabilizer simulator uses, and two tableaux compare exactly:
Clifford equality on a GHZ ladder with a planted S gate
n= 5 a==a True a==bad False 1.6 ms
n= 20 a==a True a==bad False 2.8 ms
n= 50 a==a True a==bad False 7.0 ms
n=200 a==a True a==bad False 28.1 ms
for comparison, Operator() at n=20 needs 4^20 x 16 bytes = 16 TiB
Two hundred qubits, twenty-eight milliseconds, and it is input-independent — so it cannot be blind in §26.4's sense. Sixteen terabytes versus 28 ms is the difference between a technique that does not exist and one that is free, and the price of admission is that your circuit must be built from $H$, $S$, CNOT and the Paulis. That is a real restriction, and it is satisfied by more of a quantum codebase than people expect: syndrome extraction, encoders, decoders, stabilizer measurements, randomized benchmarking sequences, and every magic-state distillation circuit minus the magic states.
The rest of the picture:
technique statevector stabilizer MPS density_matrix
Statevector.from_instr. yes no yes* no
prefix inspection yes yes yes yes
Operator comparison to ~13-14 n/a no no
Clifford tableau equality n/a 200+ qubits n/a n/a
purity / partial_trace yes no** no yes
ancilla-clean assertion yes yes yes yes
* Aer's MPS backend can reconstruct a full state vector on request, at which point you are paying
statevector memory again. ** a stabilizer state on a simulator is always pure by construction, so
the purity check has nothing to detect — the dirty-ancilla diagnostic of §26.5 needs a method that
can represent a mixed state.
The honest summary is that past the statevector wall you keep the assertions, keep exact equality if and only if you are Clifford, and otherwise lose the comparisons. You can still check that an ancilla returned to $|0\rangle$; for a general 40-qubit circuit you can no longer check that it computes the right unitary by any means in this chapter. Which is why §26.9's protocol insists on establishing correctness at a size you can simulate before scaling up. That is not a stylistic preference — it is the only window in which the strong tools exist.
🔀 In Another Framework: every simulator has a stepping debugger; they just hide it differently.
Qiskit's
prefix+Statevector.from_instructionpattern is a workaround for something Cirq exposes directly.simulate_moment_stepsyields the state after each moment:
python for i, step in enumerate(cirq.Simulator().simulate_moment_steps(circuit)): print(i, step.state_vector())
text moment 0: [0.7071+0.j 0. +0.j 0.7071+0.j 0. +0.j] moment 1: [0.7071+0.j 0. +0.j 0. +0.j 0.7071+0.j]Compare that first line to §26.2's
after_hfrom Aer, which was[0.7071, 0.7071, 0, 0]. Same circuit, same physical state, different index. Cirq orders qubits with the first one most significant; Qiskit orders it least. The Chapter 14 bug in §26.5's gallery is visible here as a difference between two printouts of a correct result — which is exactly how it gets past people.PennyLane marks the points it wants and collects them in one call:
python qml.Snapshot("after_h") # placed inside the QNode snaps = qml.snapshots(qnode)()
text start: [1. +0.j 0.+0.j 0. +0.j 0.+0.j] after_h: [0.7071+0.j 0.+0.j 0.7071+0.j 0.+0.j] execution_results: [0.7071+0.j 0.+0.j 0. +0.j 0.7071+0.j]That is Aer's
save_statevectorwith a different spelling — and note PennyLane uses Cirq's convention, not Qiskit's. Q#'s equivalent isDumpMachine(), which prints the amplitudes of the simulator's current state wherever you place the call.All four give you the same capability, and all four are simulator-only. No framework has a hardware breakpoint, because none can.
26.3 Four equality tests, four different questions
You have a circuit and a reference. Are they the same? Qiskit offers several ways to ask, and they disagree:
p = QuantumCircuit(1); p.x(0); p.z(0); p.x(0); p.z(0) # = -I
q = QuantumCircuit(1) # = I
Operator(p) == Operator(q) False
process_fidelity(...) == 1 True
Statevector.equiv(...) True
state_fidelity(...) == 1 True
All four are correct. They answer different questions, and choosing between them is a decision about how the circuit will be used.
$XZXZ = -I$ differs from $I$ by a global phase of $\pi$. Global phase is unobservable: it cancels in $|\langle\psi|\psi\rangle|^2$, so no measurement can detect it.
⚛️ The Physics Underneath: global phase is unobservable until it isn't.
For a circuit run as a whole program, $-I$ and $I$ are the same operation and
equivis the right test. Insisting on exact operator equality will send you chasing a difference that no experiment can see.But controlling a gate makes its global phase relative. $\text{ctrl}(-I)$ applies $-1$ to the branch where the control is $|1\rangle$ and $+1$ where it is $|0\rangle$ — which is a $Z$ on the control, and entirely observable:
text Operator(p).equiv(Operator(q)) True Operator(ctrl-p) == Operator(ctrl-q) False <-- phase became RELATIVE Operator(ctrl-p).equiv(Operator(ctrl-q)) False <-- and not a phase convention
So the rule is about use, not about correctness:
standalone circuit, run as a whole program -> equiv / process_fidelity
sub-circuit that will be CONTROLLED -> Operator equality, exactly
comparing states after a specific input -> state_fidelity
comparing the whole map, input-independent -> process_fidelity or Operator
Chapter 18 §18.4 found the same thing from the other direction: translating between frameworks loses global phase, which is harmless until the translated circuit becomes a controlled subroutine.
📐 Math Aside: controlling a gate turns its global phase into a $Z$ rotation on the control.
The ⚛️ callout above asserts this. Here is the matrix, because the derivation is three lines and it makes the rule memorable.
Let $U$ and $V = e^{i\phi}U$ be two implementations differing only by global phase. Controlling on one qubit gives block-diagonal matrices:
$$\text{ctrl}(U) = \begin{pmatrix} I & 0 \\ 0 & U\end{pmatrix}, \qquad > \text{ctrl}(V) = \begin{pmatrix} I & 0 \\ 0 & e^{i\phi}U\end{pmatrix}.$$
Factor the second one:
$$\text{ctrl}(V) = \begin{pmatrix} 1 & 0 \\ 0 & e^{i\phi}\end{pmatrix} \otimes I \;\cdot\; > \text{ctrl}(U) \;=\; \big(P(\phi) \otimes I\big)\,\text{ctrl}(U).$$
The stray phase has become a phase gate $P(\phi)$ sitting on the control line. It is no longer global — it multiplies only the $|1\rangle$ branch — and $P(\pi) = Z$, which is why $XZXZ = -I$ controlled is not merely unequal to $\text{ctrl}(I)$ but differs from it by a $Z$ you can measure in the $X$ basis.
Two consequences follow immediately, and both are practical:
The error does not shrink with $\phi$ in any useful sense. A "small" phase discrepancy of $\phi = 0.01$ becomes $P(0.01)$ on the control, which is a genuine one-percent rotation error — and §26.1's Math Aside priced that at 360,000 shots to detect by sampling. The phase you were right to ignore standalone is a bug you cannot afford to sample for.
equivis not a weaker version of==; it is a different question.Operator(a).equiv(b)asks "are these the same operation?"Operator(a) == basks "are these the same matrix?" Only the second survives being placed inside a larger circuit, and Chapter 19's phase oracles are exactly circuits whose entire job is to be placed inside a larger circuit.
26.4 Circuit bisection
Here is the technique that does the most work.
You have a circuit that is wrong and a reference that is right — an earlier version, a library implementation, a small hand-checked case. Both have the same instruction sequence length. Binary search for the first instruction at which they diverge.
def bisect(suspect, reference):
n = len(suspect.data)
diverged = lambda k: Operator(prefix(suspect, k)) != Operator(prefix(reference, k))
if not diverged(n):
return None
lo, hi = 0, n
while lo < hi:
mid = (lo + hi) // 2
if diverged(mid):
hi = mid
else:
lo = mid + 1
return lo
Because the divergence property is monotone — once two circuits differ, appending more gates cannot make them agree again — binary search applies, and the cost is logarithmic:
circuit ops linear bisection
7 7 4
500 500 10
3,368 3,368 13 <-- Chapter 23's Shor(15)
20,000,000 20,000,000 26 <-- Shor on a 2048-bit key
Thirteen comparisons to localize a bug in Chapter 23's factoring circuit. Twenty-six for a circuit with twenty million gates.
The bug in the debugger
The first version of that function compared states, not operators:
diverged = lambda k: state_fidelity(
Statevector.from_instruction(prefix(suspect, k)),
Statevector.from_instruction(prefix(reference, k))) < 1 - 1e-9
Run against a seven-operation QFT-like circuit with a deliberately wrong rotation angle planted at operation 5, it reported: no divergence anywhere.
The circuits are definitely different — Operator(good) == Operator(bad) is False, and the process
fidelity is 0.9498. But Statevector.from_instruction starts from $|000\rangle$, and from
$|000\rangle$ every control qubit reaching the buggy gate is in $|0\rangle$, so every
controlled-phase gate is the identity and the wrong angle never fires.
Trying the other obvious input does not help:
OPERATOR (no input) -> divergence at op 5 ('cp'), 4 comparisons
state |000> -> NOTHING FOUND (blind)
state |+++> -> NOTHING FOUND (blind)
state |001> -> divergence at op 5 ('cp'), 4 comparisons
state |0+1> -> divergence at op 5 ('cp'), 4 comparisons
RANDOM state (seed 7) -> divergence at op 5 ('cp'), 4 comparisons
RANDOM state (seed 11) -> divergence at op 5 ('cp'), 4 comparisons
$|000\rangle$ and $|{+}{+}{+}\rangle$ are both blind — the two inputs everyone reaches for. $|000\rangle$ trivially; $|{+}{+}{+}\rangle$ because the Hadamard on qubit 1 maps $|+\rangle$ to $|0\rangle$ before the buggy gate arrives.
How common is blindness?
The obvious next thought is that these two are unlucky special cases. They are not. Counting:
computational basis states blind: 4/8 ['000', '010', '100', '110']
states over {0, 1, +} blind: 11/27 (41%)
random states blind: 0/100
Four of the eight computational basis states are blind — exactly those with $q_0$ in $|0\rangle$, since $q_0$ controls the buggy gate and it therefore never fires. Across the 27 states built from $\{|0\rangle, |1\rangle, |+\rangle\}$, 41% are blind. Across 100 random states, none are.
This number was itself a correction. The project checkpoint originally asserted that $|1{+}0\rangle$ would see the bug — a reasonable-looking guess, since it has a $|+\rangle$ and a $|1\rangle$ in it. It does not: $q_0 = |0\rangle$, so the gate never fires. The test failed, and the guess was wrong.
Why blindness happens, in one sentence
Blindness is not a quirk of this circuit. It has an exact characterization, it is short, and once you have it three separate disasters from earlier chapters collapse into one.
At prefix $k$ the two circuits realize unitaries $P_k$ and $Q_k$. Define the discrepancy operator
$$E_k = P_k^{\dagger} Q_k,$$
which is the identity while the circuits agree and something else afterwards. State-based bisection at prefix $k$ computes
$$F\big(P_k|\psi\rangle,\, Q_k|\psi\rangle\big) = \big|\langle\psi|E_k|\psi\rangle\big|^2,$$
and because $E_k$ is unitary, $|\langle\psi|E_k|\psi\rangle| = 1$ if and only if $|\psi\rangle$ is an eigenvector of $E_k$. So:
A bug is invisible on input $|\psi\rangle$ exactly when $|\psi\rangle$ is an eigenvector of the discrepancy operator.
That is the whole theory. And it is, word for word, Chapter 25 §25.3's diagnosis — "the stored state was an eigenstate of the very error the experiment was trying to detect" — and Chapter 19's, since a phase oracle's discrepancy operator is diagonal, and every computational basis state is an eigenvector of a diagonal matrix. Chapter 19's oracle was not 4/8 blind in the computational basis. It was 8/8 blind, for the same reason, and that is the worst case of this phenomenon rather than a different phenomenon.
The operator comparison is now easy to place. Operator(P_k) != Operator(Q_k) asks whether
$E_k \ne e^{i\theta}I$ — whether any state fails to be an eigenvector. The operator test is the
state test with the input universally quantified, which is precisely why it cannot be blind and
precisely why it costs $4^n$ instead of $2^n$. You are not buying a better test. You are buying the
quantifier.
📐 Math Aside: deriving 4/8 and 11/27 by hand.
The measured blind counts are not arbitrary. Both fall out of the circuit in a page, and doing it once tells you what to look for in your own circuits. Labels below are Qiskit's — a label $c_2c_1c_0$ passed to
Statevector.from_labelsets qubit 2 leftmost, so001means $q_0 = |1\rangle$.The bug is at instruction 5,
cp(π/2 → π/3)on qubits $(0,1)$. Instructions 6 and 7 are shared, so they cancel out of $E$; instructions 1–4 are shared too, and conjugate it. With $A$ = instructions 1–4 and $D = \text{cp}(\pi/2)^\dagger\text{cp}(\pi/3)$,$$E = A^{\dagger} D A, \qquad > D = \text{diag}\big(1,\,1,\,1,\,e^{-i\pi/6}\big) \text{ on } (q_0,q_1).$$
$|\psi\rangle$ is an eigenvector of $E$ iff $A|\psi\rangle$ is an eigenvector of $D$, and $D$ has exactly two eigenspaces: the block $B$ where both controls are 1, and everything else. So
blind $\iff$ the state just before instruction 5 has either none or all of its probability in the $q_0 = q_1 = 1$ block.
Checked against the bisection on all 27 structured states, this criterion agrees 27 times out of 27. Now count.
Family one: $q_0$ never reaches $|1\rangle$. Instructions 1–4 are
h(2),cp(1,2),cp(0,2),h(1). The Hadamards act on other wires and thecpgates are diagonal, so $q_0$'s populations are untouched. If $c_0 = |0\rangle$ the block is empty and the gate never fires — $3\times3\times1 = \mathbf{9}$ of the 27 labels, and $\mathbf{4}$ of the 8 computational-basis labels, namely000,010,100,110.Family two: $q_1$ never reaches $|1\rangle$. Writing $c_1(\cdot)$ for the initial amplitudes of $q_1$, the amplitude for $q_1 = 1$ after instruction 4, conditioned on $q_2 = b_2$, is
$$g(1 \mid b_2) = \tfrac{1}{\sqrt{2}}\left[c_1(0) - i^{\,b_2} c_1(1)\right].$$
For $c_1 \in \{|0\rangle, |1\rangle\}$ this is nonzero for both $b_2$, so those inputs always see the bug. For $c_1 = |+\rangle$ it gives $g(1\mid 0) = 0$ but $g(1\mid 1) = (1-i)/2 \neq 0$ — so $q_1$ can still reach $|1\rangle$ through the $q_2 = 1$ branch, and that branch exists unless $q_2$ is pinned to $|0\rangle$. Instruction 1 is a Hadamard, so $q_2$ is pinned exactly when $c_2 = |+\rangle$. Family two is therefore $c_1 = c_2 = |+\rangle$: the $\mathbf{2}$ labels
++1and+++.The "all of it in the block" case never occurs, because $g(0\mid b_2) \neq 0$ for every choice of $c_1$. Total: $9 + 2 = \mathbf{11}$, matching the measurement, with the basis subset $\mathbf{4}$.
And now the useful part. This refines the chapter's own account of $|{+}{+}{+}\rangle$. It is blind because the Hadamard sends $|+\rangle \to |0\rangle$ on $q_1$ — but only because $q_2$ was pinned first, so instruction 2's controlled-phase had nothing to act with. Change the one qubit the bug does not touch:
text |+++> BLIND |0++> sees it <-- only q2 changed, and q2 is not a control of the bug |1++> sees itBlindness is a property of the circuit-plus-input, not of the input. No amount of staring at a state label will tell you whether it is safe, which is the measured version of "you cannot reason your way to a safe test input."
Why zero of a hundred random states were blind
That result reads like luck. It is not, and the reason is worth having.
The blind set is $A^{\dagger}(B) \cup A^{\dagger}(B^{\perp})$ — two proper subspaces of the
8-dimensional state space, of dimension 2 and 6. A proper subspace has Haar measure zero. A state
drawn from random_statevector is blind with probability exactly zero, so 0/100 is not a favourable
sample; it is the only outcome the experiment could have produced.
Three caveats keep that from being a promise.
Floating point converts a measure-zero set into a thin shell of positive measure. Bisection declares agreement at fidelity $> 1 - 10^{-9}$. Writing a state as $\cos\delta\,|u\rangle + \sin\delta\,|v\rangle$ with $|u\rangle$ outside the block and $|v\rangle$ inside, the fidelity is $1 - 2\cos^2\!\delta\,\sin^2\!\delta\,(1 - \cos\Delta)$ with $\Delta = \pi/6$ here. Setting that equal to the tolerance gives
sin^2(delta) = 1e-9 / (2 x 0.133975) = 3.73e-09
delta = 6.11e-05 radians
Any state within about $6\times10^{-5}$ radians of the blind set is reported as agreeing. That is a tiny target, and it is not zero — which is the real reason to use more than one random input rather than trusting a measure-theoretic argument.
The blind fraction is a property of the bug, not a constant. 41% is this discrepancy operator's number. A diagonal discrepancy — Chapter 19's phase oracle — makes every computational basis state blind. A discrepancy that acts only on an ancilla makes every state with a clean ancilla blind. Do not carry 41% around as a rule of thumb; carry the eigenvector condition, which is what generates it.
And past 14 qubits the tool's refusal cannot be enforced. bisect returns BLIND instead of
AGREE by cross-checking against the operator — and operator_buildable is False above
OPERATOR_QUBIT_LIMIT. So on a 20-qubit circuit, a state-based bisection that finds nothing returns
AGREE with no cross-check performed, and the honest reading of that verdict is "no input I tried
could see a difference." It is the weaker claim, and the number of random inputs you ran is the only
evidence behind it.
⚠️ Common Pitfall: bisect on the OPERATOR, or on a RANDOM input — never on a "nice" one.
A nice input state is exactly what a bug can hide behind. Structured inputs have structured blind spots, the most structured inputs are the ones you reach for first, and you cannot reason your way to a safe one — 41% of the structured inputs here are blind, and picking the good ones requires already knowing where the bug is.
Use
Operatorwhen the circuit is small enough to build one — it is input-independent and cannot be blind. Past about 12–14 qubits, fall back torandom_statevector, and use more than one.
The project module encodes this as a refusal. bisect never returns "these circuits agree" from a
state comparison without cross-checking against the operator; if the circuits differ and the input
could not tell, it returns BLIND, which is a failed measurement rather than a pass.
This is the third instance of one failure mode in three chapters. Chapter 19's oracle "worked" because it was only tested in the computational basis. Chapter 25 §25.3's QEC test reported zero logical error because the stored state was an eigenstate of the failure. Now a debugging tool reports a clean bill of health because its default input cannot see the defect.
A test that cannot fail is not evidence — and the tool you use to find bugs is itself a program that can have this bug.
🧪 Run It: plant your own bug and count your own blind set.
Twenty minutes, and it is the fastest way to stop believing you can pick a good test input.
- Take
code/example-02-circuit-bisection.pyand move the planted angle error fromcp(0, 1)tocp(1, 2). Re-run the census over all 27 structured states.- Predict the new blind count before you run it, using the recipe above: which qubit's populations are untouched by the earlier instructions, and which initial labels pin a control to $|0\rangle$?
- Now change the bug type. Replace the wrong-angle
cpwith an extrazon qubit 0. The discrepancy operator is now diagonal — predict what happens to the computational-basis count before running, then check.- Finally, put the bug in a
swapinstead. A swap discrepancy is a permutation, so states symmetric under that permutation are blind. Which of the 27 are?The point of the exercise is step 2, and the point of step 2 is getting it wrong at least once. The author's own wrong prediction — $|1{+}0\rangle$ — is the reason §26.4 exists.
The seam: two correct pieces and one wrong composition
Bisection has a precondition that is easy to miss: it needs a reference circuit with the same instruction sequence. A whole class of bugs offers neither, because each piece is individually correct and the defect lives in how they were joined.
the shape of a seam bug
bisect(stage_A, reference_A) -> AGREE
bisect(stage_B, reference_B) -> AGREE
the composed program -> wrong
Four seams that produce exactly this:
- A subroutine correct standalone and wrong once controlled. §26.3's global phase. Both authors verified their piece with the right test for their piece.
- An ancilla register two stages both believe they own. Stage A leaves it dirty because it never read the contract; stage B assumes it starts at $|0\rangle$. Chapter 19's bug, promoted to a composition failure.
- A bit-order convention each half is self-consistently wrong about. Chapter 14's, and
is_bit_order_onlywill reportTrueon the pair while both halves pass their own tests. - Two error budgets measured against two different references. Chapter 36's Case Study 36.1 is precisely this shape, and it is the most expensive one in the book: a VQE error of $2.04\times10^{-9}$ Ha measured against one Hamiltonian, an active-space error of $0.0201$ Ha measured against another, and a slide that composed them into a claim wrong by a factor of 9,870,104. Neither measurement was incorrect. As that case study puts it: the error is in the seam.
Seams resist verification for a structural reason rather than a technical one. Each piece has an owner and the seam has none. The tests that exist were written by the people who wrote the pieces, against the references those people were using, and nobody's job description contains the joint.
The remedy is to make the contract at each boundary explicit and machine-checked. That is what §26.6's assertions are actually for — the ancilla check is not a check on stage A, it is the postcondition stage B depends on, written down:
at every subroutine boundary, state it and check it:
which qubits are OWNED, which are BORROWED -> a register map, in the docstring
ancillas returned to |0> -> ancilla_report
bit-order convention on each register -> is_bit_order_only vs a reference
may this block be CONTROLLED? -> equality_verdict(..., will_be_controlled=)
is global phase meaningful here? -> the same flag
what reference is THIS stage's error measured against? -> Chapter 36 §36.4
equality_verdict taking no default for will_be_controlled is a seam device, not a piece of API
pedantry. The question "will this be composed into something larger?" cannot be answered inside the
piece, so the function refuses to answer it for you.
26.5 A gallery of real bugs
Five bugs from earlier in this book, each with the one diagnostic that finds it.
Bit order (Chapter 14)
Two circuits that both "put an X on the first qubit":
A: {'001': 1.0} # qiskit: q0 is the LEAST significant bit
B: {'100': 1.0} # big-endian intent
Diagnostic — compare against a reversed reference:
Operator(a) == Operator(b) False
Operator(a) == Operator(b.reverse_bits()) True <-- it is ONLY bit order
If reversing makes them equal, the logic is right and the convention is wrong. That is a very
different fix from "the algorithm is broken," and Chapter 14's reverse_bits is the single function
permitted to touch it.
Dirty ancilla (Chapter 19)
An ancilla used and not uncomputed stays entangled with the data, which destroys the data register's coherence and looks exactly like decoherence.
Diagnostic — trace out the ancillas and check purity:
uncompute=True data-register purity = 1.0000 PURE
uncompute=False data-register purity = 0.6250 MIXED <-- the ancilla stole coherence
from qiskit.quantum_info import partial_trace, purity
purity(partial_trace(sv, ancilla_indices))
A pure state that has become mixed on a noiseless simulator is not noise. There is no noise. It is entanglement with something you forgot to clean up — and the number is exact, not statistical.
Wrong convention (Chapter 22)
Chapter 22's AQFT fidelity table decreased with cutoff when it should have increased, because the QFT was built in the wrong qubit-ordering convention.
Diagnostic — process fidelity against the library gate:
descending, swaps=True process_fidelity vs QFTGate = 1.0000 <-- CORRECT
descending, swaps=False 0.2500
ascending, swaps=True 0.1547
ascending, swaps=False 0.2500
Four conventions, one scores 1.0. When a library implementation exists, comparing against it is faster than reasoning about which convention you are in — and the fidelity tells you how wrong you are, which the reasoning does not.
Silently deleted gates (Chapter 25)
before transpile: {'id': 1, 'measure': 1}
after transpile: {'measure': 1} <-- the id is GONE
Diagnostic — diff count_ops() across transpilation. The circuit you build and the circuit that
runs are different objects, and the difference is usually intended. When it is not, this one-line
check finds it.
Global phase (§26.3)
Diagnostic — pick the equality test that matches how the circuit will be used, and if in doubt,
check both. equiv says yes, Operator equality says no, and which one you want depends entirely on
whether the circuit will ever be controlled.
From gallery to decision procedure
The gallery is organized by bug. In practice you arrive with a symptom, so here is the same material inverted — what you observed, and the one diagnostic that most cheaply eliminates the largest class of causes:
SYMPTOM FIRST DIAGNOSTIC §
--------------------------------------------------------------------------------
wrong distribution on a NOISELESS sim Operator vs a reference 26.3
...and they differ bisect (operator or random) 26.4
right answer, wrong bit strings Operator(a) == Operator( 26.5
b.reverse_bits())
fidelity decays with depth, no noise model purity(partial_trace(...)) 26.5
an amplitude that should be real has an i assert on the amplitudes 26.6
correct standalone, wrong as a subroutine equality_verdict(..., 26.3
will_be_controlled=True)
correct before transpile, wrong after count_ops_diff, then 26.7
verify_transpilation
correct on the simulator, wrong on hardware you are validating now, 26.8
not debugging
a test passes that should have failed cross-check with Operator; 26.4
read BLIND as a failure
results change run to run on a simulator seeding (Ch. 27 §27.7) —
no reference circuit exists at all metamorphic properties —
(Ch. 27 §27.3)
Two things about the order. Every entry in the top half is exact, simulator-only, and costs milliseconds — §26.7's table measures a 12-qubit state vector at 1.7 ms. Nothing here is expensive except the operator comparisons, and those become expensive suddenly rather than gradually.
And the two entries most likely to be skipped are the two that most often apply: the transpile
diff, because the transpiler is assumed correct; and "a test passes that should have failed," because
a green result does not present itself as a symptom. Case Study 1 is the second one. Chapter 25's
vanished id gates were the first.
⚠️ Common Pitfall: assuming the bug is in the library.
Across the five bugs in this gallery — Chapters 14, 19, 22, 25 and §26.3 — the library was correct every time. So was the transpiler in §26.7, and so was Qiskit in Case Study 2, where a team filed an issue against it.
This is not deference to authority. It is base rates: Qiskit's QFT and
Operator.from_circuithave been executed by a very large number of people, and your modular exponentiation routine has been executed by you. Reach for the library as a reference rather than as a suspect — comparing againstQFTGatefound the Chapter 22 convention bug in one line, and would have found it whichever of the two was wrong.
26.6 Assertions inside circuits
Classical code asserts invariants. Quantum code can too, on a simulator, and the most valuable assertion is the uncomputation check:
def assert_ancilla_clean(qc, ancillas, tol=1e-9):
"""Every ancilla must return to |0> before it is released."""
probs = Statevector.from_instruction(qc).probabilities(ancillas)
p_dirty = float(1 - probs[0])
return p_dirty < tol, p_dirty
uncompute=True P(ancilla != |0>) = 0.0000 CLEAN
uncompute=False P(ancilla != |0>) = 0.2500 DIRTY
Chapter 19 established that ancilla hygiene is not optional. This makes it checkable, cheaply, at every point in a circuit where an ancilla is supposed to be released — and it turns a bug that manifests as mysterious fidelity loss into a hard failure at a known line.
Other assertions worth writing:
- A register that should be unentangled —
purity(partial_trace(...)) == 1. - A distribution that should be uniform — compare against $2^{-n}$ within tolerance.
- A state that should be real — many algorithms produce real amplitudes at specific points; a stray $i$ means a rotation went the wrong way.
- Norm preservation — cheap, and catches a surprising number of hand-built matrix errors.
These are simulator-only, and that is fine. They are how you establish the circuit is right before you take it somewhere you cannot look.
26.7 Debugging the circuit that actually runs
Chapter 10 covered transpilation and Chapter 12 covered layout. Both matter here, because the circuit you wrote and the circuit that executes are related by a permutation you did not choose.
Transpile a 3-qubit circuit for a 5-qubit backend:
logical: {'cx': 2, 'h': 1, 'cp': 1}, depth 4
transpiled: {'cx': 7, 'rz': 5, 'sx': 1}, depth 12, 5 qubits
initial layout: [3, 2, 4, 1, 0]
final layout: [4, 2, 3]
Your logical qubit 0 is physical qubit 3 at the start and physical qubit 4 at the end — routing swapped things around. Now verify the transpilation was correct:
1. Operator(logical) vs Operator(transpiled) QiskitError: 3 qubits vs 5
2. padded logical vs transpiled, ignoring layout process_fidelity = 0.001406
3. Operator.from_circuit(transpiled) vs padded process_fidelity = 1.0000000000
Attempt 2 is the trap. It runs without error, returns a plausible-looking number, and declares a
perfectly correct transpilation broken. Operator.from_circuit is the function that applies both the
initial layout and the routing permutation.
⚠️ Common Pitfall: a comparison that runs is not a comparison that is right.
0.001406looks like a real measurement of a real problem. It is a measurement of comparing two circuits on mismatched wires. Case Study 2 is a team that spent a week on it.⚙️ Under the Transpiler: what
Operator.from_circuitis actually undoing.Two separate permutations, applied at two separate stages, and the two printed lines above name them both.
The initial layout is chosen by the layout pass.
initial_index_layout()returning[3, 2, 4, 1, 0]reads positionally: your virtual qubit 0 is placed on physical qubit 3, virtual 1 on physical 2, virtual 2 on physical 4, and the two unused wires take physicals 1 and 0. Chapter 12 §12.4 is where those choices come from — the transpiler is aiming at the qubits with the best calibration data, and Chapter 29 measured what that is worth (a hardware-aware level-1 layout scoring 0.9116 against a naive level-3's 0.7720).The routing permutation is added afterwards by the swap pass, which inserts SWAPs to satisfy connectivity.
final_index_layout()returning[4, 2, 3]says that by the last instruction your virtual qubit 0 has ended up on physical 4 and virtual 2 on physical 3. Those swaps are why thecxcount went from 2 to 7.
Operator.from_circuitreads both offtranspiled.layoutand composes them back out, returning an operator expressed in virtual wire order — which is the order your reference circuit is in.Here is why almost nobody meets this problem until they try to verify something. When your circuit ends in measurements, the transpiler remaps the measure instructions too, so counts come back keyed to your virtual qubits and the whole permutation is invisible. Unitary comparison is the one operation with no measurement to carry the relabelling. The permutation was always there; you had just never looked at it without a measurement in the way.
📊 What the Numbers Say: 0.001406 is the fidelity of a stranger.
There is a constant that identifies this class of false alarm on sight, and it takes one line of arithmetic.
Process fidelity is $|\text{Tr}(U^\dagger V)|^2 / d^2$, and for two unrelated $d$-dimensional unitaries the expected value of $|\text{Tr}(U^\dagger V)|^2$ is 1. So the fidelity floor — the score you get comparing your circuit against something with no relationship to it whatsoever — is
$$F_{\text{unrelated}} \approx \frac{1}{d^2} = \frac{1}{4^n}.$$
The transpiled circuit here is 5 qubits, so $d = 32$ and $1/d^2 = 1/1024 = 0.000977$:
text measured false alarm 0.001406 1/d^2 at n=5 0.000977 ratio 1.44xThe alarming number was, to within a factor of 1.5, the score for comparing the circuit to a stranger. It contains no information about the transpiler at all. It is the numeric signature of "these two operators are on different wires," and once you know the constant it announces itself.
So: before believing a catastrophically low fidelity, compute $1/4^n$ and compare. A genuinely broken circuit usually lands somewhere in between — Chapter 22's wrong QFT conventions scored 0.2500 and 0.1547 at $n=3$, well above that circuit's floor of $1/64 = 0.0156$. A score sitting on the floor is far more likely to mean your comparison is misaligned than that your circuit is maximally wrong, because being maximally wrong is difficult and being misaligned is one forgotten function call.
And there is a hard limit here too. The same 3-qubit circuit transpiled for a 127-qubit backend:
Operator.from_circuit(transpiled): ValueError: Maximum allowed dimension exceeded
2^127 = 1.701e+38 amplitudes
On a real-sized backend you cannot verify transpilation by building operators at all. The options are to transpile to a small backend with similar coupling structure for the correctness check, to compare simulated results, or to move up a level and test the output distribution — which is Chapter 27.
Two walls, and the one you actually hit is at 14
The 127-qubit ValueError is dramatic and it is not the wall that stops you. Long before the matrix
becomes impossible it becomes unaffordable, and the transition is sharp. Building Operator and
Statevector for the same circuit — Hadamards on every wire, then a CNOT chain:
qubits Operator(qc) Statevector matrix size
6 21.1 ms 0.8 ms 0 MiB
8 31.8 ms 0.8 ms 1 MiB
10 507.4 ms 5.8 ms 16 MiB
12 9,666.0 ms 1.7 ms 256 MiB
13 37,546.6 ms 3.4 ms 1,024 MiB
14 125,669.4 ms 6.6 ms 4,096 MiB
Two minutes and six seconds to build one 14-qubit operator, against 6.6 milliseconds for the state vector of the same circuit — a factor of about 19,000. And the growth is the predicted one: 9.7 s → 37.5 s → 125.7 s is 3.9× then 3.3× per qubit, against the $4\times$ per qubit that $16 \cdot 4^{n}$ demands.
Extrapolate one step and the practical limit is obvious. At 4× per qubit, 15 qubits is about eight
minutes per operator and 16 qubits about half an hour — and bisection needs $\lceil\log_2 n\rceil + 1$
of them. OPERATOR_QUBIT_LIMIT = 14 is not a memory failure. It is a patience failure, and it
arrives while 4 GiB is still a perfectly ordinary allocation.
🔬 Honest Assessment: what this table does and does not establish.
It is one machine, one circuit shape, and one Qiskit build (2.5.1), so treat the constants as indicative. The scaling is the durable part — $4^n$ is arithmetic, not benchmarking, and the measured ratios track it to within 20%.
It also does not say the operator test is a bad idea. It says the opposite: at 10 qubits an input-independent, never-blind equality check costs half a second, which is nothing at all for what it buys. The table tells you where to switch to random inputs, not whether to bother.
26.8 What you lose on hardware
Everything in this chapter so far requires a simulator. On hardware you get counts, and nothing else.
The textbook answer is tomography — reconstruct the state from measurements in many bases. The cost:
qubits state tomography process tomography
1 3 12
2 9 144
5 243 248,832
10 59,049 61,917,364,224
20 3,486,784,401 3.8 x 10^21
$3^n$ measurement settings for state tomography, roughly $12^n$ for process tomography — each needing many shots. At 20 qubits, state tomography alone is 3.5 billion settings. It is a diagnostic for one or two qubits, and it does not scale to anything you would want to debug.
⚛️ The Physics Underneath: where $3^n$ and $12^n$ come from.
Both exponents are counting the same thing — the number of independent questions a projective measurement is allowed to answer at once, which is one.
A single-qubit state is a point in the Bloch ball, three real parameters. A measurement in the $Z$ basis returns the $z$ component and destroys the rest; it cannot be persuaded to also report $x$. So reconstructing one qubit requires three separate experiments, in the $X$, $Y$ and $Z$ bases, each on freshly prepared copies. For $n$ qubits the basis choice is made independently per wire: $3^n$ settings.
Process tomography reconstructs a map rather than a state, so it must also vary the input. Four input states per qubit span the single-qubit operator space — $|0\rangle$, $|1\rangle$, $|+\rangle$, $|{+}i\rangle$ — and each must be measured in all three bases:
$$12^n = 4^n \cdot 3^n = (\text{inputs})^n \cdot (\text{measurement bases})^n.$$
That factorization is the useful part, because it says exactly what tomography is buying you that §26.3's
Operatorcomparison already had for free.Operatoris the entire process matrix, exact, with no inputs prepared and no bases scanned — the $4^n$ memory cost in §26.2's Math Aside is the same $4^n$ appearing here as a shot count. The exponential does not go away when you move to hardware. It changes currency, from bytes to experiments, and the exchange rate is terrible: 4 GiB of RAM at 14 qubits versus $12^{14} \approx 1.3\times10^{15}$ hardware settings.Every remedy is denominated in the currency of the disease.
So hardware debugging is not debugging. It is a small set of coarse checks:
- Does the output distribution match the simulator's? Chapter 27's subject, and the main one.
- Does it degrade the way noise degrades? Smoothly with depth, worse on bad qubits (Chapter 12), responsive to mitigation (Chapter 13). A logic error usually does not behave this way.
- Does a scaled-down version work? Run the same algorithm at 3 qubits where you can simulate.
- Do the classical checks pass? Chapter 23's Shor verifies its factors classically; Chapter 24's QAOA verifies its cut. A quantum result you can check classically is a result you can debug.
That last point deserves emphasis. Chapter 23's Shor and Chapter 24's QAOA are both Las Vegas procedures — the quantum part proposes, classical verification disposes. That structure is not only an efficiency trick; it is the only form of hardware debugging that scales.
When the simulator agrees and the hardware does not
This is the situation you will actually be in. Steps 1–5 of §26.9 pass. The unitary is right, the ancillas are clean, the transpilation verifies — and the device returns something else. It has an ordering too, and the cheap checks eliminate most of the causes.
1. Re-transpile with different seeds, several times, before anything else. Chapter 39 took one 14-qubit logical circuit and transpiled it under 24 seeds:
estimated fidelity 0.5755 - 0.7911 (2.03x in error)
two-qubit gates 49 - 112
Same logical circuit, same backend, same optimization level. If your disappointing result sits inside a spread like that, you have transpiler variance and not a bug. This is free, it takes a loop of four lines, and it accounts for a large share of "the hardware disagrees" reports.
2. Look at which physical qubits you landed on. Chapter 30 measured a factor of 9.6 between the best and worst two-qubit error rates quoted for a single chip on a single day — 0.00750 to 0.07205. Chapter 29 turned that into an end-to-end result: a hardware-aware layout at optimization level 1 scored 0.9116 against a naive level-3 layout's 0.7720, and a hand-picked qubit chain scored 0.6790 where the calibration-picked one scored 0.9764. A layout difference is routinely larger than the effect you are trying to measure.
3. Predict the fidelity from calibration data and compare it to what you got. Chapter 30's median-error prediction landed within 12% of Chapter 28's measured circuit fidelity. If your observation is within that band of what the device's own numbers predict, the device is behaving as specified and there is no bug to find. This is the closest thing hardware has to a pass/fail assertion.
4. Only now suspect logic — and go back to the simulator with a noise model built from that backend (Chapter 11 §11.7), where you can look again.
⚠️ Common Pitfall: the scaled-down version is not a control.
§26.8 lists "does a scaled-down version work?" as a hardware check, and it is a good one, but Chapter 39 measured its limit and the measurement is blunt. The 24-seed layout experiment above produced a 2.03× spread in error at 14 qubits. Re-run on 4 qubits and the spread is exactly zero — not small, zero, across every seed.
At 4 qubits every layout is effectively equivalent and routing has nothing to do, so the mechanism generating the variation does not exist. A clean 4-qubit reproduction is therefore not evidence about the 14-qubit run; it is a measurement in a regime where the thing you are worried about cannot occur — a measurement that cannot detect the thing being asked about, which is §26.4's blindness wearing a different costume. There the input state was an eigenvector of the bug; here the problem size is below the threshold at which the bug exists.
One more thing worth saying, because it looks like a failure and is not: a mitigation that makes the result worse is information. Chapter 31 applied dynamical decoupling and measured $-0.0053 \pm 0.0012$ — significantly worse, at 4.4 standard errors. That is a real, publishable, useful result about that circuit on that device, and the instinct to bury it as a mistake is the instinct this chapter exists to argue against.
📉 Noise Report: the defect bisection finds in four comparisons is invisible to 1,000 shots.
Put §26.4's bug next to Chapter 27's detection sweep and the case for simulator-first debugging stops being an argument and becomes arithmetic.
Bisection localized the planted wrong angle at operation 5, in 4 comparisons, exactly, with no shots, no noise, and no statistics. Now the sampled version of a comparable defect:
text bug size eps true TVD detected @1k/tol.10 detected @10k/tol.02 0.05 0.0250 0% 86% 0.10 0.0499 0% 100% 0.20 0.0993 52% 100%A real defect of size $\varepsilon = 0.10$ is caught 0% of the time at 1,000 shots with a tolerance of 0.10 — a configuration that also never flakes, which is exactly how it gets adopted. It takes 10,000 shots and a tolerance of 0.02 to see it at all.
And that is still the noiseless sampled case. Add a device with Chapter 30's factor-of-9.6 spread in two-qubit error and Chapter 39's 2.03× layout variation, and the bug is now a small perturbation on top of two larger ones you do not control. Nothing about hardware makes a bug easier to find. Everything about it makes a bug easier to explain away.
💰 Cost and Queue: what debugging in the wrong place costs.
Bisection on Chapter 23's 3,368-gate Shor circuit is 13 comparisons. Price them three ways, using Chapter 39's measurements.
On a simulator, as operators. §26.7's table puts a 10-qubit operator at 507 ms, and prefixes are shorter than the full circuit, so 12 comparisons is a coffee-free few seconds. Exact, and the answer is an instruction index.
On hardware, as jobs. Chapter 39 measured a device utilization of $2.31\times10^{-5}$ at a five-minute queue — the circuit occupies the QPU for 1/43,340th of the wall-clock time you wait. Twelve sequential job submissions at a five-minute queue is an hour of wall clock, and each comparison comes back as a distribution needing a statistical test rather than a yes/no.
On hardware, as billed time. Chapter 39 priced one VQE run three ways depending on the billing model: \$50** per-minute, **\$7,432 per-shot, \$185,542 on a trapped-ion machine. The spread is the point — you can be wrong about the cost of a debugging loop by three orders of magnitude without being wrong about the physics.
Chapter 39 also measured the fix for the wall-clock part: the same 120-iteration VQE took 10 hours submitted as individual jobs and 5 minutes inside a session, and batching 100 circuits into one job was about 99× faster. If you must iterate against hardware, iterate inside a session and batch — and get everything §26.9 steps 1–5 can tell you before you queue at all.
🗝️ Version Note: what changed under this chapter's tools.
Everything in this chapter was run on Qiskit 2.5.1, and three items are worth pinning.
qiskit.pulsewas removed in Qiskit 2.0, takingadd_calibration,.calibrations,backend.defaults,instruction_schedule_mapanddrive_channelwith it. For debugging that means the bottom rung is gone: if you suspect a gate is miscalibrated rather than mis-specified, you can no longer inspect its waveform from Qiskit. Chapter 31 covers what remains.The layout accessors are methods returning lists of integers —
t.layout.initial_index_layout()andt.layout.final_index_layout(), giving[3, 2, 4, 1, 0]and[4, 2, 3]in §26.7. Older tutorials show aTranspileLayoutattribute holding aLayoutobject keyed byQubitinstances; the index-list form is whatOperator.from_circuitconsumes and what you should print.
qc.data[k]yields aCircuitInstruction, not the(instruction, qargs, cargs)tuple that pre-1.0 code unpacks. §26.2's four-lineprefixdepends on this —out.append(inst)works precisely because aCircuitInstructioncarries its own qubit arguments. If you findprefiximplementations that unpack a three-tuple, they predate this and will fail.
26.9 A protocol
Assembling everything, in the order that finds bugs fastest:
1. DOES IT RUN? Diff count_ops() before and after transpilation.
Cheap, and catches Chapter 25's deleted noise slots.
2. IS IT THE RIGHT UNITARY? Operator or process_fidelity against a reference:
a library gate, a small hand-computed case, an earlier version.
NOT against a single "nice" input state.
3. IF NOT, BISECT. Binary search the first divergent instruction, on the
OPERATOR or on a RANDOM input. 13 comparisons for a 3,368-gate circuit.
4. IS IT A CONVENTION? Compare against reverse_bits() and against the
global-phase-insensitive test. Both are one line, and both change the fix
completely.
5. ARE THE ANCILLAS CLEAN? purity(partial_trace(...)) == 1.
A pure state gone mixed on a noiseless simulator is not noise.
6. NOW SCALE UP. Only once 1-5 pass at a size you can simulate.
7. ON HARDWARE, COMPARE DISTRIBUTIONS AND VERIFY CLASSICALLY.
You are no longer debugging; you are validating.
Steps 1–5 all happen on a simulator, are all exact, and are all cheap. The discipline is to exhaust them before running anything on hardware, because once you are on hardware the tools are gone and every symptom looks like noise.
What we measured
Statevector.from_instructionplus a four-lineprefixfunction is a stepping debugger. Its limit is memory: 16 GB at 30 qubits, and hopeless by 40.- Four equality tests disagree on $XZXZ$ versus $I$, and all four are correct. Global phase is
unobservable standalone and observable once controlled —
Operator(ctrl-p) == Operator(ctrl-q)isFalse. - Bisection localizes a bug in $\lceil \log_2 n\rceil$ comparisons: 13 for Chapter 23's 3,368-gate Shor circuit, 26 for a 20-million-gate one.
- ★ Bisecting on states from $|000\rangle$ found nothing — and neither did
$|{+}{+}{+}\rangle$. Both are blind to a wrong rotation angle whose controls are in $|0\rangle$ when
it fires.
|001⟩,|0{+}1\rangle, random states, and the operator comparison all found it at operation 5. - Diagnostics that work:
reverse_bitsequality for bit order; purity 1.0000 → 0.6250 for a dirty ancilla; process fidelity 1.0000 vs 0.2500/0.1547/0.2500 across four QFT conventions;count_opsdiff for deleted gates. - Verifying a transpiled circuit ignoring the layout gives process fidelity 0.001406; with
Operator.from_circuitit gives 1.0000000000. And on a 127-qubit backend the operator cannot be built at all. - Tomography costs $3^n$ settings for states and $\sim 12^n$ for processes — 3.5 billion at 20 qubits. $12^n = 4^n \cdot 3^n$: four input states and three measurement bases per qubit.
- ★ Blindness has an exact condition. A bug is invisible on $|\psi\rangle$ precisely when
$|\psi\rangle$ is an eigenvector of the discrepancy operator $E = P^\dagger Q$. Deriving the
eigenspaces of this circuit's $E$ reproduces both measured counts exactly — 9 labels with
$q_0 = |0\rangle$ plus
++1and+++gives 11/27, and 4/8 in the computational basis. It is the same theorem as Chapter 25's eigenstate result and Chapter 19's diagonal phase oracle, which is 8/8 blind rather than 4/8. - 0 of 100 random states blind is measure zero, not luck — the blind set is a union of proper subspaces. Floating point widens it into a shell about $6.11\times10^{-5}$ radians thick at tolerance $10^{-9}$, which is why you use more than one random input.
- An $n$-qubit operator costs exactly what a $2n$-qubit state vector costs, $16\cdot4^n$ bytes.
Measured:
Operator()takes 9.7 s at 12 qubits, 37.5 s at 13, and 125.7 s at 14 — about 19,000× the state vector for the same circuit.OPERATOR_QUBIT_LIMIT = 14is a patience limit, not a memory one. - Clifford tableau equality is input-independent and scales: a planted $S$ gate caught at
200 qubits in 28.1 ms, where
Operator()at 20 qubits would need 16 TiB. - A rotation error $\varepsilon$ gives $\text{TVD} = \sin(\varepsilon)/2$ — matching Chapter 27's measured sweep to four decimals — so sampling needs $N \gtrsim 36/\sin^2\varepsilon$ shots: 360,012 for $\varepsilon = 0.01$, against one exact operator comparison.
- 0.001406 is 1.44× the $1/d^2 = 0.000977$ you score against an unrelated 5-qubit unitary. Compute $1/4^n$ before believing a catastrophic fidelity.
The theme: debug where you can see, validate where you cannot — and the tool you use to find bugs is itself a program that can have the bug you are looking for.