open-standards/wai/quantum-cross-simulator/tools/tolerance.py
Transaction Science 3ba49a43ee wai-quantum: the cross-simulator corpus's checks can fail where the review found they could not
Each change below was shown first as a planted fault that the previous tools let
pass, then as a failure of the check it breaks:

- The engine under test is held to answering. A crash, a time-out or a
  refusal that corpus/expected-refusals.json does not list, in its words,
  fails the run; so does an answer to a circuit it must refuse. The bit-exact
  method's refusals are listed by its reader's rule, computed from the exact
  angles. A time-out kills the engine's whole process group.
- The bit-exact method's op-log, run in double precision, must give the
  circuit's exact state: a phase dropped in lowering now fails, where the
  distance and the referee could not see it. Each small-angle gate is checked
  held or dropped, exactly, on every engine.
- repeated_phase_k19 is held to the state with its phase, beside a quarter
  turn that a wrong sign cannot pass, and to the extension's stated figures.
- No closed-form row is exempt for the bit-exact method. QPE, Grover and the
  phase table are checked in integers, mirror_n10 by the referee.
- Matrix product states are charged what each engine reports it truncated;
  a row whose bound cannot see a wrong phase is recorded, not passed.
- Sampled counts are also held as a whole in L1 distance; the 40-qubit
  Clifford outcomes per outcome, per free bit and in number.
- The account of another engine's errors is corrected, and each claim is a
  case of a reproducer committed beside it.
- Timing records where each engine's timer runs (Aer's own experiment time),
  memory the peak of the process that ran the engine, threads what each was
  given, and ops what the engine ran.
- Provenance names the trees as they were on disk, and the toolchain the
  checkout picks. CI covers every kind of check but the wide pairwise one, on
  referee changes too, and its comment says so.
- The self-test injects every fault and requires its own check to fail, on
  its own engine only.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-11-06 03:17:01 -05:00

