Case Study 2: Four Conventions, One Right Answer, No Error Message
Why anyone writes their own QFT
Qiskit has QFTGate. Most people should use it.
But there are real reasons to build your own: you want the approximate QFT with a cutoff (§22.5), you are targeting a framework that lacks one, you need to interleave the rotations with something else, or you are teaching it and want the structure visible.
So you write it from the textbook description: Hadamard on each qubit, controlled phase rotations between pairs, and a swap layer at the end. Four lines of nested loop.
And there are four plausible ways to write those four lines.
The four
The textbook says "apply $H$ to each qubit, then controlled rotations from the remaining qubits." Two ambiguities compound:
Which order do the Hadamards go in? Ascending qubit index or descending?
Do you need the final swap layer? Some presentations include it, some note it can be omitted "if you reverse the qubit ordering," which is not a statement you can act on without knowing which ordering the rest of your code assumes.
Measured against QFTGate at $n = 4$:
convention fidelity vs QFTGate
ascending + swaps 0.264679
DESCENDING + SWAPS 1.000000 <- the right one
descending, no swaps 0.250000
ascending, no swaps 0.250000
One of four is right. The other three produce a valid circuit, a valid unitary, and a wrong answer.
Why this is worse than it looks
No error. All four circuits compile, transpile, and run. Every one is unitary. Every one has the right gate count. A test that checks "does it produce a normalized state" passes for all four.
The wrongness is not obvious. A fidelity of 0.25 or 0.265 does not scream "you have the qubit order backwards." It looks like a subtle problem — a phase convention, a normalization, a rounding issue — which sends you looking in exactly the wrong places.
And $1/4$ is a suspiciously reasonable-looking number. For a 4-qubit system, $1/4$ is $1/n$, which invites the thought that something is being averaged or that only one qubit's worth of the transform is landing. Both are wrong, and both are plausible enough to spend an afternoon on.
It gets worse downstream. A wrong QFT inside phase estimation does not produce garbage — it produces a number, systematically offset. Chapter 23's period-finding would return periods that fail the classical verification step, and the natural diagnosis is "the period-finding is unreliable, we need more shots" rather than "the transform is transposed."
The general shape: a convention with more than two options, where the wrong choices produce plausible-looking output.
Chapter 14's endianness had two options and a 50% chance of stumbling into the right one. Here there are four, and the three wrong answers cluster around a value that looks like a partial success.
The check that settles it
Compare against the DFT matrix. Not against another implementation — against the mathematical definition:
$$\text{QFT}\,|j\rangle = \frac{1}{\sqrt N}\sum_k e^{2\pi i jk/N}|k\rangle$$
def dft_matrix(n):
N = 2 ** n
return np.array([[np.exp(2j*np.pi*j*k/N) for k in range(N)]
for j in range(N)]) / np.sqrt(N)
assert np.allclose(Operator(my_qft(n)).data, dft_matrix(n), atol=1e-10)
Six lines, and it is unambiguous. The DFT matrix has no convention to get wrong — it is the definition.
vqelab/qft.py runs exactly this as verify_against_dft(n), and its test suite includes one that
asserts the three wrong conventions are detectably wrong — so the trap is documented in executable
form rather than in a comment nobody reads.
Why a reference implementation is not enough
The obvious alternative is "compare against QFTGate." That works, and it is what the checkpoint's
first test does.
But it only tells you that you match Qiskit, not that either of you matches the mathematics. If you are porting to a framework with a different convention — and Chapter 18 §18.2 measured that Qiskit is the endianness outlier among three frameworks — then "matches Qiskit" and "computes the DFT" can come apart.
Compare against the definition. It is the same amount of work and it settles a strictly stronger question.
This is Chapter 7 §7.7's reference-value habit, applied to a circuit rather than an expectation value: compute the right answer independently, by the most direct means available, and check against that.
The approximate QFT makes it worse
If you build the AQFT (§22.5), you now have a cutoff parameter — and a wrong convention and an aggressive cutoff produce similar symptoms: reduced fidelity that improves as you add rotations back.
n = 8, correct convention
cutoff fidelity
1 0.458548
3 0.970902
7 1.000000 <- reaches exactly 1
The diagnostic is the endpoint. A correct implementation reaches fidelity exactly 1.0 at cutoff $n-1$. A wrong convention plateaus somewhere below.
test_aqft_fidelity_increases_with_cutoff asserts both properties — monotonic increase and reaching
1.0 — which catches a convention error even when the cutoff logic is fine.
The lessons
Verify against the definition, not against an implementation. Matching a reference implementation proves agreement; matching the mathematics proves correctness. They differ exactly when the reference has a convention.
Conventions with more than two options are more dangerous than binary ones, because you cannot stumble into the right answer and the wrong answers can cluster around plausible values.
A plausible-looking wrong number is worse than an obviously wrong one. 0.25 invites explanations. 0.0 would have been diagnosed in a minute.
Assert on the endpoint, not just the trend. An approximation scheme should reach the exact answer in its exact limit, and testing that is what distinguishes "my approximation is coarse" from "my construction is wrong."
And this is the third time this book has hit a silent convention mismatch — Chapter 14's endianness between frameworks, Chapter 18's endianness through OpenQASM, and now qubit ordering inside a single framework. In every case the fix was the same: check against something that has no convention.
Reproduce it: code/example-01-qft-and-the-readout-problem.py measures all four conventions;
verify_against_dft() in code/project-checkpoint.py is the check.