DEV Community

Two gates that hide a bug from every test you have

A production quantum simulator shipped a gate with an inverted sign. Its test suite passed. Every benchmark scored on measurement counts reported perfect agreement, and would have kept reporting it forever.

Not because the tests were sloppy. Because that class of fault does not live in probability space at all.

Two gates are enough to show it. Apply H, then Rz(θ), to a single qubit, and compare a correct backend against the sign-inverted one:

Two gates: H then Rz(theta). State fidelity follows cos squared of theta and reaches zero at pi over two, while the largest difference in measured probability stays flat at zero across the whole range.

The red curve is the state fidelity, cos²(θ) exactly. The green line is the largest difference between the two backends' measurement probabilities.

Read the crossing point slowly. At θ = π/2 the two states are orthogonal — as far apart as two quantum states are allowed to be — and every measurement probability matches to the last bit.

Both numbers can be written 0.000. Zero state fidelity, zero probability divergence. Same three digits, opposite meanings. That single collision of vocabulary is why the bug shipped.

One word doing two jobs

The word is fidelity, and in practice it names two different quantities:

  • state fidelity|⟨ψ|φ⟩|², which reads amplitudes
  • outcome agreement — whether two backends produce the same measurement statistics

They usually move together, which is why the distinction rarely comes up. On this fault they disagree completely, and any tool scored on counts is blind to it structurally, not by accident.

The instrument that is sometimes silent

The natural next move is to test on something realistic instead of two gates. A QAOA circuit, say, where the fault has room to accumulate.

That turns out to be a worse instrument, not a better one:

Heatmap of the difference in expected cut value between the two backends over an 80 by 80 grid of gamma and beta. Most of the plane is coloured, meaning the backends disagree, but there are uncoloured bands where they agree exactly. A marked circle sits in one of those bands at gamma equals pi over four, beta equals pi over eight.

Every coloured point is an angle where the broken backend gives a different answer. The uncoloured bands are angles where it does not.

The circle sits at γ = π/4, β = π/8 — the tidy angles, the ones you type without calculating. The difference there is 8.9e-16, which is floating point noise. A demonstration built on those angles runs, produces numbers, and proves nothing.

So QAOA screams at most parameters and goes silent at others, and which one you get depends on values you did not choose for that reason. That is the definition of a bad instrument, and it is why the book replaces it with the two-gate witness, which is never silent.

The book

Cover of Quantum Computing for Rust Developers, second edition: a cream cover with an engraved moth above the title

Quantum Computing for Rust Developers — second edition, out this week. Paperback $29.99, Kindle $12.99, 248 pages.

It builds the tooling that can see the difference above, from nothing, in Rust:

  • a statevector simulator to 25 qubits, every standard gate, OpenQASM 3 sign conventions
  • an OpenQASM 2 parser that handles real circuits exported from IBM Quantum — gate aliases, pi/2 expressions, whole-register broadcasts
  • Grover verified against all 2^n targets and QAOA with its classical outer loop, both checked rather than assumed
  • a differential fuzzer with generator, oracle, shrinker and triage
  • Python bindings via PyO3, and CI running cross-backend fidelity tests on every commit

The chapter I care about most is the one on finding real faults in real compilers. Not exercises: public issue numbers you can open and read, in Qiskit and in NVIDIA's CUDA-Q. The fuzzer found a soundness bug in Qiskit's CommutativeCancellation pass — a three-gate sequence equal to an X, cancelled to an empty circuit at optimisation level 2 and above. Confirmed by a core maintainer, fixed in 2.5.1.

The campaigns that found nothing are in there too, with the mutation testing that shows they could have found something. That number is what makes "we found no bugs" mean anything at all.

You should already know Rust. You need no physics and no prior quantum computing.

Why there is a second edition

The first edition used fidelity for both quantities above, in the same book, and the demonstration I wrote to expose the sign bug used the tidy angles — the ones in the blind band on that heatmap.

The second edition keeps that section and tells the truth about it: the angles, the blind band, and what I concluded from a check that could not fail. Nine errors from the first edition are documented rather than quietly patched, because how they were found is the more useful half.

Every figure in the book is now produced by a script that computes each number it plots. The two above are generated by this one — run it and you get these plots.

The question underneath all of it

What change to the code would make this assertion fail?

If you cannot answer that quickly and concretely, the assertion is decoration and the code it covers is untested. You write a test after the code already works, so it passes on the first run — and a test that passes on the first run has never shown you what it would have caught.

Break it on purpose. Watch it fail. Put it back. Two minutes, and it is the only way to know your test has any power.


The fuzzer is CleitonForge. The Qiskit fault is issue #16594.

Top comments (0)