Skip to content

Absorb t corrections - #4

Merged
stevenhofmeyr merged 22 commits into
mainfrom
absorb-T-corrections
Jul 26, 2026
Merged

stevenhofmeyr merged 22 commits into
mainfrom
absorb-T-corrections

Conversation

@stevenhofmeyr

Copy link
Copy Markdown
Collaborator

Absorb the T corrections into a Pauli frame instead of inserting S corrections, except in the case of following Cliffords.

stevenhofmeyr and others added 22 commits July 24, 2026 03:27
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>
@stevenhofmeyr
stevenhofmeyr merged commit 3e584a6 into main Jul 26, 2026
1 check passed
@stevenhofmeyr
stevenhofmeyr deleted the absorb-T-corrections branch July 26, 2026 05:22
stevenhofmeyr added a commit that referenced this pull request Aug 2, 2026
stevenhofmeyr added a commit that referenced this pull request Aug 2, 2026
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.

1 participant