Distance finding and fault-tolerance tooling: Rust engine, typed inputs, DEM fault distance, docs - #415
Distance finding and fault-tolerance tooling: Rust engine, typed inputs, DEM fault distance, docs#415ciaranra wants to merge 53 commits into
Conversation
…c matrix input for code specification
…agating multiple faults
|
Two fault-tolerance correctness fixes have been folded in (previously #441 and a follow-on), alongside the distance tooling. Multi-fault propagation
Faults are now injected at their own tick and before/after position. The duplicated Pauli-injection mapping that let the two functions drift is now a single shared helper. Single-leg fault enumeration
These two bugs were masking each other. Three tests described injecting a single data-qubit X, the iterator actually produced The DAG path was already correct ( What the enumeration fix surfacedThe omission erred toward false confidence: faults that are never enumerated cannot be found to break a circuit.
Conversely No test containing an actual fault-tolerance assertion fails. Reported counts move widely, as expected when more faults are tested — for example Two follow-ups worth separate attention, deliberately not changed here:
Verification
|
|
Hook-error diagnosis added ( What it reports
A hook error is defined as a fault whose OWN Pauli weight is exactly 1 but whose propagated support on the data qubits has weight at least
This is why circuit fault distance falls below code distance, so the point of the diagnostic is attribution: not "your fault distance is 3 rather than 5", but which gate makes it so. Design notesIt is a readout over existing machinery, reusing
This diagnosis is only meaningful because of the single-leg enumeration fix in this PR: an ancilla-only fault on a CX was previously unreachable, so the analysis would have found nothing and looked correct doing it. Rust-only for now. Verification
|
|
Circuit fault distance and DEM search pruning added. Circuit fault distance as a number, per logical
Added The existing single-weight methods are untouched — they are shipped API with dependents. Mutation-verified: collapsing the weight loop to a single weight turns the discrimination result from DEM cross-validation was investigated and deliberately NOT added: the two fault models are not directly comparable. Connected-cluster pruning for the DEM search
Added
Measured on a 594-mechanism DEM with non-peelable cycle padding, so the numbers isolate the connectivity gain rather than peeling collapsing the input:
About 38x, same answer. At weight 4 the pruned search completes in 0.8 ms; the blind search would have to consider 5.13e9 candidate subsets. A bug this work exposed in the existing codeThe extended property test caught an inconsistency introduced earlier in this PR. Only the randomised generator surfaced this: it needs a DEM with a hyperedge AND a weight-1 detector-free mechanism together, which was case 27 of 512 and which no hand-written fixture had produced. TestsThe seeded property test now generates hyperedge DEMs as well as graphlike ones, and asserts the blind and pruned searches agree on distance and solution existence for every case, with the graphlike method additionally agreeing where applicable. Plus fixtures for peeling reaching a fixpoint without changing the distance, and peeling preserving a witness whose detectors are all shared. Mutation-verified: making peeling over-prune (deactivating a mechanism whose detectors are all shared) fails four tests including the property test and both distance-3 witness fixtures. Verification
|
|
Flag fault-tolerance verification added ( What it checks
The At Scope limitation, stated rather than impliedThe paper's definition also requires that a fault-free run does not flag. That is not checked here, and cannot be: The verdict field is therefore named TestsSix tests, built around a discriminating pair rather than a single circuit: the standard single-flag weight-4 stabilizer measurement satisfies the condition at Also: the stabilizer-equivalence case above, restriction of weights to the caller-supplied data qubits, determinism, and a negative Mutation-verified:
Verification
Python exposure is the outstanding follow-up; |
|
Exact distance certification for qLDPC-scale codes added, closing the algorithm-import item of the distance roadmap. Method choice, from evidence rather than reputationThe large-scale empirical study arXiv:2606.12445 was read before choosing: Brouwer-Zimmermann no longer holds its traditional advantage on qLDPC codes; branch-and-bound MaxSAT wins; scalability is governed by cardinality-constraint handling (sequential counter / totalizer), XOR-aware reasoning does not systematically help; exactness comes from an incremental loop where UNSAT at weight w proves d > w and the first SAT weight is d.
|
| Code | Result | Total time |
|---|---|---|
| BB [[72,12,6]] | d = 6 certified, witness verified | ~0.4 s |
| BB [[144,12,12]] (the gross code) | d = 12 certified, witness verified | ~15 min |
Per-weight profile on the gross code: UNSAT proofs escalate (w=10: 163 s, w=11: 674 s), then SAT at w=12 in 1.3 s — proving d > 11 is where the time goes. For comparison the study's MaxCDCL does this instance in ~48 s, so the pure-Rust backend is roughly 19x off the state of the art but completes the flagship qLDPC benchmark entirely in-process. The WCNF export is the documented path to external MaxSAT solvers for anyone needing the faster regime; both were measured, neither is guessed.
These are regimes the existing weight search cannot touch (C(72,6) * 3^6 alone is ~1e11 candidates).
Verification
cargo test -p pecos-qec: 697 unit + 108 integration + 58 doctests, 0 failures (BB probes are#[ignore]d timing tests, run separately)cargo clippy --locked -p pecos-qec --all-targets -- -D warnings,cargo fmt --all -- --check: cleanjust lintincluding the dependency-integrity gate over the new lockfile entries: clean
Python bindings for DistanceProblem/certified_distance are the noted follow-up.
… its distance cost
|
Generic CSS syndrome extraction via Tanner-graph edge coloration — and its measured distance cost. Any CSS code's (Hx, Hz) now builds a valid memory circuit: exact Delta-edge-coloring of each Tanner graph (Konig; construction per arXiv:2308.08648), each color class a depth-1 CNOT matching, entangling depth DeltaZ + DeltaX. The coloring helper is general graph machinery in pecos-num with its own tests; the detector/observable wiring is factored into shared machinery now used by both this builder and the specialized BB one. Validity is guaranteed and tested (fault-free DEMs are empty; a corrupted coloring is rejected independently by the matching verifier and by TickCircuit's same-tick qubit exclusivity). Distance preservation is deliberately NOT claimed — it is measured, and the first measurement is a finding:
So the generic builder gives every CSS code a correct schedule and, combined with the fault-distance tooling in this PR, a measurement of what that schedule costs — which is precisely the workflow for judging whether a specialized schedule is worth designing. The direction-bracket scheduler family (arXiv:2504.02673) is noted as a possible future addition. Verification: cargo test on pecos-qec and pecos-num clean; cargo test -p pecos --features neo clean (gate-handling blast-radius guard); Python QEC suite 1155 passed; clippy/fmt/build/lint clean. |
|
Bounded-enumeration exact distance (the Brouwer-Zimmermann family) added, CPU only — completing the exact-method portfolio with the dense-code regime. Method per the modern treatment in arXiv:2408.10743, with the random-information-set upper seed of arXiv:2308.15140: greedily peeled disjoint information sets over a kernel basis of H; level loop enumerating d-row combinations per active systematic generator; lower bound LB(d) = sum max(0, (d+1) - (K - r_i)) with even-weight upward rounding; exact termination when LB >= UB. Both bounds are computed natively, so the certificate carries NO trust caveat — unlike the SAT path's solver-trusted UNSAT half. Budget exhaustion returns the honest interval (proven lower bound plus best witness) rather than None. Witnesses are natively verified before acceptance, as everywhere else in this PR. Entry points mirror the connected-cluster family (code/x/z/stabilizer variants, the last via the same three-mechanism reduction, so the five-qubit non-CSS code is covered), with Python bindings. Measured regime map (release, reproduced independently):
Roughly 3,200x over both other methods on the dense case; connected-cluster keeps the sparse regime, as expected and as now documented by data in both directions. The seeded property test cross-validates bounded enumeration against the exhaustive DIMACS minimum alongside the other methods. Mutations: the d+1 -> d lower-bound off-by-one moves a hand-analyzed [6,2,4] termination from level 1 to level 2 and is caught; disabling even-weight rounding demotes an exact certificate to an interval and is caught; disabling the L-trigger is caught by three-way agreement. Verification: cargo test -p pecos-qec full suite clean; workspace clippy -D warnings; cargo test -p pecos --features neo (blast-radius guard); Python QEC suite 1157 passed; build/lint clean. |
|
wgpu backend for the bounded-enumeration level loop — shipped with an honest verdict, and the packet's real win was on the CPU. The seam and the CPU windfallThe level loop now runs through a backend seam: the packed CPU backend (bit-packed word-major rows, prefix-cached XOR walk) is the default, and it transformed the method — a dense [[80,16]]-class instance that previously ran 14+ minutes without finishing now certifies in 91 ms. All existing results are bit-identical; the full regression net passes untouched. The GPU backend, measured on real hardwareThe WGSL kernel (combination unranking, packed XOR, popcount, atomic-min per level; witnesses reconstructed deterministically on CPU so results are bit-identical) was audited on an RTX 4090: the seeded 64-case CPU/GPU agreement suite, level boundaries, and the hand-analyzed termination case all pass on hardware. Performance (RTX 4090, Vulkan, release):
The GPU loses every measured case to a ~270 ms dispatch floor. It is retained anyway, with this framing documented: it is an additive optional backend in pecos-gpu-sims behind a clean seam (no core-path cost, unlike the reverted incremental-SAT experiment), correctness is hardware-verified, and the deep regime is genuinely open — instances exist (by seed draw, not construction shape) where the packed CPU backend still runs 10+ minutes, and the [[80,16]] ratio suggests GPU break-even near CPU-seconds with plausible wins beyond. The CPU backend is the recommendation until such a case is measured end to end. GPU-availability handling follows the repository policy (explicit adapter detection and rejection of software rasterizers; the sandbox environment sees only llvmpipe and skips cleanly — hardware verification above was run outside it). Verification: cargo test on pecos-qec and pecos-gpu-sims audit clean; clippy -D warnings on both; neo blast-radius guard; Python QEC suite 1157 passed; build/lint clean. |
|
Operator coset weights and classical distance — the first packet implemented directly rather than dispatched, and the trust architecture caught its own author. What landed
All four bound to The bug story, told on myselfThe first implementation of the affine path returned None for cosets that provably contained weight-3 elements. The discriminating probe was the architecture's own: Mutations, both killed with sha-verified restores: ignoring the affine targets fails all five coset tests; dropping the nontriviality requirement drives classical distance to 0 against the asserted 3. Verification
|
|
Hypergraph-product code construction — the family unlock, closing the second item of the borrow list.
Oracles, both engines agreeing:
Bound to Verification: pecos-qec + pecos-quantum suites clean; neo blast-radius guard clean; clippy |
|
Subsystem-code dressed distance — closing the fifth borrow-list item via pure engine reuse.
Validation enforces the subsystem structure before searching, with named indices on failure: every gauge generator must commute with every stabilizer, and every bare logical with every gauge generator. Skipping the validation is caught by both rejection tests (mutation run with sha-verified restore). Oracles: Bacon-Shor on the 3x3 grid gives dressed distance 3 (agreed independently by the certified SAT path over the same specification), and the rectangular 2x3 grid gives 2 — the min(m, n) law, measured. The fixtures build the full gauge/stabilizer/bare-logical structure from the grid definition in test code. Rust-only for now (Python binding to follow with the remaining item). Verification: pecos-qec suite clean, neo blast-radius guard clean, clippy -D warnings, build/lint clean. Implemented directly, same packet discipline. Remaining from the borrow list: item 4 (BP-OSD randomized upper bounds for large DEMs), queued as a dispatch packet — it needs decoder-API reconnaissance that a fresh implementer session does best. |
|
The open circuit-distance question is now closed, exactly. BB [[72,12,6]] depth-8 schedule: circuit fault distance = 6 = code distanceThe check-driven connected-cluster search completed the two-cycle circuit DEM (4,284 fault mechanisms, 12,564 contributions) and returned exact distance 6 with the natively verified witness The earlier reported bound of For contrast, the same engine certifies the [[144,12,12]] gross code's CODE distance in about 22 seconds. Circuit-level exactness at this scale is reachable but expensive; the randomized upper-bound path (BP-OSD sampling over the same (H, L) DEM formulation) remains the practical tool for larger circuits and is the one outstanding item on the improvement list. Also in this push: renamed an ambiguous single-letter variable in the certification binding tests (ruff E741 — CI's ruff enforces it, the locally pinned version does not). |
|
Randomized decoder-based upper bounds on DEM fault distance — the last item of the improvement list, and it reaches the regime every exact method in this PR cannot. The result
The two-cycle exact circuit distance is 6, established earlier in this PR by a search that ran about 14 hours. The sampler reaches the same value in 1.5 seconds — roughly a 34,000x reduction in time to that number, with the crucial difference that it is a bound rather than a certificate. The five-cycle DEM is beyond any exact method here; its bound of 6 is reported as a bound and nothing more. Why a heuristic decoder is safe hereEvery candidate passes The API is named and documented so the result cannot be mistaken for an exact distance — Also guarded: bound >= exact on cases where the exact value is known (repetition triad and a distance-3 surface memory, both reaching equality), zero samples returns None rather than a meaningless number, and a DEM with no undetectable logical error returns None however many samples run. Deviation worth noting: seeded randomization currently varies the observable subset only, not per-sample priors or column orderings — a possible future tightening. Verification: |
|
External validation: the exact engine reproduces a third party's published qLDPC code parameters. A publicly available MIT-licensed qLDPC processor toolkit ships finalized code families as raw check matrices (
This matches the published Scaling is roughly 40x per two additional weights of exhaustive proof, which places the larger members of that family (up to No external data or code was copied into the repository; this was a local validation run against the cloned toolkit. |
…ication edge cases
…he enumeration APIs
|
Adversarial review cycle closed: two independent review rounds, fourteen findings, all fixed and regression-tested. Round 1 (10 findings against the recently added distance/coset machinery) and round 2 (4 findings against round 1's own fixes) are both fully addressed. The second round existed because fixes written under review pressure are biased toward the reviewer's exact cases — and it proved the point: round 1 added a logical-count check, and round 2 demonstrated that count without kind still certifies false distances in both directions. The last commit closes round 2:
What the second round confirmed sound, after targeted attack: the gauge-center rank correction under dependent generators, the outcome enums and their Python attribute surfaces, the fallible-API migration (no panicking callers), and all of round 1's edge-case fixes. Full verification on the final state: 775 Rust unit tests plus integration and doctests, regenerated guide doc tests, clippy |
|
Review round 3 — a final scoped pass over the one previously unreviewed diff (the round-2 fixes themselves) — is closed. Two findings, both fixed in the last commit; the declining yield across rounds (10, then 4, then 2) says review has converged. Fixed:
Verified clean under targeted attack, closing the questions round 3 was scoped to answer: discovered logicals are index-paired by the tableau invariant, so the stricter validation cannot reject any discovered spec (exhaustively checked through three qubits, including Y-containing cases); non-CSS handling is intact; budget clamping covers every outcome-producing path; and the subsystem bare-logical pairing argument holds by commutation with the full gauge group, not by fixture luck. Three review rounds, sixteen findings total, all fixed, every counterexample now a permanent regression test. Full gates green on the final commit. |
# Conflicts: # Cargo.lock # Cargo.toml
# Conflicts: # python/quantum-pecos/src/pecos/qec/__init__.py
Distance-finding and fault-tolerance work, consolidated into one PR (previously split as #439 and #440).
1. Expose the Rust distance search and verification workflow
The incremental-weight distance search in
pecos-qecand theStabilizerCodeSpecverification machinery were fully implemented in Rust but had no Python bindings and no callers. This exposes them as a supported replacement for the legacypecos.analysis.VerifyStabilizersdevelopment loop.StabilizerCodeSpecwith a builder:check,logical_z,logical_x, and three build modes —build(),build_verified()(errors name the exact anticommuting generator pair), andbuild_with_discovered_logicals()(derives paired logicals and destabilizers by stabilizer simulation).distance(max_weight=None, css=False, verbose=False)returningDistanceResult;min_weight_logicals();shortest_logicals(delta)for logicals withindeltaof the minimum. The search grows error weight from 1, so cost scales with the distance rather than the qubit count, reaching codes the coset enumeration inStabilizerCode.distance()(capped at k + rank <= 30) cannot.Xs,Ys,Zsmulti-qubit Pauli helpers, so checks read asZs([0, 1]) * Y(2).ParityCheckMatrix(pecos-qec, role-neutral) andSymplecticMatrix(pecos-quantum,[X block | Z block]), with CSS orthogonality validated (Hx * Hz^T = 0, errors naming the offending row pair) and width-bearingzerosconstructors for single-stabilizer-type codes. Phase-dropping conversions are named honestly (to_positive_paulis,from_pauli_sequence_ignoring_phase) because symplectic form carries no sign.StabilizerCodeSpecconstructors now reject linearly dependent stabilizers (DependentStabilizers { rank, count }). Previouslynum_logical_qubits()returnedn - stabilizers.len()while documenting "independent generators", so redundant generators silently corrupted k — which matrix input makes easy to hit.pecos/tools/fault_tolerance_checks.pyandpecos/tools/stabilizer_verification.py, byte-identical dead copies unreachable through the public path (pecos.toolsis a deprecation shim re-exportingpecos.analysis).pecos.analysis.VerifyStabilizersitself is untouched; retiring it is a follow-up now that every capability has a Rust-backed home.2. Consolidate the duplicate searches and parallelize
Two independent implementations of the same weight-increasing search existed. Their predicates were verified equivalent — both test "commutes with every stabilizer generator AND anticommutes with at least one configured logical" — so
StabilizerFlipChecker::{has_undetectable_logical, compute_distance}now delegate to the shared engine via a newhas_logical_error_at_weight. The checker's existing tests are unchanged and act as the regression guard. Thecombinations/pauli_product/build_pauli_stringhelpers are deliberately retained:analyze_weightneeds configurable X/Y/Z subsets the shared iterator cannot express.The per-weight candidate scan now runs on rayon, partitioned over support combinations. Output is bit-identical to serial rather than merely equivalent — reduction is on enumeration index, so the same operator and the same vector order come back, and tests cover both the serial and parallel branch.
PARALLEL_CANDIDATE_THRESHOLD = 65_536candidates at one weight, derived from a measured sweep, not intuition. Below roughly 22k candidates parallelism loses (forcing the toric [[18, 2, 3]] weight-3 tier, 22,032 candidates, parallel made that search 4.6x slower); above roughly 193k it stops engaging where the time is spent (the color [[17, 1, 5]] search is dominated by its weight-4 tier, 192,780 candidates, and a higher gate erased the speedup entirely).shortest_logicalsdelta=1, color [[17,1,5]]This is a trade, not a free win: microsecond-scale searches pay about 10%, while searches long enough to wait on improve 5-13x. Small-code figures were confirmed by an A/B/A run after an initial measurement proved to be machine drift. Adds
benches/modules/code_distance.rs; no distance benchmark existed before.3. Detector-error-model fault distance
Code distance is not circuit distance. A distance-5 code whose syndrome extraction spreads one fault across multiple data qubits can have fault distance 3, so code distance alone can overstate real protection. Nothing in PECOS computed the circuit-level number —
check_undetectable_logical_errorsenumerates failing configurations but never reports a minimum.This adds the DEM level: the minimum number of fault mechanisms whose XOR flips no detector but flips at least one observable. With
Hthe detector-by-mechanism matrix andLthe observable-by-mechanism matrix, that is minimum|e|withH*e = 0andL*e != 0— structurally the same problem as code distance, which is why it lands beside the existing fault-tolerance checkers rather than in a separate silo.graphlike_fault_distanceis exact when every mechanism flips at most two detectors, searching the parity-doubled graph with every detector AND the boundary as a BFS root. Rooting only at the boundary is not exact: a DEM whose minimum cycle avoids the boundary (D0 D1 L0/D1 D2/D0 D2, distance 3) leaves the boundary isolated and finds nothing. That is now a regression test.exhaustive_fault_distance(max_weight)is correct for any DEM including hyperedges;max_weightis required because the cost is combinatorial in the mechanism count.DemMatchingGraphsilently skips hyperedges, so building on it would have returned quietly wrong distances. The implementation readsto_mechanisms()directly and reusesFaultMechanism::{xor, is_graphlike, is_hyperedge}.DistanceResult::min_weight_operator— knowing which faults conspire is the point.Guarded by a seeded property test over 512 random small graphlike DEMs asserting both methods agree on distance and on solution existence. Fixture tests alone shared a blind spot with the original design (every case happened to have a boundary edge); cross-validating an exact special case against a general reference catches the class rather than one instance. Verified by mutation: restricting the roots to boundary-only fails both the boundary-free regression and the property test.
4. Documentation
New
docs/user-guide/stabilizer-code-verification.md, replacing the legacystab_code_verification.rstnarrative on the current API: the builder workflow, the ten-qubit design storyline (anticommuting pair diagnosed, then [[10, 3]] at distance 2, then [[10, 1]] at distance 3), logical-operator exploration, matrix input with its error diagnostics, and a note on choosing between the two distance methods. Ten executable doc tests, no skip markers, wired into the mkdocs nav.Verification
Run on the combined branch:
cargo test -p pecos-qec -p pecos-quantum: no failuresuv run --frozen pytestacross the stabilizer-code binding suites, the fault-distance suite, and the generated doc tests: 40 passedjust build-debugandjust lint(new files staged first, since pre-commit only inspects tracked files): clean