Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

B2 — Noisy circuit verification

Manual: Noise channels, Truncation

The 127-qubit heavy-hex kicked-Ising circuit from Benchmark C gets per-gate depolarizing noise added, and the noise makes the simulation cheaper. At p = 3e-2 the tracked set peaks at 651× fewer terms than at p = 0, and the run finishes 1078× faster, at the same cutoff. Every density-matrix method scales the opposite way.

Tracked-set shrinkage from noise

A Kraus/density-matrix simulator pays for noise twice: it carries a 4ⁿ object instead of a 2ⁿ one, and each channel costs more than a gate. Pauli propagation pays negatively: a single-qubit depolarizing channel rescales a coefficient by 1 − 4p/3 per non-identity qubit on its support, so a weight-w string crossing one noise layer loses (1 − 4p/3)^w, and after d layers a string that held weight w throughout is down by (1 − 4p/3)^{wd}. The filter is exponential in weight, so a fixed min_abs_coeff becomes an effective weight cap that tightens with depth, and the tracked set of a scrambling circuit is dominated by its high-weight tail. One channel runs on every qubit in the support of the gate that just ran, 686 channels per step at n = 127, 13 720 across 20 steps. p = 0 runs as depolarize(0.0) rather than a separate circuit, so the rescale is exactly 1.0 and the p = 0 leg is the same computation as the noiseless one, on the noisy legs’ channel schedule — pinned term for term by a test.

Running it

source .venv/bin/activate
RAYON_NUM_THREADS=1 python examples/b2_noisy_verification/run_b2.py           # ~26 min
RAYON_NUM_THREADS=1 python examples/b2_noisy_verification/run_b2.py --quick   # ~1 s, 20 qubits, writes nothing
python examples/b2_noisy_verification/run_b2.py --figures-only                # re-render the SVGs
pytest examples/tests/test_showcase_b2.py                          # the CI gate

RAYON_NUM_THREADS=1 must be exported before the interpreter starts; the driver refuses to run otherwise. The driver adds one noise call per gate (circuit.depolarize/dephase/amplitude_damping/pauli_channel) and propagates with harness.run_propagation(circuit, observable, TruncationSpec(min_abs_coeff=eps), direction="heisenberg").

Terms and time across the noise sweep

θ_h = 5π/16, 20 Trotter steps, Z_62, Heisenberg, contracted against |0…0⟩, single-threaded, fixed min_abs_coeff = 2⁻¹⁴:

p⟨Z₆₂⟩final termspeak termsmax weightmean weightwall (1 thread)peak vs p=0
0+0.01613137438634 69814 396 4631910.30531.0 s—
1e-3+0.01299805063820 0989 710 246199.80361.1 s1.5× fewer
5e-3+0.0062808760863 5212 818 675148.6182.5 s5.1× fewer
1e-2+0.002432260559408869 299137.4120.2 s16.6× fewer
3e-2+0.000000000000022 105000.49 s651× fewer

Maximum surviving weight falls 19, 19, 14, 13, 0 and mean weight 10.3, 9.8, 8.6, 7.4, 0, the exponential-in-weight filter acting on the tail directly. Wall time falls faster than the term count (1078× against 651×) because the merge phase sorts a resident sum far out of cache at the larger sizes, so a smaller tracked set pays twice. A density-matrix method at n = 127 is not on the table (4^127 amplitudes), so the comparison is within this engine: p = 0 against the noisy legs in the same sweep.

Convergence across cutoffs

At p = 3e-2 the fixed-cutoff answer above reads exactly 0; the converged answer is 7.8·10⁻⁵.

p2⁻⁸2⁻¹⁰2⁻¹²2⁻¹⁴2⁻¹⁶2⁻¹⁸2⁻²⁰last Δplateau?
00.0119410.0147300.0154810.016131———6.50e-4yes
1e-30.0071010.0117070.0127370.012998———2.61e-4no
5e-300.0054860.0059180.006281———3.63e-4yes
1e-2000.0022720.0024320.002484——5.14e-5yes
3e-200007.156e-57.753e-57.795e-54.23e-7yes

A fixed cutoff is not a fixed accuracy: as p grows the signal shrinks, so the cutoff must tighten with it, and at p = 3e-2 a 2⁻¹⁴ cutoff sits above every contributing coefficient. Noise buys the cutoff reach that fixes this cheaply: the whole p = 3e-2 sweep to 2⁻²⁰ costs 76 s, an eighth of the single p = 0 point at 2⁻¹⁴, resolving the answer to 4.2·10⁻⁷ against 6.5·10⁻⁴ at p = 0; at p = 0 the next dyadic pair is out of reach, projecting ~1.9·10⁸ terms and ~2 h single-threaded. The plateau verdict reuses Benchmark B’s criterion unchanged, and rejects p = 1e-3: its last difference is 2.61·10⁻⁴ but its previous one is 1.03·10⁻³, just over tolerance, so that leg is unresolved rather than quoted.

The same collapse, three other channels

Same circuit, same 2⁻¹⁴ cutoff, same 20 steps, with the depolarizing channel swapped for each of the others the bindings expose:

channel⟨Z₆₂⟩final termspeak termsmax weightwall
depolarize(1e-2)+0.002432260559408869 2991320.2 s
amplitude_damping(1e-2)+0.1252302218823 0962 660 50313103.1 s
pauli_channel(0.002, 0.002, 0.008)+0.002862382863516673 5651316.1 s
dephase(1e-2)+0.0059345569231 9761 198 5311332.5 s

