Absorb t corrections - #4
Merged
Merged
Conversation
In unbounded-weight mode (no Clifford products in the circuit), T gate failures no longer inject a physical S gate. Instead, a per-qubit parity flag is toggled at failure time (O(weight)) and applied just-in-time when each subsequent product is first scheduled, patching its operators in-place (X↔Y per the S conjugation tableau; Z unchanged). Two failures on the same qubit automatically cancel via XOR (S²=I). The jit_corrected guard prevents double-application on T gate retries. Bounded-weight and --no-t-failures modes are unaffected; verified bit-identical output against baseline.
The cultivation-time distribution file is now written only when -C / --record-cultivation-dist is passed; it is suppressed by default.
… product is_unbounded_weight_mode() was iterating all circuit products on every call, which was invoked for every product on every lcycle via apply_jit_s_correction. Now computed once at construction and stored as a bool field.
In bounded-weight mode each T gate failure costs an extra physical S correction product, so t_gate_failures was added to the reported count. In unbounded-weight mode failures are absorbed into the Pauli frame with no extra physical products, so the count should equal the circuit product count regardless of how many failures occur.
Toggle runtime_s_parity on T gate failure in bounded mode so that subsequent non-Clifford products receive the same X↔Y JIT correction already applied in unbounded mode. Clifford products are skipped in apply_jit_s_correction (parity propagates past them; the carry-forward routing handles their S correction physically). Remove the extra t_gate_failures term from the scheduled-product count — S corrections are now absorbed into the Pauli frame in both modes.
When a JIT S correction transforms a single-qubit product's operator to match the last completed single-qubit measurement on the same qubit, the product outcome is already known and it can be skipped without routing or consuming a magic state. Track last_completed_basis per qubit (Some(basis) after single-qubit non-Clifford products; None after Cliffords or multi-qubit products, since joint measurements leave qubits entangled rather than in individual eigenstates). Check after apply_jit_s_correction in sched_remaining; handle dropped products in complete_lcycle via dropped_products_this_lcycle buffer for purging, child unlocking, lcycle_scheduled, and scheduled_products.
After a T gate recovery lcycle in bounded mode, the carry-forward routing physically applies the S correction as part of the retry path. The qubit is restored to the same state as a first-attempt success. Clear runtime_s_parity for the recovered qubit so downstream products are not incorrectly JIT-corrected on top of the already-applied physical correction. Detected by tracing the bounded circuit qft_N016 with --log-scheduler info: pp5 (1Y<T>) was being JIT-corrected Y->X after pp3's recovery + pp4 (circuit S gate), putting it in the X terminal while the qubit was in a Y eigenstate. Also demote last_completed_basis update logging from info to debug level.
A T gate failure toggles runtime_s_parity (pending JIT S correction) and previously also set last_completed_basis[q] = Some(basis) via the normal update_last_completed_basis path. This caused a cascade of wrong drops: the JIT correction flipped the next T gate's basis, which then matched the failed gate's recorded basis, triggering a drop that treated the measurement as deterministic success. The S correction is a rigid rotation of the entire Bloch sphere: every subsequent axis rotates by the same amount, so the relative orientations between adjacent measurements are preserved. A well-compiled circuit has no two adjacent same-axis T gates, and this rotation cannot create any. There is therefore no valid drop opportunity immediately following a no-retry failure. Fix: track no-retry failed T gate IDs separately in process_t_gate_outcomes and, in complete_lcycle, exclude them from update_last_completed_basis while explicitly setting last_completed_basis[q] = None. Drops after a subsequent success are still valid and still fire (that path goes through normal update_last_completed_basis on the successful gate). Also in this commit: - Remove unbounded_weight_mode flag; replace with per-T-gate Clifford child check so the retry-vs-JIT decision is made per gate - Track t_gate_retries count; print retry and drop percentages in stats - Add Clifford eigenstate propagation in update_last_completed_basis for single-qubit S/SX gates (S: X<->Y, Z->Z; SX: Y<->Z, X->X) to enable valid drops in T->Clifford->T sequences - Remove stale bounded/unbounded mode tests and comments
The cultivation-time distribution file is now written only when -C / --record-cultivation-dist is passed; it is suppressed by default.
When a T gate injection fails, instead of immediately inserting an S gate, track a per-qubit S^k correction power (0-3) and conjugate future T gates just-in-time (X<->Y swap for odd power). The correction is deferred until a single-qubit Clifford (S/SX) is encountered on that qubit, at which point a physical S gate is emitted before the Clifford proceeds. Key properties: - Consecutive T failures on the same qubit cancel in pairs (power mod 4) so no correction gate is needed when power reaches 0 or 2 (Z, classical) - The correction gate count is bounded by N_SX/2 where N_SX is the number of S/SX Cliffords in the circuit; measured ratios are 0.40-0.71 across 10 max-weight-1 benchmark circuits - CX gates are unaffected; corrections only apply to S/SX single-qubit Cliffords Also fixes: - Progress bar overflow when dynamically added correction gates increase scheduled_products.len() past tot_pps_to_sched - Plotting branch entered spuriously when plotting==false - sched_circuit returned inflated count (loaded + failures) instead of unique scheduled products - Deadlock: mark_blocked_product_as_used was called even when correction S gate emission failed, blocking the qubit node unnecessarily - Panic: schedule_lcycle panicked when pp_paths was empty and magic was available, but no T gates were actually pending (only deferred Cliffords)
Two bugs caused "Cannot schedule on current layout" errors when magic states are produced faster than they are consumed: 1. terminal_nodes vacuous-truth false-negative: when a correction S gate's terminal has no same-row routing neighbor in its precomputed root_info (because sched_s_sx uses any same-row neighbor, not just routing nodes), both preferred and side lists are empty. iter().all() over an empty iterator is vacuously true, so the "all roots occupied" guard fired unconditionally, making try_emit_s_correction always fail even when all nodes were free. Fixed by guarding the check with (!preferred.is_empty() || !side.is_empty()). 2. Outer-loop cultivation check too strict: with very high -m, magic is produced instantly so is_cultivating is always false. When a correction S gate emission fails and only Cliffords remain in pps_pending, the scheduler returned an error instead of continuing. Fixed by only checking cultivation when T gates are pending (T gates need magic; Cliffords don't).
The failed_t_paths recovery mechanism was the old way of physically applying S corrections: when a T gate failed, a second routing lcycle reused the trimmed path to complete the correction. With lazy correction tracking, this is unnecessary — the S correction lives in correction_power and is only emitted as a physical gate when a Clifford is encountered. Remove failed_t_paths, recovery_t_ids, and all carry-forward logic for recovery T gates. T gate failures now complete in exactly 1 lcycle (same as successes), with correction_power incremented classically. Children are unlocked immediately after the lcycle. Result: max-weight-0 circuits (no Cliffords) show ~1.000x lcycle ratio with vs. without failures. Max-weight-1 circuits show ~1.000x lcycles with a small number of extra S correction gates at Clifford boundaries.
When plotting lcycles/ancilla_qubits with ratio file pairs, both series are ratio series on the same axis. The ancilla_qubits fit was picking ratio_series[0] (the lcycles/Time series) instead of the ancilla_qubits series, placing the fit line at the wrong height. Fix by tagging each Series with its y_key and filtering to the ancilla_qubits series when selecting data for the fit.
For circuits like QFT, Max simultaneous qubit usage (Q) is less than the actual number of data qubits (e.g. Q=14 for a 20-qubit QFT), causing FLASQ points to be plotted at the wrong x positions when using -x data_qubits. Switch to parsing Circuit qubits (n_qubits) which always matches the data_qubits values from the BUS/PureMagic output files.
apply_t_conjugation was called unconditionally for every pending T gate every lcycle, allocating multiple Vecs and recomputing terminals/root info even when correction_power was all zeros. For max-weight-1 circuits with many T gates across many lcycles this added ~40% overhead. Add conjugated_t_products to track which products have stale precomputed terminals, and skip apply_t_conjugation entirely unless an odd correction power is active or a prior conjugation needs restoring. Also simplify the conjugation to a single pass, eliminating one Vec allocation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.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.
Absorb the T corrections into a Pauli frame instead of inserting S corrections, except in the case of following Cliffords.