Skip to content

Fail loudly when DEM construction meets a gate Pauli propagation cannot represent - #600

Open
ciaranra wants to merge 2 commits into
devfrom
dem-fail-loud-nonclifford
Open

Fail loudly when DEM construction meets a gate Pauli propagation cannot represent#600
ciaranra wants to merge 2 commits into
devfrom
dem-fail-loud-nonclifford

Conversation

@ciaranra

Copy link
Copy Markdown
Member

Closes #594. Extends #549 with the builder-side half.

Problem

The DEM builders classified bare T / Tdg -- and rotations with non-Clifford angles -- as ordinary one-qubit noisy fault locations, while the Pauli propagator had no arm for them and silently left the propagated Pauli unchanged. Faults were inserted around a gate that propagation treated as absent, producing a silently wrong detector error model with no error and no warning.

This cannot be fixed by teaching the propagator to conjugate T: T maps Pauli operators outside the Pauli group, so no conjugation rule exists. The input has to be rejected.

Approach

apply_gate (crates/pecos-qec/src/fault_tolerance/propagator/pauli.rs) becomes the single source of truth, returning PauliPropagationOutcome::{Propagated, Unsupported}. Propagated covers gates actually conjugated plus those transparent to Pauli propagation by design; Unsupported covers everything the old code silently skipped.

The transparent set is not a new hand-written list. is_supported_prep_gate (PX, PZ, QAlloc) and is_supported_noop_or_metadata_gate (QFree, I, Idle, TrackedPauliMeta, both measurement-crosstalk payloads) were promoted into fault_tolerance/propagator.rs, and the classification, validate_tick_circuit, and the tick propagator all read that one definition. Maintaining several copies of one gate list is what produced #575.

Every DEM entry point converts Unsupported into a structured error naming the gate and its location, including the independently supplied exact-replay circuit accepted by with_exact_branch_replay_context (and exposed to Python), which was previously unguarded. Tick inputs are validated before DAG conversion so the error reports Tick { tick, gate_in_tick } rather than a node index in a derived DAG.

Validation of gate payloads happens once per circuit at the entry points; the repeated per-measurement propagation walks use a crate-private non-validating dispatcher that remains bounds-safe.

Scope notes

Two behaviours are documented rather than changed, because altering them belongs in separate PRs:

  • R1XY retains its existing 1e-9-turn snapping tolerance while axis rotations require exact angle matching.
  • InfluenceBuilder accepts rotations only at exactly zero angle and rejects every R1XY, including R1XY(0, phi). This errs conservatively, so it is not silent wrongness; the accepted subset is now stated in its rustdoc.

Non-DEM consumers (Pauli-frame path, propagation checker, generic propagation drivers) keep their previous permissive behaviour explicitly.

Verification

  • cargo test -p pecos-qec -- 991 passed, 0 failed
  • cargo check --workspace --all-targets -- clean
  • cargo clippy -p pecos-qec --all-targets -- -D warnings -- clean, run cold
  • Rust doc-test crate -- 176 passed
  • Regression tests cover bare T, both eighth-turn RZ signs, non-Clifford RZ, malformed angle arity, the replay-context path, and QAlloc / QFree circuits building successfully through every DEM entry point
  • Each new guard was mutation-tested: disabling it fails the intended tests

@ciaranra

Copy link
Copy Markdown
Member Author

Merge-risk check: does this reject anything the repository actually builds?

This change makes previously-succeeding DEM construction fail on gates Pauli propagation cannot represent, so the question that matters for merging is whether any real workflow now errors. Answered two ways.

The Python surface pipeline: complete gate inventory

Rather than run a sample of examples and hope they cover the paths, I enumerated every gate the Python surface generators can emit and checked each against the new classification.

Gate-emitting calls in python/quantum-pecos/src/pecos/qec/surface/*.py:

cx, h, mz, mz_free, pz, qalloc, sz, szdg, szz, szzdg, x, z, tracked_pauli

plus the generic add_gate, which is used at exactly two sites (circuit_builder.py:2026 and :2029) with GateType.SX and GateType.SXdg.

That is the complete vocabulary: CX, H, MZ, MeasureFree, PZ, QAlloc, SZ, SZdg, SZZ, SZZdg, X, Z, TrackedPauliMeta, SX, SXdg. Every one of them classifies as Propagated -- the Cliffords and measurements through apply_named_gate, and PZ / QAlloc / TrackedPauliMeta through the shared transparent predicates. No surface-code workflow is rejected by this change.

QAlloc is worth calling out: an earlier revision of this PR omitted it from the transparent set, which would have rejected every circuit these generators produce. The Rust test suite did not catch it because Rust test circuits allocate with pz() rather than qalloc(). That is why this inventory is by enumeration rather than by sampling, and why the current tests include qalloc and qfree circuits building successfully through every DEM entry point.

Rust examples

influence_builder_example runs clean.

fault_distance_timing fails both on this branch and on a clean dev checkout, with the same underlying error (missing detector id); it is pre-existing and unrelated. The only difference is where it surfaces: on dev it panics inside dem_builder/builder.rs:194, and on this branch the now-fallible API returns the error and the example's own .unwrap() panics on it. Same outcome, better mechanism -- the example simply does not handle the Result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DEM builders accept bare T/Tdg as fault locations while Pauli propagation ignores them

1 participant