174 lines
7.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Every tolerance the corpus holds an engine to, each derived from that engine's arithmetic.
None is fitted to a result. Each is a first-order worst-case bound, and so is loose: an engine
that passes is within it, and the run also records how far inside it each engine is. Where an
assumption enters (a library's backward-error constant, which the library does not state), the
function says so and states the number it takes.
Floating point (unit roundoff u: 2^-53 in double, 2^-24 in single). One gate acting on w qubits
at once, applied to a normalised state, errs in the Euclidean norm by at most C(w)·u:
C(w) = (2√2 + (2^w − 1) + 2 + π) · 2^(w/2)
- 2√2 u: a complex product of two rounded numbers (Higham, Accuracy and Stability of Numerical
Algorithms, 2nd ed., §3.6);
- 2^w − 1: the additions of a row of 2^w terms, summed in any order;
- 2 + π: the matrix entries, each within 2u from its sine and cosine, and moved by at most π·u
by the rounding of an angle of at most 2π;
- 2^(w/2): the norm of the matrix of absolute values, at most √(2^w) for a unitary.
Errors of successive gates add (each gate is unitary, so it carries the error before it unchanged
in norm). A fused gate of width w costs C(w) for each gate fused into it, since its matrix is a
product of theirs computed in the same arithmetic.
"""
import math
U_DOUBLE = 2.0**-53
U_SINGLE = 2.0**-24
def C(w):
return (2 * math.sqrt(2) + (2**w - 1) + 2 + math.pi) * 2 ** (w / 2)
def gate_count(steps):
n = 0
for s in steps:
for x in s.get("then", [s]):
if "g" in x:
n += 1
return n
def _first_order(total):
# The bound's own second-order term: (1 + x)·x for x below 1.
return total * (1 + total) if total < 1 else float("inf")
def double_state(gates, n, width):
return _first_order(gates * C(width) * U_DOUBLE)
def single_state(gates, n, width):
"""Single precision (complex64 amplitudes) with the same model and u = 2^-24."""
return _first_order(gates * C(width) * U_SINGLE)
def contraction(steps, width):
"""A tensor network contracted pairwise: a contraction with an index of 2^width values is a
row sum of that many terms, so each step costs C(width). The model assumes, as for a gate, that
the norm of an intermediate's matrix of absolute values is at most √(2^width) times its own;
for a network of unitaries contracted in a sensible order that holds, and the measured
errors (recorded beside it) are the check on the assumption."""
return _first_order(steps * C(width) * U_DOUBLE)
DD_TOLERANCE = 2.0**-52 * 1024 # mqt.core's dd::RealNumber::eps, read from its header
def decision_diagram(gates, n):
"""A decision-diagram engine merges edge weights that lie within its tolerance τ of an entry
of its table, so each weight moves by at most τ; an amplitude is a product of n + 1 weights,
so each gate moves it by at most (n + 1)·τ relatively, and the double arithmetic adds the
state vector's bound."""
return _first_order(gates * (n + 1) * DD_TOLERANCE) + double_state(gates, n, 2)
LAPACK_CONSTANT = 16 # p(m, n) in LAPACK's SVD backward error p(m, n)·u·||A||, taken as 16·m
def svd_steps(steps):
"""The two-site updates a matrix product state makes for a circuit: a gate on qubits d apart is
routed by d − 1 swaps there and d − 1 back, each an update, and the gate itself one more."""
total = 0
for s in steps:
for x in s.get("then", [s]):
if "g" in x and len(x["q"]) >= 2:
total += 2 * (max(x["q"]) - min(x["q"])) - 1
return total
def mps(updates, chi, truncation):
"""A matrix product state: each two-site update is a contraction and an SVD of a 2χ by 2χ
matrix, whose backward error LAPACK states as p(m, n)·u with p "a modestly growing function"
of the shape; we take p = 16·2χ. `truncation` bounds what the engine's truncations moved the
state: a truncation that drops weight w moves a normalised state by at most 2√w (√w dropped,
at most w more by renormalising), and the unitary steps after it carry that unchanged, so
these add. The weights are the ones the engine reports (truncation_from)."""
svd = updates * (C(2) + LAPACK_CONSTANT * 2 * chi) * U_DOUBLE
return _first_order(svd) + truncation
def truncation_from(weights=None, total=None, count=0):
"""2·Σ√wᵢ from each truncation's weight when the engine reports them one by one; from a total
W over `count` truncations, 2·√(count·W), which bounds that sum (Cauchy–Schwarz)."""
if weights is not None:
return 2 * sum(math.sqrt(max(w, 0.0)) for w in weights)
return 2 * math.sqrt(max(count, 0) * max(total or 0.0, 0.0))
def density(gates, n):
"""A density matrix: each gate is applied on both sides (2·C(2)), each depolarizing channel
is a sum of up to 16 Pauli conjugations (16·C(2)); an expectation value of a Pauli string
errs by at most √(2^n) times the error of ρ in the Frobenius norm."""
return math.sqrt(2**n) * gates * (2 + 16) * C(2) * U_DOUBLE
def dem(noise_sites):
"""A detector error model's probabilities. A channel's per-Pauli rate, (1 − √(1 − 4p/3))/2 or
its two-qubit form, is within 3u absolutely; merging two mechanisms, p + q − 2pq, adds at most
3u more; a mechanism merges at most every noise channel of the circuit. Two engines differ by at
most twice that: 2 · noise_sites · 6u, written 16u per site."""
return noise_sites * 16 * U_DOUBLE
# Fixed point: the extension's own bound (quantum-sim.md §5). Per inexact gate, every component
# it writes is within 1 + √2/2 units of 2^-30 of the ideal gate applied to the same values: one
# unit from two products rounded half a unit each, √2/2 from table constants within half a unit.
PER_COMPONENT = 1 + math.sqrt(2) / 2
UNIT = 2.0**-30
def fixed_point_op(n, base, controls, param):
"""The Euclidean bound, in units, for one op of the bit-exact op-log: base gate codes are
quantum-sim.md's (§3.0): I X Y Z H S S† T T† P = 0..9."""
c = len(controls)
if base in (0, 1, 2, 3, 5, 6):
return 0.0 # entries 0, ±1, ±i: exact
if base == 9 and param <= 2:
return 0.0 # P(1) = -1 and P(2) = i: exact
if base == 4:
# H writes both amplitudes of each pair: four components per pair.
return PER_COMPONENT * math.sqrt(4 * 2 ** (n - 1 - c))
# T, T† and P(k) write one amplitude of each pair: two components.
return PER_COMPONENT * math.sqrt(2 * 2 ** (n - 1 - c))
def fixed_point_bound(n, ops):
"""The bit-exact method's distance from the ideal state, bounded by its published per-gate
bound summed over the op-log it ran, each gate carrying the error before it: a fixed-point
gate's matrix is within 2·2^-30 of a unitary, so it can grow that error by at most that."""
total = 0.0
inexact = 0
for base, controls, target, param in ops:
b = fixed_point_op(n, base, controls, param)
if b:
inexact += 1
total += b
return total * UNIT * (1 + 2 * UNIT) ** inexact
def l1_distance(shots, outcomes, alpha):
"""ε with P(Σ|X_k/shots − p_k| ≥ ε) ≤ alpha for counts X of `shots` draws over `outcomes`
categories: Weissman, Ordentlich, Seroussi, Verdú and Weinberger (2003), (2^K − 2)·exp(−shots·ε²/2).
The L1 distance is twice the total variation. No normal approximation. It holds the whole
distribution at once, where the per-outcome test below holds each count alone."""
return math.sqrt(2 * (outcomes * math.log(2) + math.log(1 / alpha)) / shots)
def bernstein(shots, p, alpha):
"""t with P(|X − shots·p| ≥ t) ≤ alpha for X binomial(shots, p), from Bernstein's
inequality 2·exp(−t² / (2(V + t/3))), V = shots·p·(1 − p). No normal approximation."""
b = math.log(2 / alpha)
v = shots * p * (1 - p)
return b / 3 + math.sqrt(b * b / 9 + 2 * b * v)