All four collapse the tracked set by one to two orders of magnitude against the p = 0 baseline’s 1.44·10⁷ peak, a property of Pauli-basis noise generally, not of depolarizing noise specifically. amplitude_damping moves ⟨Z₆₂⟩ upward, to +0.125, since it relaxes toward |0⟩, the +1 eigenstate of Z, and is the only channel here neither self-adjoint nor key-preserving, its Heisenberg dual Z → (1−γ)Z + γI fanning out — checked against the comparisons page’s cross-engine noise-channel parity results. dephase(1e-2) damps ⟨Z₆₂⟩ less since dephasing leaves Z untouched.

Claimable results

At p = 0 the noise model must vanish and this showcase must return Benchmark C’s claimable numbers, read from C’s committed summary.json at run time rather than transcribed: the two 5-step configurations reproduce C’s exact causal-cone references (a 19-qubit reduction cross-checked between an Aer statevector and an untruncated Pauli propagation) to gaps of 3.9·10⁻¹⁵ and 2.2·10⁻¹⁶ against a 0.01 bar, and the 20-step configuration reproduces C’s self-converged 20-step run to a gap of exactly 0.0. Against the exact 5-step reference, ⟨Z₆₂⟩ departs from it linearly in p over two decades (2.2e-16 at p = 0, 1.1e-5 at 1e-6, 1.1e-3 at 1e-4) while the term count holds at 2 146 424 throughout, since at 5 steps the sum has already saturated.

The circuit is ingested, not reconstructed: the 144-edge Eagle coupling map comes from a checked-in file generated from qiskit-ibm-runtime’s FakeSherbrooke, and Z_62 is loaded through a provenance-tagged JSON of published operator supports. The noiseless configuration is claimable at θ_h = 5π/16, 20 steps (⟨Z₆₂⟩ = +0.016131374386, converged from 2⁻⁸ to 2⁻¹⁴ with a last difference of 6.50·10⁻⁴, agreeing with Benchmark C’s independent reference to 0.0), and so are the noisy configurations whose sweeps plateaued, the ones a hardware comparison would actually want, since a device is noisy:

configuration⟨Z₆₂⟩uncertaintyplateau at
p = 5e-3, 20 steps, 5π/16+0.0062808760863.6e-42⁻¹⁴
p = 1e-2, 20 steps, 5π/16+0.0024836896865.1e-52⁻¹⁶
p = 3e-2, 20 steps, 5π/16+0.0000779526534.2e-72⁻²⁰

p = 1e-3 at 20 steps is not claimable, since its cutoff sweep never plateaued, and neither is θ_h = 7π/32 at 20 steps noiseless: it is Benchmark C’s measured reachability boundary.

Reachability boundary, with and without noise

Running that 7π/32, 20-step circuit noisily at p = 1e-2 shows what noise does to the boundary. At the same 2⁻¹⁶ cutoff, the noiseless run (Benchmark C) peaks at 45.4 million terms and ends at 38.8 million, last difference 1.44e-1; the noisy run peaks at 0.94 million, ends at 74 717, last difference 9.32e-4 — 48× fewer peak terms, 519× fewer resident at the end, 154× smaller last difference. It still does not pass, since the second-to-last difference is 1.08·10⁻³, over the 1e-3 bar, so converged = false. ⟨Z₆₂⟩ under the noisy channel is also a different quantity from the unitary-circuit value, agreeing only as p → 0, but it is the quantity a hardware verification claim actually needs.

Performance

Peak resident terms range from 14 396 463 at p = 0 down to 22 105 at p = 3e-2, and wall time from 531.0 s down to 0.49 s. Process RSS peaks at 1.25 GiB across the full driver run, which covers 31 records (the noise grid, the channel variants, the noiseless-limit and reachability legs, and the convergence sweeps) in 25.7 min single-threaded end to end, on ccqlin038, rustc 1.94.0, Python 3.11.11.

Validation: an independent dense noisy reference

Every oracle in the suite refuses a noise channel by construction (the statevector oracle because it is unitary-only, the stim oracle because a tableau simulation samples one Pauli error rather than averaging over them), so B2’s correctness gate hand-rolls a dense reference instead: gates applied to an explicit 2ⁿ × 2ⁿ density matrix, forward from |0…0⟩⟨0…0|, unitaries by conjugation and channels as Σ_k K_k ρ K_k† with Kraus operators from each channel’s definition, not the engine’s dual. Two-qubit Cliffords are sums of embedded Pauli products (CNOT = (I + Z_c + X_t − Z_c X_t)/2, SWAP = (II + XX + YY + ZZ)/2), and with no noise gate the reference must reproduce the engine’s exact untruncated propagation. At 1e-10, for all five channels mixed with h/s/cnot/cz/swap, non-Clifford rx/ry/rz, and a weight-two XY rotation:

directionwhat is comparedsize
heisenberg⟨0…0|Φ†(O)|0…0⟩ from the engine against Tr(O Φ(ρ₀)) from the Kraus evolutionn = 6
forwardevery one of the 4ⁿ Pauli coefficients of the engine’s evolved ρn = 4

29 tests, ~1 s, 97 MiB peak RSS, numpy-only.

Caveats

A truncated Pauli sum has no variational bound, since discarded terms carry signs and the error need not shrink monotonically with the cutoff; converged = false means no usable uncertainty estimate, not a slightly weaker one. The noise model is a model, not a device: a real Eagle device has different error rates, coherent errors, crosstalk, and readout error, none of which is here. Peak, not final, term count is the cost, since the sum grows mid-circuit and collapses by the end.

Numbers: raw records in examples/b2_noisy_verification/results.json, verdicts in summary.json next to it, source and provenance in examples/b2_noisy_verification/README.md.