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 41 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.
…ia assumption-gated bounds" This reverts commit 646c251.
|
Incremental SAT certification: tried, measured, reverted. The hypothesis: Same machine, same run, gross code [[144,12,12]]:
25% SLOWER where it was built to win, break-even on [[72,12,6]] (345 vs 349 ms). Plausible mechanism: the fresh path builds a sequential counter only to width w each round, while the incremental solver carries the full-width-12 counter from the first solve, taxing every propagation; MiniSat-class clause retention across assumption bounds did not recover the difference. Per the profile-before-optimize rule, the switch is reverted ( |
|
Final batch of the improvement list. Per-observable DEM fault distances + the recommended method reaches Python. Non-CSS distance certification. Deprecation cycle for Also from this batch, recorded rather than shipped: the incremental-SAT experiment measured 25% slower on its target workload and was reverted (see the earlier comment) — the improvement list's performance item closes with a documented negative result rather than a regression. Verification on the final state: |
|
Code-level connected-cluster distance added — valuable, with an honestly negative performance headline. What landedThe DEM-level connected-cluster engine is now shared with code-level distance: Measured performance — the hypothesis failed
The expectation that native connected-cluster would outrun the SAT path on bivariate bicycle codes does not hold in this implementation: cluster growth cost climbs steeply with distance (gross weight 7 alone: 138 s). For exact distance on BB-family codes, the in-tree answer remains the SAT path, with the WCNF export to external branch-and-bound MaxSAT solvers for the faster regime. This is the second externally-inspired performance hypothesis to die on measurement here, and both are recorded rather than shipped. Why it staysThree independent exact methods now cross-validate: weight search, SAT with native witness certification, and connected-cluster. That third arm has immediate teeth — a seeded property test agrees with the exhaustive DIMACS minimum across random (H, L) pairs, and dropping the L-row adapter or the Y mechanisms is caught by four and two tests respectively. No solver trust anywhere in the CC path. The YY codeThe five-qubit code proved structurally unable to catch Y-handling mutations (ten Y-free weight-3 logicals exist — enumeration during review refuted the originally proposed mutation claim). The new fixture is minimal and forcing: n=2 with single stabilizer Y0 Y1, where every weight-1 X-only or Z-only operator anticommutes with the stabilizer while Y0 and Y1 are logical — true distance 1, every minimum witness a Y. Omitting Y mechanisms provably flips the answer, and the test catches exactly that, alongside a mechanism-level guard pinning each Y_i's detector/output sets. Verification
|
…tsat at upstream arithmetic in debug
|
The connected-cluster search is now driven by unsatisfied detectors, and the performance story inverts. The changeThe previous engine grew clusters by extending with any adjacent larger-index mechanism — enumerating enormous families of connected sets whose syndromes can never close. The search now selects the lowest-index unsatisfied detector and branches only over mechanisms incident to it: every branch must make progress toward closing the syndrome, so the branching factor is one check's degree and dead branches die immediately. Completeness holds because any valid completion must include at least one incident mechanism of the chosen unsatisfied detector; the minimum-index-seed canonicalization is unchanged, as is peeling, determinism, and every public signature. A second pruning rejects detector-free prefixes below the exact target weight, justified by the component/minimality argument under ascending-weight search. Method per the connected-cluster approach of arXiv:2603.22532. Measured (release, re-run independently)
The gross code's exact distance-12 certification — every weight's absence proof plus the witness — now completes natively in half a minute, with no solver in the loop and no trust asymmetry. This supersedes the earlier finding that the cluster search was the slow path: the deficit was the branching rule, not the method. Mutation evidence
Verification
|
… 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). |
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