Fix surface-code hook errors with an orientation-aware CNOT schedule#672
Merged
Conversation
The stabilizer-round kernel executed every plaquette's CNOTs in ascending
data-qubit index order (the parity matrices carry no ordering), so both X
and Z plaquettes used the same row-major order. Mid-round ancilla faults
("hook errors") therefore propagated onto data pairs aligned *with* one of
the logical operators, halving the effective circuit-level distance of one
memory basis (measured: 2 instead of 3 at d=3, 3 instead of 5 at d=5 for
prep0/ZH).
- stabilizer_grid::get_cnot_schedule_x/z(): timestep-encoded schedule
matrices (0 = no support, k in 1..4 = CNOT timestep) using the standard
zigzag schedule pair (Tomita & Svore, arXiv:1404.3747; hook errors per
Dennis et al., arXiv:quant-ph/0110143), selected per orientation so hook
pairs land perpendicular to the same-type logical. Rows match the sorted
parity matrix rows.
- code::get_stabilizer_schedule_x/z(): new virtuals, defaulting to the
parity matrices, so the stabilizer_round kernel signature and all other
codes' behavior are unchanged; surface_code overrides them.
- memory-circuit drivers pass schedule matrices to the kernel; the surface
code kernel executes the X and Z checks interleaved over the four shared
timesteps (one depth-4 extraction round; the zigzag pair is a valid
schedule under interleaving).
- realtime app examples and python bindings consume the new grid API
instead of rebuilding ascending-order pair lists.
- tests: schedule properties (parity alignment, hook orientation, per-step
conflict freedom) for d=3,5,7 across all orientations, plus a graphlike
DEM effective-distance check (== d for both bases, all orientations, at
d=3,5) that fails against the previous ordering.
Monte Carlo validation (pymatching, CX depolarization, 100k shots,
rounds=d): at p=0.002 the prep0 logical error rate improves 14x at d=3
(0.030 -> 0.0018) and 5.8x at d=5 (0.0057 -> 0.00098); prepp is unchanged,
and the two bases are balanced after the fix, as expected.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Ben Howe <bhowe@nvidia.com>
Findings from self-review of the CNOT-schedule change: - build_cnot_schedule aligned schedule rows with parity-matrix rows via an unenforced no-tie invariant (stated only in a comment). Enforce it: throw if two same-type stabilizers ever share their first supported data qubit, turning a silent CNOT-to-wrong-ancilla misalignment into a loud error. - Add stabilizer_grid::get_cnot_schedule_pairs_x/z() and replace the five copy-pasted schedule-to-(stabilizer, data)-pair conversions (four C++ app examples plus the python example) with calls to it. - get_stab_data_supports in surface_code-4-yaml now derives supports from the schedule matrix rows, so support[s] aligns with ancilla[s] by construction instead of through a parallel sortStabilizerOps ordering. - Python bindings return the schedule matrices as numpy arrays via the existing copyCUDAQXTensorToPyArray helper, consistent with get_parity_x/z, instead of hand-rolled list-of-lists copies. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
…e CNOT schedule - The stabilizer_round kernel-contract section notes the two vector arguments are schedule matrices and links to code::get_stabilizer_schedule_x, whose Doxygen comment is the single source of truth for the encoding and the parity-matrix default. - The surface-code section documents what is unique to the narrative page: the depth-4 interleaved extraction round, the orientation-aware zigzag schedule and its hook-error rationale (with references), and links to the get_cnot_schedule[_pairs]_x/z accessors. The C++/Python API pages pick up the new methods automatically via doxygenclass/autoclass :members:. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
bmhowe23
marked this pull request as ready for review
July 14, 2026 15:34
bmhowe23
requested review from
eliotheinrich,
kvmto and
melody-ren
and removed request for
melody-ren
July 14, 2026 15:34
eliotheinrich
left a comment
Collaborator
There was a problem hiding this comment.
Left a few suggestions, but looks good to me. Most substantial is probably the Python support, but that's maybe best suited for a follow-up.
- Hardcode num_steps = 4 in the surface-code stabilizer kernel instead of computing the max schedule entry at runtime - Rename the stabilizer kernel and memory_circuit vector parameters to x/z_stabilizer_schedule to reflect that they carry CNOT schedules - Document the stabilizer_round schedule semantics in the user-defined code section of the QEC introduction - Add Python bindings for code.get_stabilizer_schedule_x/z with a test - Extend checkCnotSchedule to verify the plugin's Z schedule too Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
PyCodeHandle now dispatches get_stabilizer_schedule_x/z to optional same-named methods on the Python code class, validating that the returned matrix matches the parity matrix's shape and support pattern. A plain hasattr check does not work because the @qec.code decorator makes every Python code inherit from the bound Code class, which exposes same-named read-only getters, so the handle walks the class __mro__ to detect a genuine user override. Includes an end-to-end test with a schedule-aware stabilizer_round kernel, negative shape/support tests, and a doc note in the Python code-authoring section. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
anjbur
added a commit
to anjbur/cudaqx
that referenced
this pull request
Jul 16, 2026
…chedule (NVIDIA#672)" This reverts commit 22772f7. Signed-off-by: Angela Burton <angelab@nvidia.com>
anjbur
added a commit
to anjbur/cudaqx
that referenced
this pull request
Jul 16, 2026
…integration Signed-off-by: Angela Burton <angelab@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
The surface-code
stabilizerkernel executed every plaquette's CNOTs in ascending data-qubit index order — the flattened parity matrices it receives carry no ordering information, so an optimized schedule could not even be expressed. With the same row-major order on both X and Z plaquettes, a mid-round ancilla fault ("hook error", Dennis et al., arXiv:quant-ph/0110143) propagates onto a data-qubit pair aligned with one of the logical operators, halving the effective circuit-level distance of one memory basis.Measured impact (before this PR, ZH orientation,
prep0): effective distance 2 instead of 3 at d=3, and 3 instead of 5 at d=5. Which basis is damaged depends on the code orientation;preppwas coincidentally unaffected for ZH.This PR gives the surface code the standard zigzag CNOT schedule (Tomita & Svore, arXiv:1404.3747), selected per orientation so hook pairs land perpendicular to the same-type logical, and executes the X and Z checks interleaved over four shared timesteps (one depth-4 extraction round).
Design, with no breaking interface changes:
stabilizer_grid::get_cnot_schedule_x/z(): schedule matrices with the same shape/support as the parity matrices; entry 0 = no support, k ∈ 1..4 = CNOT timestep. Rows match the sorted parity-matrix rows (enforced: a first-support tie throws instead of silently misaligning ancilla wiring).get_cnot_schedule_pairs_x/z()provides the flat (stabilizer, data) pair-list form used by the realtime examples.code::get_stabilizer_schedule_x/z(): new virtuals defaulting to the parity matrices, so thestabilizer_roundkernel signature is untouched and repetition/Steane receive byte-identical inputs. The memory-circuit drivers pass these to the kernel.get_parity_x/z().Validation:
test_qec_stim): builds the DEM from the actual kernel under two-qubit depolarizing noise and asserts the shortest undetectable logical error has weight d, for d=3,5 × both bases × all four orientations. Fails with weight ⌈d/2⌉ against the previous ordering.test_qec): d=3,5,7 × all orientations — parity-row alignment, valid distinct timesteps, hook-pair perpendicularity, no data qubit touched twice per timestep.Runtime / performance impact
Monte Carlo (pymatching, CX depolarizing noise, 100k shots, rounds = d): at p=0.002 the
prep0logical error rate improves 14× at d=3 (0.030 → 0.0018) and 5.8× at d=5 (0.0057 → 0.00098);preppis statistically unchanged and the two bases are balanced after the fix. The pre-fix DEM also contained parallel edges with identical detector signatures but different observable effects — fundamentally undecodable mechanisms that pymatching's default merge strategy rejects outright; these disappear with the fix.Build/test-time cost: the new tests add ~50 ms to the two unit-test binaries. Kernel classical overhead adds a constant-factor scan per stabilizer round (negligible on stim, which traces the kernel once).
Self-review checklist
Before requesting review
Scope and size
Tests
Negative tests added where exceptions are expected.The new no-tie guard inbuild_cnot_scheduleis unreachable for any valid rotated surface code by construction (geometric argument in the code comment), so it cannot be exercised without an artificial grid; existing invalid-orientation/invalid-distance exception tests still cover the public entry points.EXPECT_*/assertchecks are insufficient for algorithmic correctness. (effective distance == d via graphlike-DEM shortest-error search; Monte Carlo numbers above)Documentation
Code style
snake_case) for the area being modified.Dependencies
🤖 Generated with Claude Code