Skip to content

Allow passing ancillas to Steane class#71

Closed
perlinm wants to merge 54 commits into
PECOS-packages:masterfrom
perlinm:feat-qeclib-steane
Closed

Allow passing ancillas to Steane class#71
perlinm wants to merge 54 commits into
PECOS-packages:masterfrom
perlinm:feat-qeclib-steane

Conversation

@perlinm
Copy link
Copy Markdown
Contributor

@perlinm perlinm commented Aug 26, 2024

This should allow different logical qubits to share ancillas, with something like

program = pecos.slr.Main(
    logical_qubit_0 := pecos.qeclib.steane.steane_class.Steane("lq_0"),
    logical_qubit_1 := pecos.qeclib.steane.steane_class.Steane("lq_1", ancillas=logical_qubit_0.a),
    ...
)

Warning: I am not sure how to test for unintended side effects, so I'm not sure whether this breaks anything...

ciaranra and others added 30 commits October 4, 2023 20:32
* Move ruff config to its own file

* Clean up README

* Pre-commit pass on all files

* Add typos pass

* Sync gitignore with GitHub's official Python.gitignore

* Add testing instructions

* Fix docs generation and copyright

* Remove redundant gitignore

* Create python-app.yml

* Also run CI on PRs to dev branch

* Fix testing command

* Install package before running tests

* Cache dependencies for faster CI processing

* Ignore PyCharm idea folder as before

* Also run CI on dev

* Revert and exclude formatting changes to gates table

* Added more to README install section

* Setting the copyrights for The PECOS developers to original 2018

* To .gitignore, added more cython extensions, + vscode + avoiding tracking C++ extern

* Replacing Quantinuum logo with svg file

* Moved custom ignores to the top of file

* fixed README typo

* Auto-generate requirements.txt using pip-tools / pip-compile

* Updating copyright dates as suggested

---------

Co-authored-by: Ciaran Ryan-Anderson <ciaran.ryan-anderson@quantinuum.com>
* Add Makefile with useful commands

* Ignore DeprecationWarning: datetime.datetime.utcfromtimestamp()

* Adding [all] options

* Removing install-all from tests and docs

---------

Co-authored-by: Ciaran Ryan-Anderson <ciaran.ryan-anderson@quantinuum.com>
* Update packages, cleanup ruff.toml

* Adding backing in ANN per directory ignore

---------

Co-authored-by: Ciaran Ryan-Anderson <ciaran.ryan-anderson@quantinuum.com>
* Fix conditional eval oversight

* Cherry-picked from c4e5ed
* Fixing some documentation issues found by doctest + blackdoc

* Expanding Makefile to include additional useful options

* refactor resetting environment tracking attributes in cinterp

* unit and integration test stubs

* Adding comments to makefile + modifying .venv to replace old .venv

* Tweaked Makefile to not both with __pycache__ + added comment + removing VENV_BIN since not used right now

* Assume all tests are integration tests unless proven otherwise later

* remove print statement from cinterp

* Apply suggestions from code review

Co-authored-by: Kartik Singhal <130700862+qartik@users.noreply.github.com>

* Adding docstrings to empty test __init__.py files

* Rewriting to focus on using venv only.

* Updated Makefile given PR conversation. Hopefully improves clarity

---------

Co-authored-by: Ciaran <ciaranra@gmail.com>
Co-authored-by: Kartik Singhal <130700862+qartik@users.noreply.github.com>
* Removing dependencies from README + using | to separate Python versions

* Makefile refactor + help + docs now uses venv

* Testing PHIR. Fixing wasmer/wasmtime pathlib issue

* Added basic PHIR wasm tests + tests-dep to Makefile

* Adding pytest.ini

* Adding tests of PHIR and Wasm

* Move error_params and machine_params to be declased only at the object level

* Making optional dep (ProjectQ) not default.

---------

Co-authored-by: Ciaran Ryan-Anderson <ciaran.ra@gmail.com>
Co-authored-by: Ciaran Ryan-Anderson <ciaranra@gmail.com>
…ages#18)

* Minor fix to PHIR tests + adding pytest as metadependency

* adding optional dep mark to test

---------

Co-authored-by: Ciaran Ryan-Anderson <ciaranra@gmail.com>
* Fixed issue with yielding instructions of blocks

* bump to 0.5.0.dev3 + avoiding [] buffer yielding

* Fixing linting because my pre-commit hook was off
PECOS-packages#19)

* add external phir dependency + remove Python 3.8 + test both required and optional dependencies

* Updating workflow to include diff OSs. Added skipping of tests based on optional dependency OS/Python version support.

* Skip wasmer tests on for Python >= 3.11

* Skipping test if Wasmer could not be imported.

* More robust method for skipping tests of Wasmer due to unsupported systems

* Add simple test of extern phir

* Requirement update to phir 0.1.6

* Included PHIR model validation and related tests

* small bump for requirements.txt

---------

Co-authored-by: Ciaran Ryan-Anderson <ciaranra@gmail.com>
* Added support for qparallel.

* Bumping to 0.5.0.dev4
* Fixed buggy yielding of Sequence/QParallel blocks

* Add a test of the sequence of op buffers.

* Reworked recursive iteration through blocks to be more elegant

* Marked test using state-vec sim as optional dependency

* Added Clifford Bell qparallel test and silence tz warning

* Bumping version to 0.5.0.dev5 and updating requirements
* Fixing support of PHIR's multi-assignment

* bump version to 0.5.0.dev6
…s#38)

* Initial support for barriers (skips barriers for now).

* Additional barrier testing and associated fixes
PabloAndresCQ and others added 7 commits August 5, 2024 09:27
…ECOS-packages#68)

* Added Qulacs simulator

* Added wrapper for QuEST

* Updated readme with simulator installation instructions

* Adding tests for HybridEngine on statevector simulators

* Supporting new statevector sims on HybridEngine

* Fixed a bug in CuStateVec relating to HybridEngine
@perlinm perlinm requested a review from ciaranra as a code owner August 26, 2024 18:26
@perlinm
Copy link
Copy Markdown
Contributor Author

perlinm commented Aug 26, 2024

Oops, meant to open a PR into the feat-qeclib-steane branch. Closing.

@perlinm perlinm closed this Aug 26, 2024
ciaranra added a commit that referenced this pull request May 22, 2026
…e gate over the audit corpus. Honest non-faked baseline (0 compliant; uniform missing-output_labeling_schema cause; build-failure set pinned so a new qir_bc regression trips it); trips deliberately on Stage-B progress. Codex blocker folded + re-confirmed; Pickle signed off. Test-only, production byte-identical.
ciaranra added a commit that referenced this pull request May 22, 2026
…output_labeling_schema=labeled, qir_profiles=adaptive_profile, !llvm.module.flags qir_*_version + dynamic_*/arrays=false). validate_qir 0->27/28; honest two-tier gate (Tier-1 validate, Tier-1b exact entry-attr value pins, Tier-2 qir_to_qis still create_creg until B2; build-fails pinned). Dual post-review: Pickle YES; Codex blocker (presence-only value pin) folded+re-confirmed. QASM/Stim/QC byte-identical; audit/optional_dep unchanged.
ciaranra added a commit that referenced this pull request May 22, 2026
…is__mz__body, __quantum__rt__read_result) for B2b to wire. Decls-only, zero behavior/gate change; verified inert (validate/qir_to_qis split, gate, broad 1124, audit all unchanged). Helper methods deferred to B2b (no dead code).
ciaranra added a commit that referenced this pull request May 22, 2026
…- LLIRBuilder::{alloca,load,store,zext,trunc,icmp_unsigned} + LLConstant::zero (Array->zeroinitializer, Pointer->null) in crates/pecos-llvm; LLType PartialEq/Eq + manual Hash consistent with inkwell's LLVMTypeRef Eq (inkwell 0.8.0 derives Hash only for IntType); 6 pyo3 #[pymethods] + Constant(ty,value=None)->zero + PyLLValue.type getter + structural __richcmp__/__hash__ on Py*Type (PyAnyType gains IntoPyObject so the getter is returnable). Additive only: end-to-end B2-shaped module via the new ops round-trips binding.parse_assembly + qir_qis.validate_qir + qir_to_qis; #71 gate 1 passed unchanged, slr_tests 368 passed, broad qir/qasm/codegen sweep 1785 passed 0 fail; cargo fmt/clippy clean.
ciaranra added a commit that referenced this pull request May 22, 2026
…/Gt/Ge) now return NotImplemented so Python raises TypeError instead of a silently-wrong False; Eq/Ne unchanged (lltype_richcmp -> Py<PyAny>). Verified i1<i64 raises, eq/hash/dedup intact, #75 e2e + #71 gate + slr_tests 368 + broad sweep 1785 all green; clippy/fmt clean.
ciaranra added a commit that referenced this pull request May 22, 2026
…ntime helpers with the standard M-B2-static model. CReg -> entry-block alloca [N x i1] + zeroinitializer; measure -> __quantum__qis__mz__body + static %Result* + read_result + store (no-result mz; slot=measurement_count-1); BitRef/whole-CReg c.set(int) -> store/per-bit lshr+trunc unpack; BitExpr -> load+zext (i64-canonical); _as_i1/_as_i64 via #75 value.type; record pack load/zext/shl/or -> int_record_output (kept); point-of-use GEP. B2c removes the now-uncalled create_creg/get_creg_bit/set_creg_bit/get_int_from_creg/set_creg_to_int/mz_to_creg_bit decls. #71 structural gate honestly rebaselined: _EXPECTED_QIS_OK 7->27, qis_failed empty, BUILD_FAILED 3->2, qeclib.steane_pz triaged build->validate-fail (B2 makes it build; then hits qir-qis allowlist on PECOS's non-standard __quantum__qis__barrierN__body -- pre-existing barrier gap, out of B2 scope). New tier2_semantic.py: real qir-qis-compiler acceptance + emitted-QIR structural invariants + deterministic AST->Guppy->selene cross-anchor (set_int/zero_init A+B-only; Guppy can't return a set(int) CReg). Executable qir_to_qis->selene differential blocked by LLVM 14<->21 / opaque-vs-typed gap -> tracked separately. slr_tests 368, broad sweep 1785/0-fail, ruff/format clean.
ciaranra added a commit that referenced this pull request May 22, 2026
… >64-bit CReg silently miscompiled (creg_map recorded size but alloca/output only when <=64, so CReg(65) built+validated+qir_to_qis-lowered with storage/output silently dropped) -> _process_declarations now raises NotImplementedError for size>64 (single-i64 pack cap; fail-loud, not a silent fallback) + regression test test_oversize_creg_raises_loud (CReg(64) builds, CReg(65) raises). Non-blocking: register `slow` marker in tests/conftest.py::pytest_configure (invocation-independent; canonical sweep 0 warnings; residual single-file pytest-path warning is a conftest-discovery quirk, cosmetic); tighten tier2_semantic Layer B from read_result-subset-of-mz to per-measurement adjacency (_assert_mz_rr_pairing: each read_result immediately preceded by its own same-slot mz), drop dead _MZ_SLOT/_RR_SLOT. Gates: tier2 PASS (8 A/B + tightened pairing + 2 C), oversize regression passes, #71 gate green, slr_tests 368, broad sweep 1785/0-fail, ruff/format clean. Pickle YES; Codex re-confirm pending (blocker fix + regression test were its exact sign-off condition).
ciaranra added a commit that referenced this pull request May 22, 2026
…plementedError BEFORE writing context.creg_map (fail before any partial state). Behaviour-equivalent (size>64 raises regardless; size<=64 unchanged); Codex minor note from codex-71-stageB2-postreview-reconfirm.md. Codex re-confirm: YES (0 blockers); both reviewers now YES -> B2 dual-signed-off.
ciaranra added a commit that referenced this pull request May 22, 2026
…s ->constant 0), While (was silent one-pass dropping condition+iteration), Print (was silent drop losing observable output) now raise NotImplementedError with clear messages -- each was valid-QIR-but-wrong-semantics that qir-qis cannot catch. Bounded scope: real While/scalar-SSA/Print->record are explicitly deferred (v1-feature-matrix: real While "too large for first sound emitter"); min bar = fail-fast not silently wrong (same pattern as B2 >64 cap). qir_bc() does not wrap them (only parse_assembly RuntimeError caught) -> surface at QIR-gen. Deliberate #71 gate rebaseline (triaged like steane_pz): only docs.while_loop exercises a B3 site -> _EXPECTED_QIS_OK 27->26, _EXPECTED_BUILD_FAILED 2->3 (NotImplementedError, "does not support While loops"); aligns QIR path with the Guppy path which already rejects While; 0 corpus Print, VarExpr only via for_loopvar_symbolic (build-fails earlier). Fixed enshrined-bug test: test_print.py test_qir_byte_identical asserted the old silent drop -> test_qir_raises_loud_on_print (Stim/QC byte-identical siblings left, documented out-of-scope). 4 fast regression tests in tier2_semantic.py. Gates: 5 fast + #71 gate green, cross-codegen Print 4 passed, slr_tests 368, ruff/format clean; broad sweep in-flight.
ciaranra added a commit that referenced this pull request May 22, 2026
…compile -- static `For` body dropped. `_process_for`'s `isinstance(node.start, int)` guard was always false (converter wraps For range bounds in LiteralExpr), so every static-For body was silently dropped: valid QIR, wrong semantics, qir-qis-uncatchable -- and docs.for_static_indexing sat in _EXPECTED_QIS_OK on body-dropped QIR (gate dishonest). Proper fix (static fixed-bound For is v1-supported + small, unlike deferred While/SSA): _static_int_bound resolves LiteralExpr(int) start/stop/step; _process_for unrolls range(start,stop,step) exclusive (matches canonical gen_quantum_circuit); symbolic/non-static bound -> NotImplementedError (never silent-drop). _process_repeat left as-is (RepeatStmt.count SLR-enforced int -- never the same bug; bounded fix to _process_for only). Honest resolution, NO baseline shuffle: docs.for_static_indexing stays in _EXPECTED_QIS_OK (26) but its QIR is now correct (body unrolled) -- gate honest by fixing code not the pin. New regression test_static_for_unrolls_body_not_dropped (3 call sites, not the declare; unrolled QIR still qir-qis-lowered). Gates: 6 fast tests + #71 gate green, broad sweep 1785/0-fail, ruff/format clean. Pickle YES; Codex re-confirm pending (its exact sign-off condition).
ciaranra added a commit that referenced this pull request May 22, 2026
…faced (inline/Return-only CReg lost its record; non-Z Prep basis dropped), re-pin #71 gate from actual _qir_state (3 programs QIS_OK->BUILD_FAILED)

qir.py: _require_creg() helper raises NotImplementedError at the 4 unknown-CReg silent-skip sites (_process_measure, _process_assign x2, _eval_expression BitExpr -- the last a #74-class silent Constant(0)). converter.py::_convert_prep: non-Z Prep basis raises at the shared root so QIR/Stim/QC/QASM all reject it (mirrors the Guppy Z/+Z preflight; Guppy/HUGR route unchanged -- its SLR-preflight runs pre-conversion). test_qir_spec_compliance.py: pins + docstring counts (build-fail 3->6, QIS_OK n->23) + resolved-#77 narrative. tier2_semantic.py: 2 regression tests. Verified: focused 3/0, broad 258/0, ruff/black clean.
ciaranra added a commit that referenced this pull request May 22, 2026
…(PZ/PNZ/PX/PNX/PY/PNY) + converter _PREP_BASIS routing + recast stray-string guard (basis is the gate identity -- any string qarg on any prep gate fails loud, generalizes/supersedes #80 non-Z guard) + block-sub & pretty-print basis preserve + qb exports. Prep retained as PZ alias so the ~223 sites stay green (no churn until Stage C). #71 pins fragment -> 'stray string argument'. Verified: focused 4/0 (#71 gate green -- recast broke no QIS_OK program), broad ast_guppy 258/0, ast_tests 664/0, ruff/black clean
ciaranra added a commit that referenced this pull request May 22, 2026
…ctories _docs_prep_basis_x/_docs_surface_syndrome_block18->dedicated gates, GuppyCodegenError expected_failure deleted; #71 re-pinned from ACTUAL _qir_state (BUILD_FAILED 6->5/QIS_OK 23->24: prep_basis_x->QIS_OK; surface_syndrome_block18 stays BUILD_FAILED on its inline Measure(data)>CReg(final) #80 'was not declared at Main scope' reason NOT prep -- pinned honestly; counts+docstring prose); sim hard-rename Pn{X,Y,Z}->PN{X,Y,Z} 8 files + gate_type.rs comments + removed dead .typos.toml Pn (rslib rebuilt; SIM DISPATCH 13/13 + PZ/PNZ/PX state-correct); fixed 5 genuinely-#81 test-fallout (test_ast_hugr->test_hugr_rejects_stray_prep_basis_string asserting converter NotImplementedError stray-string; 4 converter scratch-lifecycle messages Prep->PZ, re-Prepped verb kept to match deterministic sed-renamed test spec). Verified FULL slr_tests: 391 passed, only #87(14 perm/meas legacy-QIR-comment) + #88(9 #74/#80 scalar-var/oversize fail-loud + SX-rx/parallel/array-unpack) failing -- BOTH proven pre-existing-rel-to-#81 (exception-identity + no-prep; pecos/regression+guppy never subset-verified), #81 net-new=0; #71 green; behavioral all-6 PASS; doc-tests 22/0; ruff/black/cargo-fmt clean
ciaranra added a commit that referenced this pull request May 22, 2026
… Guppy unpack xfail (88B)

#88A: qir._process_gate `if qir_name is None: return` was a silent
#74-class drop (18 gates absent from GATE_TO_QIR:
CH/CRX/CRY/CRZ/CY/F/F4/F4dg/Fdg/SX/SXX/SXXdg/SXdg/SY/SYY/SYYdg/SYdg/
SZZdg) -> valid QIR, wrong semantics, qir-qis-uncatchable. Now raises
NotImplementedError. The "broad reliance" #78 feared did NOT
materialise -- blast radius is tiny: test_sx_sxdg -> raises (SX/SXdg
never had a QIR lowering; legacy gen_qir didn't either) + an honest
#71 re-pin (qeclib.generic_check_xyz / generic_check_1flag_ch use the
"XYZ" Check whose Y branch emits CY (no QIR lowering); they were
dishonestly QIS_OK on the silent drop -> moved _EXPECTED_QIS_OK 24->22
into _EXPECTED_BUILD_FAILED "has no QIR lowering"; docstring "(7)" /
n=22, re-pinned from the actual _qir_state(), never guessed).

#88B: test_unique_unpacked_names investigated -> real Guppy-emitter
bug, NOT shallow: slot-local naming f"{allocator}_{index}"
(guppy.py _local_name + guppy_linearity binding init + entry-unpack
LHS -- 3 sites that must agree) collides when an unpack name (`q_0`)
equals another declared register's name; `q_0,q_1 = q` rebinds the
`q_0` param, then `q_0_0, = q_0` raises UnpackableError. A correct fix
needs a single namespace-wide uniquification authority feeding all 3
sites (linearity state has no register/block-decl names) with
corpus-wide local-name churn -- cross-cutting, out of #87/#88 pre-PR
scope. Per the investigate->fix-if-shallow-else-xfail rule:
@pytest.mark.xfail(strict=True) with full findings, tracked.

Verified: default lane 403/0; optional_dependency lane 42 passed /
3 xfailed / 0 failed; behavioral prep all-6 24/0; ruff/black-25.9.0
clean.
ciaranra added a commit that referenced this pull request May 22, 2026
For every audit-corpus program qir_to_qis accepts (the live #71
_qir_state() QIS_OK set, now 22), EXECUTE the lowered QIS
(qir_bc -> qir_qis.qir_to_qis -> selene_sim.build -> run_shots(Stim)
via the #77-proven _qis_exec_records) and assert the EXECUTED
classical records -- the end-to-end proof of the B2 M-B2-static
lowering + #74/#78/#87(Permute)/#88A fail-loud line at corpus scale.

The oracle (the hard part): a _MANIFEST classifies all 22 QIS_OK
programs D/P/X, each derived from first principles by reading the
circuit and CONFIRMED-not-reverse-fitted by execution. D (3) =
exact record; P (8) = a HARD invariant (correlation / fixed bits /
small exact value set) asserted over a fixed seed set, must hold
AND be exercised -- NEVER a statistical/tolerance compare; X (11) =
no classical record or no sound first-principles invariant,
documented per program.

Folds the #79 dual PLAN pre-review: blocker 3 -> n_qubits from the
QIR required_num_qubits entry attr (not max-operand-ref, which is 0
for no-op QReg programs and panics selene); blocker 1 -> a
record-shape contract (an X-with-record program that executes to NO
record fails = output-loss miscompile, not silent X). Blockers 1+2
root causes were resolved upstream by #80 (inline/Return-only CReg
fails loud) + #81 (correct PX); prep cases re-derived under the
correct #81 semantics -- docs.prep_basis_x is PX=|+>, one uniform
Z-measure bit -> X, NOT the pre-review's deterministic [0] (which
assumed the old broken Prep('X')=Z-reset). One first-principles
guess corrected BY execution (the design-mandated confirm step):
legacy.multiple_qregs is NOT c1==c2-correlated (all 4 combos
occur); sound invariant is bit-1==0 per CReg, correlation NOT
claimed. Drift guard: candidate set taken live from _qir_state()
QIS_OK; class histogram pinned D=3/P=8/X=11 so a reclassification
is deliberate.

Slow + optional_dependency lane. Verified: 23 passed (22
executable + drift guard); default lane 403/0 unaffected
(deselected); ruff/black-25.9.0 clean. Pending dual post-review
(reviews/dual-79-corpus-executable-postreview-prompt.md).
ciaranra added a commit that referenced this pull request May 22, 2026
… Permute guard

Per user direction: code comments/docstrings must not name
reviewers/agents/personas or leak the review-process workflow.
Branch-wide sweep of changed .py files -- replaced "Codex"/
"Pickle"/"pre-review blocker N"/"re-confirm note" etc. with the
durable issue ref (#80/#79/#71/#88A) + the technical reason. No
behavior change (comments/docstrings only). Also folds the #87
non-blocking symmetry note: test_non_bijective_permute_fails_loud
now also pins the duplicate-TARGET guard (was only duplicate-
source + distinct-non-bijective; the target guard was live but
unpinned).

Verified: default 403/0; optional_dependency 43 passed / 3
xfailed / 0 failed; slow #79 lane 23/23; ruff/black-25.9.0 clean;
branch-wide grep for persona/review-process terms in *.py is
empty.
ciaranra added a commit that referenced this pull request May 22, 2026
…Clifford)

The user-chosen verified-safe subset of the 18 #88A fail-loud gates.
Methodology (no guessing): extract each gate's authoritative unitary
from the PECOS StateVec simulator, search the EXECUTABLE Clifford
primitive set for a sequence equal up to a global phase, then verify
end-to-end through qir_to_qis -> selene.

Key correctness finding: decomposing to rx/ry/rz is WRONG --
`__quantum__qis__rx__body` is a pinned build/exec failure
(`docs.rotation_rx`) and selene's Stim backend silently no-ops an
rx, so a rotation lowering is itself a silent miscompile (the
behavioral check caught this -- SX;SX gave 0 not 1). Correct
executable-Clifford lowering (verified up-to-phase vs PECOS sim AND
end-to-end): SX=H;S;H, SXdg=H;Sdg;H, SY=H;X, SYdg=H;Z. `_GATE_DECOMP`
+ a pre-fail-loud branch in `_process_gate` (re-emits the primitive
sequence; F-family/CY/CH/CR*/sqrt-2q stay fail-loud pending the
scoped workstream). test_sx_sxdg flipped raises->positive.
test_sqrt_clifford_gates_executable (slow+optional_dependency) pins
the end-to-end identities SX;SX==X / SXdg;SX==I / SY;SY==Y /
SYdg;SY==I + Z-randomness of the single gates.

#71/#79 corpus buckets UNCHANGED (no curated case uses SX/SY; the
CY-bearing generic_check programs stay BUILD_FAILED). Verified:
default 403/0; optional_dependency 43 passed / 3 xfailed / 0 failed;
slow 24 passed (22 #79 + manifest-cover + sqrt_clifford);
ruff/black-25.9.0 clean.
ciaranra added a commit that referenced this pull request May 22, 2026
Same verification-first method as the SX/SY subset: extracted each
unitary from the PECOS StateVec sim, searched the EXECUTABLE
Clifford set for a sequence equal up to a global phase, verified
end-to-end through qir_to_qis -> selene. Results (circuit order):
F=SZdg;H, Fdg=H;SZ, F4=H;SZdg, F4dg=SZ;H.

_GATE_DECOMP entries (incl. the existing SX/SXdg) now use
GateKind.SZ/SZdg rather than the redundant S/Sdg aliases, per the
PECOS convention that S==SZ / Sdg==SZdg (both already map to
"s"/"s__adj" in every codegen -- zero functional change;
re-verified). test_face_clifford_gates_executable pins the
end-to-end identities: F;Fdg / Fdg;F / F4;F4dg / F4dg;F4 == I,
the F|0>=|+> non-no-op discriminator, and F;F;F == I (PECOS F is
the order-3 face Clifford).

#71/#79 corpus buckets unchanged. Verified: default 415/0,
optional_dependency 44 passed / 3 xfailed / 0 failed, slow
face+sqrt 2/2 (full slow unaffected), ruff/black-25.9.0 clean.
Remaining #93: 2q Clifford (CY/SXX/SYY/SXXdg/SYYdg/SZZdg) need a
2q decomposition path; CH (non-Clifford) + CRX/CRY/CRZ
(rotations, no executable primitive) stay fail-loud.
ciaranra added a commit that referenced this pull request May 22, 2026
… (7 2q Cliffords)

Per ~/Repos/qir-qis/src/lib.rs:59 (ALLOWED_QIS_FNS, 23 functions):
the qir-qis native 2q gate is rzz (parameterized) -- there is NO
zz/szz in the allowlist (the prior `GATE_TO_QIR[SZZ]="zz"` emitted
`__quantum__qis__zz__body` which qir-qis rejects with
"Unsupported QIR QIS function"). Quantinuum supports ZZ-flavor
entanglement natively, just as `rzz`. Fixed:

- Removed the misleading `GateKind.SZZ: "zz"` from `GATE_TO_QIR`
  (and `SZZ` from `TWO_QUBIT_GATES`) -- no more misleading
  codegen surface.
- Extended `_GATE_DECOMP` to a 2q-capable shape: each step is
  (prim_kind, qubit_idx_tuple, params_tuple); `_process_gate`
  emits each step with the input gate's targets routed and
  constant params threaded.
- Added 7 verified decompositions:
    SZZ   = RZZ(pi/2)(q0,q1)
    SZZdg = RZZ(-pi/2)(q0,q1)
    SXX   = (H@H); RZZ(pi/2); (H@H)
    SXXdg = (H@H); RZZ(-pi/2); (H@H)
    SYY   = (Sdg@Sdg);(H@H); RZZ(pi/2); (H@H);(S@S)
    SYYdg = (Sdg@Sdg);(H@H); RZZ(-pi/2); (H@H);(S@S)
    CY    = Sdg(t); CX(c,t); S(t)
  Each VERIFIED up-to-global-phase against the PECOS StateVec
  unitary AND end-to-end via qir_to_qis -> selene (inverse pairs,
  CY|10> -> i|11> non-vacuity, SZZ^2 = Z discriminator).
- `test_sqrt_pauli_2q_gates_executable` (slow+optional_dependency)
  pins the end-to-end identities.

Methodology correction recorded earlier in memory: my prior "rx is
a silent no-op on the Stim backend" claim was wrong (probe-harness
bug). RX/RY/RZ/RZZ all execute correctly. The truly misleading
surface was only SZZ -> "zz" (now fixed).

#71 honest re-pin: BUILD_FAILED 7 -> 5, QIS_OK 22 -> 24
(generic_check_xyz / generic_check_1flag_ch use CY which now
lowers, so they re-enter QIS_OK). #79 manifest updated: both
classified X (XYZ stabiliser on |0..0> is not an eigenstate ->
single uniformly random syndrome bit; no hard invariant);
histogram pin D=3/P=8/X=11 -> X=13.

Verified: default 415/0, optional_dependency 44 passed / 3
xfailed / 0 failed, slow 28/0 (24 QIS_OK + manifest guard +
sqrt/face/sqrt_pauli_2q behavioral), ruff/black-25.9.0 clean.

Remaining #93 (still fail-loud, no PECOS sim oracle): CH (sim
doesn't support), CRX/CRY/CRZ (parameterized, sim doesn't
support) -- decomposition would require a convention pin without
a verification oracle. Surfaced for separate scope.
ciaranra added a commit that referenced this pull request May 22, 2026
* Regenerate doc tests and prune stale generated files

* Auto-include CUDA Python packages when toolkit and NVIDIA GPU detected

* Surface CUDA Python packages in pecos setup summary

* Stop pytest from shelling out to cargo, fix shadowed slr_tests module collision

* Sanitize generated doc test paths and fix cmake-setup example

* Introduce VariableState; fix SLR linearity bugs at use-after-unpack sites

* Add AST -> Guppy v1 acceptance tests as xfail spec

* Tighten v1 acceptance tests: strict xfail; defer rejection tests

* Add Guppy-only linearity helper for AST -> Guppy lowering

* Rewrite AST Guppy emitter for v1 linearity

* Clean up legacy AST Guppy tests after v1 emitter rewrite

* Tighten _harness lint: TYPE_CHECKING import, Error suffix on exception

* Black formatting on AST -> Guppy v1 work

* Fix Guppy bool emission for CReg bit assignments

* Add Selene behavioral test harness for AST -> Guppy v1 (Workstream A)

* Workstream B audit runner skeleton (waiting on _force_ast kwarg)

* Add audit-only AST HUGR route

* Expand audit runner with legacy HUGR test corpus (9 cases all pass)

* Harden AST Guppy behavioral coverage

* Add examples/ corpus to audit runner (pass 2)

* Add qeclib corpus + ExpectedFailure XFAIL mechanism to audit runner (pass 3)

* Harden Guppy v1 preflight + inline-CReg inference; lock down pass 4 audit (4 green, 5 XFAIL)

* Cutover: route SlrConverter.hugr() through AST by default; remove _force_ast kwarg

* Cutover: delete legacy gen_codes/guppy/ and dependent tests

* Apply Codex review fixes: no-arg entrypoint wrap + compile diagnostics + stale comments + 3-cycle and cross-register Permute coverage

* Fix Codex review v2 findings: entry wrapper mirrors emitter return shape (explicit Return passthrough + inline CReg result capture); broaden compile diagnostic catch; promote wrapper to shared entry_wrapper module

* Mirror emitter for non-result-CReg Return values; cover nested-control-flow inline Measure in regression tests

* Widen compile-error diagnostics to module-load failures; factor out _load_and_compile_entry helper

* v2 Phase 1: add Print(value, *, tag=None, namespace='result') with SLR class, PrintOp AST node, Guppy result() emission, QASM comment fallback, and 15 Selene-roundtrip behavioral tests

* Phase 1 Print: apply Codex tracer-bullet review (validate derived tags, document Selene tag-mode flip, strengthen loop/multi-Print tests with event-list shape assertions, explicit Print-skip in QIR/Stim/QuantumCircuit, import result from guppylang.std.builtins)

* Phase 1 Print: path-signature validator rejects asymmetric If branches + non-static loops with Print bodies (9 new tests)

* Phase 1 Print: inline-CReg definite-assignment dataflow validator + 8 tests (Codex's Print-before-Measure soundness gap closed)

* Phase 1 Print: tighten inline-CReg validator to bit-level (Codex tracer-bullet finding Q4) + add missing coverage tests (While/Parallel/Repeat(0)/partial-CReg)

* Phase 1 Print: reject whole-CReg Print of inline CReg outright (Codex v2 review Q2 -- prevents silent register shrink); rewrite affected tests at bit-level; add Codex shrink-case regression + direct AST non-static For test

* Phase 1 Print: adversarial tests (cross-codegen byte-identity + Selene shape edge cases + QASM exactly-one-comment assertion)

* Phase 2 deprecation warnings: CReg(result=False) construction-time warning + Main(...) implicit-return warning fired SLR-wide across all codegen entry points (cached per converter, stacklevel attributes to user, walker tightened to skip false-positive no-result Measure and result=False-only targets)

* Land Phase 3a.0-3a.3 iters 1-4: BlockDecl/BlockCall AST + Guppy emitter + 7 Steane Block conversions.

* Phase 3a.3 iter 5a-prep: typed BlockArg sum type for BlockCall arg/out bindings (AllocatorArg + four shape stubs).

* Fix Codex review of BlockArg refactor: flatten path now rejects deferred BlockArg shapes in out_bindings (symmetric with arg_bindings); add 4 lock-in tests across Guppy + flatten paths.

* Phase 3a.3 iter 5b: single-qubit BlockInput type + SingleQubitArg call-site shape end-to-end in Guppy emitter.

* Fix Codex iter-5b review: reject mismatched arg/out bindings for LIVE_PRESERVED inputs (would emit invalid Guppy); add flatten-rejection lock-in for SingleQubitArg + clarify test docstring.

* Phase 3a.3 iter 5c: single-bit BlockInput via array[bool,1] write-back proxy + SingleBitArg call-site shape end-to-end in Guppy emitter.

* Phase 3a.3 iter 5d: QubitBundleArg non-contiguous slot-bundle call-site shape end-to-end in Guppy emitter (pack/unpack across arbitrary allocators, duplicate/bounds/size validation).

* Iter-5d Codex polish: end-to-end Selene lock-in for cross-allocator bundle; pre-consume cross-input qubit-slot alias check; refresh stale _emit_block_call docstring + mismatch message.

* Iter-5d r2 polish: add asymmetric bundle test that genuinely pins unpack order; reword Bell test to not over-claim order-sensitivity; fix stale validated_args tag comment.

* Phase 3a.3 iter 5e.1: unify BlockDecl-body substitution into shared BodyRemap + recursive substitute_stmt (slot/bit-level, reject-on-partial); converter + flatten both delegate to it; 24 synthetic non-identity tests. Byte-identical on the 7 Steane conversions.

* Iter-5e.1 Codex early-fixes: token-boundary permute reject (no substring false-positive); BodyRemap rejects conflicting whole/partial binds at build time; exact prepare-slot-order lock-in + reversed-map case + builder-conflict tests.

* Phase 3a.3 iter 5e.2: SLR var->typed BlockArg detection (single Qubit/Bit, list[Qubit] bundle) + flatten shape promotion + cross-input qubit/bit aliasing guard at both converter and BodyRemap layers; strip review-attribution from code comments.

* Scratch-ancilla S1: ResourceEffect.SCRATCH + converter detection (excluded from out_bindings, kept for O2 alias guard) + R4 Prep->Measure segment-lifecycle validator (every Prep closed by Measure) + 13 lock-in tests.

* Scratch-ancilla S1: reject any ReturnOp in a scratch-bearing block (R4 clause 4, Codex S1 r2 -- scratch slot must not be handed out); drop dead _expr_mentions_allocator; +return-rejected test.

* Scratch-ancilla S2: Guppy emitter lowers SCRATCH as an internal qubit() allocation (no param/arg/return, seeded CONSUMED); fix live_arg_index to index validated_args; reuse pattern now compiles + runs (original blocker dead).

* Scratch-ancilla S2 fixes (Codex r1): whole-program guard rejecting non-scratch use of a scratch-bound outer slot (Prep carve-out for the corpus, reuse still allowed); validate scratch BlockArgs before excluding so malformed bindings fail loudly; 3 lock-in tests.

* Scratch-ancilla S2 R5-completeness (Codex r2): per-scope purity guard (main + every BlockDecl.body) covering PermuteOp + ReturnOp observation paths; 3 lock-in tests.

* Scratch-ancilla S4: convert qeclib Check (a:scratch) -- SynExtractBare reused-ancilla path now compiles to Guppy (original 5e.3 blocker dead); Check Selene records byte-identical (qubits 4->5 for internal alloc, design R2); Steane production lock-in.

* Scratch-ancilla S4: add Color488 serial SynExtractBare production lock-in (Codex S4 non-blocking finding -- design specifies Steane + Color488).

* Scratch-ancilla S5: convert qeclib Check1Flag (a,flag:scratch; Prep(a,flag)->Prep(a),Prep(flag) per O1 option a) -- SynExtractFlagged reused-ancilla+flag path now compiles; Check1Flag Selene records byte-identical (qubits 5->7); SynExtractFlagged + CH-branch lock-ins.

* Scratch-ancilla S5: fix corpus tracked-block table doc drift (Check/Check1Flag ancilla now scratch, was consumed) -- Codex S5 non-blocking finding.

* v1 cutover cleanup: port surface_code_slr_exploration notebook off the deleted IRGuppyGenerator to SlrConverter.guppy(); drop stale deleted-test reference in audit_runner comment.

* Centralize AST visitor dispatch: replace 37 per-node accept() double-dispatch stubs with one BaseVisitor _DISPATCH map; drop vacuous ABC from base nodes; nodes.py now visitor-decoupled; +completeness safety-net test (byte-identity + public API unchanged).

* Visitor dispatch: walk type(node).__mro__ so subclasses of concrete AST nodes resolve to the base node's visit_* (preserves old inherited-accept() semantics, Codex review); scope completeness test to the nodes module + add MRO-subclass regression test.

* Fix two pre-existing qeclib construction bugs (audit XFAIL->accepted, 8->6 XFAIL): PrepProjectZ now preps its list[Qubit] via the Q.pz primitive (dead/broken (QReg,indices) PrepZ removed); Color488 SynExtractBareParallel uses int math.ceil for the range() count instead of the f64 numpy-drop-in pecos_rslib.num.ceil.

* Add Phase 3b S1 codemod (libcst): append explicit Return(<result CRegs>) to implicit-return Main() calls, trailing-comma-preserving so the formatter keeps multi-line shape; validated byte-identical on test_v1_behavioral. Not yet rolled out (harness-first + chunked-black-gated plan pending Codex review).

* Phase 3b S1a: generalize _selene_harness to accept explicit Return (derive measurement_N keys from returned CRegs OR -- pre-3b -- implicit result CRegs; strict on non-CReg returns); codemod now also matches qualified pecos.slr.Main + mirrors its qualifier for Return. No-op for current tree (1129 pass, audit 31/31).

* Phase 3b gate-hardening: implicit-return warning detector now also catches whole-register inline CRegs (Measure(q) > CReg('c',n) stores the CReg itself in cout, not a Bit) -- was under-detecting; suite green (1129), warnings non-fatal.

* Phase 3b S1b: harden implicit-return detector (recursive cout -- whole-register + slice inline CRegs); re-apply per-case audit warning enforcement; migrate implicit-return programs to explicit Return across ast_guppy/QASM-regression/legacy-guppy/unit-slr/test_partial (+scoped block.extend(Return) for qeclib QASM-byte-identity, +Stim/QC helper returns). Audit 31/31; strict gate 1131 pass; QASM byte-identical; ruff+black clean.

* Phase 3b S1c: fix logical_steane_code_program example -- stale pecos.qeclib import -> pecos.slr.qeclib, prog.qasm() -> SlrConverter(prog).qasm() (was doubly-broken/non-runnable), + explicit Return(m_bell,m_out)/(m_reject,m_t,m_out) so it is S3-safe. Now runs end-to-end; wrapped regression test (own copies) still green.

* Phase 3b S2: remove Phase-2 implicit-return warning machinery (slr_converter) + CReg(result=False) warning (vars.py, kwarg kept for S3); revert now-moot per-case audit enforcement to plain hugr() (option 5a); rewrite test_deprecation_warnings into post-S2 no-warning contract tests. gen_codes legacy deprecations untouched. Independently verified: audit 31/31, suite 1121 pass byte-identical, ruff/black clean.

* Phase 3b S4: port qir_bc() to AST path (binding.parse_assembly bitcode + split diagnostics), fix invalid // builder comment so qir() emits parseable LLVM IR, sever legacy gen_qir creg.result (S3 blocker; QIRGenerator symbol kept).

* Phase 3b S4 fix: wrap binding.parse_assembly().as_bitcode() in the diagnostics try so malformed IR raises the wrapped 'Failed to compile QIR to bitcode' error (Codex post-review blocker).

* Phase 3b S3: remove CReg(result=) kwarg + RegisterDecl.is_result field + the v1 implicit-return code path (D-S3-1 b: drop EntryWrapperInfo.result_cregs, explicit-Return-only); no-Return -> main()->None; rewrite test surface to hard contracts; Codex post-review fixes (ISC001 x2 + 4 stale-doc/dead-code).

* Phase 3b S5: M2 converter-time elision of flattened composite block-boundary Returns (provenance flag, not position/count); steane_pz compiles (audit accepted, compile-scope); behavioral lock-in is strict-xfail pinning the orthogonal pre-existing v1 FT-RUS pz() non-codeword defect (tracked post-3b, task #72); EncodingCircuit provenance guard.

* #72: fix _selene_harness measurement-mapping via opt-in named return tags (D-72-B). entry_wrapper gains keyword-only emit_return_result_tags (default False -> production wrapper byte-identical); harness emits result('__pecos_return.<creg>',..) and _shot_records reads tags by name, re-exporting the historical measurement_N shape (immune to internal RUS measurements). Remove the wrong S5 strict-xfail (steane pz now genuinely passes; stim-verified); add internal-measurement regression guard.

* #72 close-out: fail-loud on unexpected Selene result-tag shape in _shot_records (Pickle non-blocking) -- explicit list/int dispatch, TypeError on any other type instead of silent single-bit wrap; behaviour-preserving (1123 passed, audit unchanged).

* #71 Stage A: add qir-qis test-group dep + QIR spec-compliance baseline gate over the audit corpus. Honest non-faked baseline (0 compliant; uniform missing-output_labeling_schema cause; build-failure set pinned so a new qir_bc regression trips it); trips deliberately on Stage-B progress. Codex blocker folded + re-confirmed; Pickle signed off. Test-only, production byte-identical.

* #71 Stage B1: emit required QIR module metadata in _finalize_module (output_labeling_schema=labeled, qir_profiles=adaptive_profile, !llvm.module.flags qir_*_version + dynamic_*/arrays=false). validate_qir 0->27/28; honest two-tier gate (Tier-1 validate, Tier-1b exact entry-attr value pins, Tier-2 qir_to_qis still create_creg until B2; build-fails pinned). Dual post-review: Pickle YES; Codex blocker (presence-only value pin) folded+re-confirmed. QASM/Stim/QC byte-identical; audit/optional_dep unchanged.

* #71 B2a: declare standard QIR classical-model functions (__quantum__qis__mz__body, __quantum__rt__read_result) for B2b to wire. Decls-only, zero behavior/gate change; verified inert (validate/qir_to_qis split, gate, broad 1124, audit all unchanged). Helper methods deferred to B2b (no dead code).

* #75: extend pecos_rslib_llvm with LLVM memory ops to unblock #71 B2 -- LLIRBuilder::{alloca,load,store,zext,trunc,icmp_unsigned} + LLConstant::zero (Array->zeroinitializer, Pointer->null) in crates/pecos-llvm; LLType PartialEq/Eq + manual Hash consistent with inkwell's LLVMTypeRef Eq (inkwell 0.8.0 derives Hash only for IntType); 6 pyo3 #[pymethods] + Constant(ty,value=None)->zero + PyLLValue.type getter + structural __richcmp__/__hash__ on Py*Type (PyAnyType gains IntoPyObject so the getter is returnable). Additive only: end-to-end B2-shaped module via the new ops round-trips binding.parse_assembly + qir_qis.validate_qir + qir_to_qis; #71 gate 1 passed unchanged, slr_tests 368 passed, broad qir/qasm/codegen sweep 1785 passed 0 fail; cargo fmt/clippy clean.

* #75 post-review nit #1 (Pickle): type __richcmp__ ordering ops (Lt/Le/Gt/Ge) now return NotImplemented so Python raises TypeError instead of a silently-wrong False; Eq/Ne unchanged (lltype_richcmp -> Py<PyAny>). Verified i1<i64 raises, eq/hash/dedup intact, #75 e2e + #71 gate + slr_tests 368 + broad sweep 1785 all green; clippy/fmt clean.

* #71 B2 (B2b+B2c) on the #75-extended binding: replace bespoke CReg runtime helpers with the standard M-B2-static model. CReg -> entry-block alloca [N x i1] + zeroinitializer; measure -> __quantum__qis__mz__body + static %Result* + read_result + store (no-result mz; slot=measurement_count-1); BitRef/whole-CReg c.set(int) -> store/per-bit lshr+trunc unpack; BitExpr -> load+zext (i64-canonical); _as_i1/_as_i64 via #75 value.type; record pack load/zext/shl/or -> int_record_output (kept); point-of-use GEP. B2c removes the now-uncalled create_creg/get_creg_bit/set_creg_bit/get_int_from_creg/set_creg_to_int/mz_to_creg_bit decls. #71 structural gate honestly rebaselined: _EXPECTED_QIS_OK 7->27, qis_failed empty, BUILD_FAILED 3->2, qeclib.steane_pz triaged build->validate-fail (B2 makes it build; then hits qir-qis allowlist on PECOS's non-standard __quantum__qis__barrierN__body -- pre-existing barrier gap, out of B2 scope). New tier2_semantic.py: real qir-qis-compiler acceptance + emitted-QIR structural invariants + deterministic AST->Guppy->selene cross-anchor (set_int/zero_init A+B-only; Guppy can't return a set(int) CReg). Executable qir_to_qis->selene differential blocked by LLVM 14<->21 / opaque-vs-typed gap -> tracked separately. slr_tests 368, broad sweep 1785/0-fail, ruff/format clean.

* #71 B2 post-review fold: fix Codex blocker + 2 non-blocking. BLOCKER: >64-bit CReg silently miscompiled (creg_map recorded size but alloca/output only when <=64, so CReg(65) built+validated+qir_to_qis-lowered with storage/output silently dropped) -> _process_declarations now raises NotImplementedError for size>64 (single-i64 pack cap; fail-loud, not a silent fallback) + regression test test_oversize_creg_raises_loud (CReg(64) builds, CReg(65) raises). Non-blocking: register `slow` marker in tests/conftest.py::pytest_configure (invocation-independent; canonical sweep 0 warnings; residual single-file pytest-path warning is a conftest-discovery quirk, cosmetic); tighten tier2_semantic Layer B from read_result-subset-of-mz to per-measurement adjacency (_assert_mz_rr_pairing: each read_result immediately preceded by its own same-slot mz), drop dead _MZ_SLOT/_RR_SLOT. Gates: tier2 PASS (8 A/B + tightened pairing + 2 C), oversize regression passes, #71 gate green, slr_tests 368, broad sweep 1785/0-fail, ruff/format clean. Pickle YES; Codex re-confirm pending (blocker fix + regression test were its exact sign-off condition).

* #71 B2 Codex re-confirm fold (cosmetic): raise the >64-bit CReg NotImplementedError BEFORE writing context.creg_map (fail before any partial state). Behaviour-equivalent (size>64 raises regardless; size<=64 unchanged); Codex minor note from codex-71-stageB2-postreview-reconfirm.md. Codex re-confirm: YES (0 blockers); both reviewers now YES -> B2 dual-signed-off.

* #74: fail loud on the 3 silent AST->QIR miscompiles (B3). VarExpr (was ->constant 0), While (was silent one-pass dropping condition+iteration), Print (was silent drop losing observable output) now raise NotImplementedError with clear messages -- each was valid-QIR-but-wrong-semantics that qir-qis cannot catch. Bounded scope: real While/scalar-SSA/Print->record are explicitly deferred (v1-feature-matrix: real While "too large for first sound emitter"); min bar = fail-fast not silently wrong (same pattern as B2 >64 cap). qir_bc() does not wrap them (only parse_assembly RuntimeError caught) -> surface at QIR-gen. Deliberate #71 gate rebaseline (triaged like steane_pz): only docs.while_loop exercises a B3 site -> _EXPECTED_QIS_OK 27->26, _EXPECTED_BUILD_FAILED 2->3 (NotImplementedError, "does not support While loops"); aligns QIR path with the Guppy path which already rejects While; 0 corpus Print, VarExpr only via for_loopvar_symbolic (build-fails earlier). Fixed enshrined-bug test: test_print.py test_qir_byte_identical asserted the old silent drop -> test_qir_raises_loud_on_print (Stim/QC byte-identical siblings left, documented out-of-scope). 4 fast regression tests in tier2_semantic.py. Gates: 5 fast + #71 gate green, cross-codegen Print 4 passed, slr_tests 368, ruff/format clean; broad sweep in-flight.

* #74 dual-review fold (Codex blocker): fix the 4th silent AST->QIR miscompile -- static `For` body dropped. `_process_for`'s `isinstance(node.start, int)` guard was always false (converter wraps For range bounds in LiteralExpr), so every static-For body was silently dropped: valid QIR, wrong semantics, qir-qis-uncatchable -- and docs.for_static_indexing sat in _EXPECTED_QIS_OK on body-dropped QIR (gate dishonest). Proper fix (static fixed-bound For is v1-supported + small, unlike deferred While/SSA): _static_int_bound resolves LiteralExpr(int) start/stop/step; _process_for unrolls range(start,stop,step) exclusive (matches canonical gen_quantum_circuit); symbolic/non-static bound -> NotImplementedError (never silent-drop). _process_repeat left as-is (RepeatStmt.count SLR-enforced int -- never the same bug; bounded fix to _process_for only). Honest resolution, NO baseline shuffle: docs.for_static_indexing stays in _EXPECTED_QIS_OK (26) but its QIR is now correct (body unrolled) -- gate honest by fixing code not the pin. New regression test_static_for_unrolls_body_not_dropped (3 call sites, not the declare; unrolled QIR still qir-qis-lowered). Gates: 6 fast tests + #71 gate green, broad sweep 1785/0-fail, ruff/format clean. Pickle YES; Codex re-confirm pending (its exact sign-off condition).

* #74 Codex re-confirm fold: satisfy the project formatter gate (the only re-confirm blocker -- semantically the static-For fix was already verified) + non-blocking step==0. Canonical chain is ruff-check --fix -> black 25.9.0 -> blackdoc (NO ruff format; ruff-format was a spurious self-introduced conflict). Collapsed two black-joined split regexes into single literals (behaviour-identical: r"a" r"b" == r"ab") to kill the black<->ISC001 conflict at the source; converged ruff/black (COM812 auto-fixed, black-stable). ruff check + black --check now clean, matching pre-commit. Non-blocking #1 (Codex): For(step=0) now raises a clear QIR NotImplementedError instead of a bare Python ValueError. Behaviour-preserving vs 177ab70c (formatting no-op; regex equiv; step==0 only changes an infinite-loop error message no test exercises). Pickle YES; Codex re-confirm pending (formatting was its sole remaining condition).

* #73 pre-PR hygiene: resolve branch-wide ruff/black debt -> repo fully green. 6 files (4 branch-touched + 2 pre-existing examples/surface, user-chose whole-repo-green). No logic change. migrate_implicit_return.py (libcst CSTTransformer): N802 x2 -> scoped `# noqa: N802` with rationale (libcst dispatches by the exact `leave_<CSTNodeClassName>` name -- API contract, renaming breaks dispatch); ARG002 x2 -> rename unused `original` -> `_original` (libcst calls positionally; no suppression needed). COM812 x7 -> ruff --fix (black-stable). 2x ISC001 from black-joined split strings (_block_flatten error msg, test_ast_visitor assert msg) -> collapsed to single literals at source (behaviour-identical). All 6 black-formatted (line-length 120). Canonical chain ruff-check --fix -> black 25.9.0. Verified: repo-wide `ruff check` All passed + `black --check` clean (760 unchanged); broad sweep 1785 passed / 15 skipped / 0 fail (behaviour-preserving: formatting / equivalent-string / unused-param-rename / comment-only).

* #78: fix the silent AST->QIR-sibling miscompiles in the Stim + QuantumCircuit AST codegens (same class as #74, applied per the explicit-over-implicit / fail-fast rule). _process_for: the isinstance(node.start, int) guard was always false (converter wraps For range bounds in LiteralExpr) -> Stim silently dropped every For body; QuantumCircuit's else *rejected every* static For (even valid For(i,0,3)). Now both resolve LiteralExpr(int) start/stop/step via _static_int_bound and unroll range(start,stop,step) exclusive (matches canonical gen_quantum_circuit); symbolic/non-static bound or step==0 -> clear NotImplementedError, never silent-drop. stim _process_while: was silent one-pass + stray TICK -> NotImplementedError (Stim has no runtime loop; same decision as #74's While; QC While already loud). Print -> fail loud in both (was a deliberately-pinned silent skip): Stim "does not support Print" (fundamental -- no classical-output stream); QuantumCircuit "does not yet support Print" (PECOS owns this format, may be added later). _process_repeat untouched (RepeatStmt.count SLR-enforced int -- never the bug; bounded to _process_for). Flipped test_print.py::TestCrossCodegenPrintEmission test_{stim,quantum_circuit}_byte_identical -> _raises_loud_on_print + docstring (same enshrined-bug-test correction #74 did for test_qir_byte_identical). New regression: TestStim/QuantumCircuitStaticForAndWhile (For unrolls 3x not dropped; While raises). Out of scope (surfaced, not changed): Stim silently skips unsupported gates / has no classical-Assign model -- long-standing, fundamental, broad reliance. Gates: ruff+black canonical clean; slr_tests 372/0-fail; broad 1923/0-fail.

* #77 DONE: real executable QIR->QIS->selene Tier-2 differential (A+B+C -> A+B+C+D). selene_sim (already a PECOS dep) natively executes qir_qis.qir_to_qis's LLVM-21 opaque-pointer QIS via the bundled selene_helios_qis_plugin (+ Helios QIR runtime) -- the long-claimed "blocked on an LLVM 14<->21 bridge" / "bounded post-PR infra (LLVM-21 JIT + runtime shim)" were BOTH wrong; no PECOS LLVM bump, PECOS-Rust stays LLVM-14. tier2_semantic.py Layer D: _qis_exec_records (qir_bc -> qir_to_qis -> selene_sim.build(BitcodeString) -> run_shots(Stim)) + test_tier2_executable_differential. Deterministic representatives execute to EXACT known classical records (empirically pinned; B2 _generate_results records ALL declared CRegs, declaration order, packed LSB-first): set_int [11], zero_init_safety [0,0], multi_creg [2,1], conditional_correction [0]. bell preserves entanglement correlation (every shot record in {0b00,0b11}, verified across seeds 1/2/7/42/123 -- only 0/3, never 1/2; H/CX present). Clean cross-path differential vs the dual-reviewed AST->Guppy->Selene oracle for conditional_correction (set_int/multi_creg excluded -- Guppy wrapper can't return a set(int) CReg, the documented Layer-C limit; zero_init declared!=returned). Slow-marked; slr_tests 372/0-fail; ruff/black canonical clean. Pending #77 dual review.

* #77 dual-review fold (Codex blocker + 2 non-blocking; Pickle YES). BLOCKER: the Bell executable check `all shots in {0,3}` was necessary-not-sufficient -- a dropped-H/dropped-CX/no-op Bell yields all-0 (subset of {0,3}) and would have passed, so the test could not catch the miscompile it guards. Fix: aggregate over fixed seeds (1,2,7,42) and require observed set == {0b00,0b11} (BOTH 00 and 11 must occur -> entanglement actually executed; 1/2 still rejected -> correlation) + single-record well-formedness. Non-blocking: (a) flipped the now-self-contradictory module docstring ("qir_to_qis->Selene blocked by LLVM 14<->21") to the truth -- Layer D selene_helios_qis_plugin runs LLVM-21 QIS, no PECOS LLVM work; framed as a *representative* (not exhaustive) differential; (b) registered the `slow` marker in slr_tests/pytest.ini (was commented under --strict-markers -> PytestUnknownMarkWarning; now clean). Verified: test 1 passed (4 deterministic exact + bell both-00/11-over-4-seeds + cond_corr cross-path), no marker warning, slr_tests 372/0-fail, ruff/black canonical clean. Pickle already YES; Codex re-confirm pending (blocker fix + docstring were its exact sign-off conditions).

* #80: fail loud on the 2 QIR silent-miscompile bugs #79 pre-review surfaced (inline/Return-only CReg lost its record; non-Z Prep basis dropped), re-pin #71 gate from actual _qir_state (3 programs QIS_OK->BUILD_FAILED)

qir.py: _require_creg() helper raises NotImplementedError at the 4 unknown-CReg silent-skip sites (_process_measure, _process_assign x2, _eval_expression BitExpr -- the last a #74-class silent Constant(0)). converter.py::_convert_prep: non-Z Prep basis raises at the shared root so QIR/Stim/QC/QASM all reject it (mirrors the Guppy Z/+Z preflight; Guppy/HUGR route unchanged -- its SLR-preflight runs pre-conversion). test_qir_spec_compliance.py: pins + docstring counts (build-fail 3->6, QIS_OK n->23) + resolved-#77 narrative. tier2_semantic.py: 2 regression tests. Verified: focused 3/0, broad 258/0, ruff/black clean.

* #80 dual-review fold: fix Codex blocker (Return-only inline CReg silently emitted no record -- _process_return now validates returned classical regs, qubit returns not false-rejected) + make the _eval_expression unknown-type default fail loud (same #74/#80 class, Codex non-blocking) + regression test; fix Pickle's surfaced doc-test by rewriting the 3 slr-qeclib.md Prep(q,"X") sites to the v1 Prep;H idiom (generated/ is gitignored). Verified: focused 5/0, doc-tests 22/0, ast_guppy 258/0, ruff/black/blackdoc clean

* #80 Codex re-confirm-1 fold: fix the Return-only inline-CReg name-collision bypass at the root (provenance, not name-guessing). _process_return skipped qubit returns by qubit_map name-membership, but _convert_return had erased QReg/CReg provenance (var.sym flatten), so an inline CReg colliding with a declared QReg name was silently dropped (same silent-output-loss class, public-SLR-reachable). Fix: additive ReturnOp.value_kinds populated from the real QReg/CReg object in _convert_return, preserved through _block_substitution, consumed by qir._process_return; unsound qubit_map name-skip removed. Guppy unaffected (reads .values). Collision regression test added. Verified: focused 5/0 (incl NAME_COLLISION raises, qreg_return builds), broad 258/0, doc-tests 22/0, ruff/black clean

* #81 Stage A: PrepareOp.basis discriminant + 6 dedicated prep classes (PZ/PNZ/PX/PNX/PY/PNY) + converter _PREP_BASIS routing + recast stray-string guard (basis is the gate identity -- any string qarg on any prep gate fails loud, generalizes/supersedes #80 non-Z guard) + block-sub & pretty-print basis preserve + qb exports. Prep retained as PZ alias so the ~223 sites stay green (no churn until Stage C). #71 pins fragment -> 'stray string argument'. Verified: focused 4/0 (#71 gate green -- recast broke no QIS_OK program), broad ast_guppy 258/0, ast_tests 664/0, ruff/black clean

* #81 Stage B: single shared canonical basis->tail map (_prep_tail.py) wired into all 5 codegens (QIR/Stim/QASM/QC/Guppy) -- reset + Clifford tail per pinned 6-row table (PZ id/PNZ X/PX H/PNX H;Z/PY H;S/PNY H;Sdg); non-PZ prepare_all fails loud where the backend no-ops it; PZ byte-identical (zero corpus churn); QC sequences reset/tail as separate ticks; Guppy functional tail. Verified: Stim peek_bloch all-6 PASS, broad ast_guppy 258/0, ast_tests 664/0, ruff/black clean

* #81 Stage C: hard-replace Prep->PZ repo-wide (63 files, all 3 subsystems: SLR gate+call sites + legacy gen_codes op_class + circuits gate-vocab); removed class Prep + _PREP_BASIS Prep entry + qeclib __init__ Prep + dead Guppy non-Z preflight reject + dead _string_args; PhysicalQubit.pz->preps.PZ; converter scratch-lifecycle messages Prep->PZ. Public SLR API break (accepted). Verified: full slr_tests 387 passed + behavioral all-6 PASS + ruff/black clean. The 27 *_qir permutation/measurement failures are PRE-EXISTING (legacy gen_codes QIR comment assertions the AST codegen never emitted; git -S Permutation empty on ast qir.py; #81 introduced 0 new failures) -- tracked task #87, out of #81 scope

* #81 Stage D: docs->dedicated PZ/PX + doc-tests regen (22/0); audit factories _docs_prep_basis_x/_docs_surface_syndrome_block18->dedicated gates, GuppyCodegenError expected_failure deleted; #71 re-pinned from ACTUAL _qir_state (BUILD_FAILED 6->5/QIS_OK 23->24: prep_basis_x->QIS_OK; surface_syndrome_block18 stays BUILD_FAILED on its inline Measure(data)>CReg(final) #80 'was not declared at Main scope' reason NOT prep -- pinned honestly; counts+docstring prose); sim hard-rename Pn{X,Y,Z}->PN{X,Y,Z} 8 files + gate_type.rs comments + removed dead .typos.toml Pn (rslib rebuilt; SIM DISPATCH 13/13 + PZ/PNZ/PX state-correct); fixed 5 genuinely-#81 test-fallout (test_ast_hugr->test_hugr_rejects_stray_prep_basis_string asserting converter NotImplementedError stray-string; 4 converter scratch-lifecycle messages Prep->PZ, re-Prepped verb kept to match deterministic sed-renamed test spec). Verified FULL slr_tests: 391 passed, only #87(14 perm/meas legacy-QIR-comment) + #88(9 #74/#80 scalar-var/oversize fail-loud + SX-rx/parallel/array-unpack) failing -- BOTH proven pre-existing-rel-to-#81 (exception-identity + no-prep; pecos/regression+guppy never subset-verified), #81 net-new=0; #71 green; behavioral all-6 PASS; doc-tests 22/0; ruff/black/cargo-fmt clean

* #81 Stage E: behavioral suite test_prep_gates.py -- 6 dedicated prep gates Stim peek_bloch (+-Z/+-X/+-Y) + 6 AST->Guppy->Selene prep-then-rotate(SZdg/H)-then-measure deterministic + emitted-tail QIR/QASM spot-check per _prep_tail + B1 Codex soundness regression (PX in BlockDecl via BlockCall keeps X-basis through flatten_block_calls in QASM&Guppy + |+> via Selene) + sim PN/alias native-basis behavioral (MZ/MX/MY). Verified: fast 410 passed (only #87/#88 pre-existing, 0 outside, #81 net-new=0) + slow prep_gates 7/7 PASS + ruff/black clean

* #81 post-review fold (round-1): Codex blocker -- direct canonical PNX/PNY keys added to statevec/bindings.py (was asymmetric vs PZ/PX/PY/PNZ; Rust dispatchers had all 6) + test_sim_pn_entry_points now 12 cases exercising all 6 direct (incl PNX-MX-1/PNY-MY-1) + 6 aliases. Pickle-surfaced 6th #81-fallout: git mv regression gold preps.Prep.qasm->preps.PZ.qasm (test_Prep builds qubit.PZ; reset-only content unchanged; tests/pecos/regression 105/0, no other gold fallout). 4 non-blocking folded: autosummary rst Prep->6 gates; emitted-tail asserts reset all 6; stray-string regression covers all 6 prep classes (was PZ-dup+PX); stale Prep prose in converter + pz() docstring. Verified: sim 6-direct+aliases 12/12 state-correct; test_Prep PASS; tests/pecos/regression 105/0; full slr_tests only #87/#88 pre-existing (#81 net-new=0); slow prep_gates 7/7; behavioral all-6 PASS; ruff/black clean

* #87/#88 partial: AST-QIR optional_dependency-lane fixes (honest, pre-existing gen_codes->AST-cutover gaps, not #81)

#87: qir._process_permute now emits the legacy-format `; Permutation:`
comment (whole-reg `a <-> b` / per-element `a[0] -> b[1], ...`),
PermuteOp carries whole_register, _block_substitution preserves it.
Element-granularity was already correct via sources/targets. Net:
test_mixed_permutation_qir + test_multiple_permutations_qir now pass.
test_permutation_with_bell_circuit_qir: measurement regex was stale vs
the committed #76-B2 M-B2-static model (it removed bespoke
@mz_to_creg_bit + legacy 1-arg %Result*-returning mz__body; mz now
lowers to standard 2-arg `call void @__quantum__qis__mz__body(%Qubit*,
%Result*)` + read_result + store) -- updated the regex to the current
correct form (QIR is correct; this is the same stale-format
test-update class as #88, user-authorized).

#88: qir.py PARAMETERIZED 1q+2q gate params resolve LiteralExpr before
float() (was a hard TypeError for parallel/literal params); 6
test_slr_phys tests updated to the #74/#76/#80 fail-loud reality
(>64-bit CReg -> NotImplementedError 'has 75 bits'; whole-CReg scalar
arith/conditions -> NotImplementedError 'classical variable').

NOT fixed (surfaced to user, not auto-guessed): 7 #87 tests hard-assert
#76-B2-REMOVED bespoke APIs (@set_creg_bit/@get_creg_bit/@create_creg/
@set_creg_to_int/creg-xor) -- un-passable without reverting the
dual-reviewed M-B2-static model; test_comprehensive_qir_verification
is a real element-wise-Permute silent miscompile (comment emitted but
qubit remap is a no-op; allocator_offsets keyed by reg name, AST never
ported legacy per-element permutation_map); 2 #88 deferred-class
(test_sx_sxdg = #78-deferred unsupported-gate silent-drop;
test_unique_unpacked_names = real Guppy size-1-unpack bug).

Verified: default lane 403/0 (no regression); behavioral prep all-6
24/0; optional_dependency lane 12->8 failed (3 #87 + 5 #88-side fixed);
ruff/black-25.9.0 clean.

* #87 resolved: real Permute realization in AST QIR (static logical relabel)

Root cause (disproven the 'just a missing comment' premise via the
actual emitted QIR + instrumentation): the AST QIR codegen never
realized Permute at all. The old allocator_offsets swap was dead code
-- offsets are {a:0,b:0} (physical base comes from a parent allocator
the swap never touched), and gate lowering never consulted it. Both
element-wise AND whole-register Permute were silent #74-class
no-op miscompiles (a `; Permutation` comment with gates still on the
original qubits).

Fix (mirrors the WORKING Guppy reference -- _emit_permute via the
linearity tracker's `.permute()` atomic relabel; QIR and the Selene
runtime have no permute intrinsic, confirmed by inspection, so a
compile-time relabel is the only mechanism, same as legacy gen_qir's
permutation_map): QirCodeGenContext.permutation_map maps a logical
(reg,index) -> the (reg,index) whose storage it resolves to.
_process_permute expands whole-register/element refs (qreg_sizes +
creg_map), requires the mapping bijective over the same ref set, emits
the legacy-format `; Permutation:` comment, then composes ATOMICALLY
(snapshot old, map[s]=old.get(t,t)) so a whole-register
sources=(a,b)/targets=(b,a) applies once instead of cancelling. Two
single-point chokepoints consult it: get_qubit_index (every qubit
ref) and _creg_bit_ptr (every classical-bit ref) -- decl-time
pre-population sees the empty map so real qubits still allocate 1:1.
Works uniformly for whole-register + element-wise, QReg + CReg.

All 14 permutation/measurement *_qir tests rewritten as POSITIVE
realized-behavior tests, expected QIR pinned from the actual emitted
output (qubit indices deterministic in declaration order; the
#76-B2-removed bespoke @set_creg_bit/@mz_to_creg_bit/@create_creg
helpers replaced by alloca-buffer stores + the standard 2-arg
@__quantum__qis__mz__body(%Qubit*,%Result*)) -- not weakened, not
xfailed. Added test_whole_register_qreg_permutation_realized_qir
guarding the realizable path.

Verified: optional_dependency lane 41 passed (only the 2 #88-deferred
fail: test_sx_sxdg, test_unique_unpacked_names); default lane 403/0
(no regression); behavioral prep all-6 24/0; ruff/black-25.9.0 clean.

* #88: broad unsupported-gate fail-loud (88A, the class #78 deferred) + Guppy unpack xfail (88B)

#88A: qir._process_gate `if qir_name is None: return` was a silent
#74-class drop (18 gates absent from GATE_TO_QIR:
CH/CRX/CRY/CRZ/CY/F/F4/F4dg/Fdg/SX/SXX/SXXdg/SXdg/SY/SYY/SYYdg/SYdg/
SZZdg) -> valid QIR, wrong semantics, qir-qis-uncatchable. Now raises
NotImplementedError. The "broad reliance" #78 feared did NOT
materialise -- blast radius is tiny: test_sx_sxdg -> raises (SX/SXdg
never had a QIR lowering; legacy gen_qir didn't either) + an honest
#71 re-pin (qeclib.generic_check_xyz / generic_check_1flag_ch use the
"XYZ" Check whose Y branch emits CY (no QIR lowering); they were
dishonestly QIS_OK on the silent drop -> moved _EXPECTED_QIS_OK 24->22
into _EXPECTED_BUILD_FAILED "has no QIR lowering"; docstring "(7)" /
n=22, re-pinned from the actual _qir_state(), never guessed).

#88B: test_unique_unpacked_names investigated -> real Guppy-emitter
bug, NOT shallow: slot-local naming f"{allocator}_{index}"
(guppy.py _local_name + guppy_linearity binding init + entry-unpack
LHS -- 3 sites that must agree) collides when an unpack name (`q_0`)
equals another declared register's name; `q_0,q_1 = q` rebinds the
`q_0` param, then `q_0_0, = q_0` raises UnpackableError. A correct fix
needs a single namespace-wide uniquification authority feeding all 3
sites (linearity state has no register/block-decl names) with
corpus-wide local-name churn -- cross-cutting, out of #87/#88 pre-PR
scope. Per the investigate->fix-if-shallow-else-xfail rule:
@pytest.mark.xfail(strict=True) with full findings, tracked.

Verified: default lane 403/0; optional_dependency lane 42 passed /
3 xfailed / 0 failed; behavioral prep all-6 24/0; ruff/black-25.9.0
clean.

* #79: corpus-wide executable QIR validation (generalises #77 Layer D)

For every audit-corpus program qir_to_qis accepts (the live #71
_qir_state() QIS_OK set, now 22), EXECUTE the lowered QIS
(qir_bc -> qir_qis.qir_to_qis -> selene_sim.build -> run_shots(Stim)
via the #77-proven _qis_exec_records) and assert the EXECUTED
classical records -- the end-to-end proof of the B2 M-B2-static
lowering + #74/#78/#87(Permute)/#88A fail-loud line at corpus scale.

The oracle (the hard part): a _MANIFEST classifies all 22 QIS_OK
programs D/P/X, each derived from first principles by reading the
circuit and CONFIRMED-not-reverse-fitted by execution. D (3) =
exact record; P (8) = a HARD invariant (correlation / fixed bits /
small exact value set) asserted over a fixed seed set, must hold
AND be exercised -- NEVER a statistical/tolerance compare; X (11) =
no classical record or no sound first-principles invariant,
documented per program.

Folds the #79 dual PLAN pre-review: blocker 3 -> n_qubits from the
QIR required_num_qubits entry attr (not max-operand-ref, which is 0
for no-op QReg programs and panics selene); blocker 1 -> a
record-shape contract (an X-with-record program that executes to NO
record fails = output-loss miscompile, not silent X). Blockers 1+2
root causes were resolved upstream by #80 (inline/Return-only CReg
fails loud) + #81 (correct PX); prep cases re-derived under the
correct #81 semantics -- docs.prep_basis_x is PX=|+>, one uniform
Z-measure bit -> X, NOT the pre-review's deterministic [0] (which
assumed the old broken Prep('X')=Z-reset). One first-principles
guess corrected BY execution (the design-mandated confirm step):
legacy.multiple_qregs is NOT c1==c2-correlated (all 4 combos
occur); sound invariant is bit-1==0 per CReg, correlation NOT
claimed. Drift guard: candidate set taken live from _qir_state()
QIS_OK; class histogram pinned D=3/P=8/X=11 so a reclassification
is deliberate.

Slow + optional_dependency lane. Verified: 23 passed (22
executable + drift guard); default lane 403/0 unaffected
(deselected); ruff/black-25.9.0 clean. Pending dual post-review
(reviews/dual-79-corpus-executable-postreview-prompt.md).

* #87 post-review fold (Codex blocker): validate expanded Permute refs BEFORE dict construction

Codex post-review NO -- 1 blocker: _process_permute built `mapping`
via `dict(...).update(zip(src_refs,tgt_refs))` and only THEN checked
`set(mapping) != set(mapping.values())`. A duplicate expanded source
collapses in the dict before the check, so a genuinely non-bijective
public Permute compiled instead of failing loud (silent miscompile,
the exact class this branch fights): `Permute([a[0],a[0]],[b[0],a[0]])`
COMPILED.

Fix (Codex's prescription): accumulate the expanded source/target
refs as LISTS, then validate BEFORE building the dict -- reject (a)
duplicate expanded sources, (b) duplicate expanded targets, (c)
src set != tgt set -- then build the map atomically. Regression
test_non_bijective_permute_fails_loud covers both Codex repro
shapes (distinct non-bijective -> "bijective over the same ref
set"; duplicate-source -> "duplicate source ref"). Pickle already
YES; this was Codex's sole blocker.

Verified: both repros now raise NotImplementedented loud; default
lane 403/0; optional_dependency 43 passed / 3 xfailed / 0 failed
(+1 new regression); behavioral prep all-6 24/0; ruff/black-25.9.0
clean.

* Strip reviewer/persona names from code comments; pin duplicate-target Permute guard

Per user direction: code comments/docstrings must not name
reviewers/agents/personas or leak the review-process workflow.
Branch-wide sweep of changed .py files -- replaced "Codex"/
"Pickle"/"pre-review blocker N"/"re-confirm note" etc. with the
durable issue ref (#80/#79/#71/#88A) + the technical reason. No
behavior change (comments/docstrings only). Also folds the #87
non-blocking symmetry note: test_non_bijective_permute_fails_loud
now also pins the duplicate-TARGET guard (was only duplicate-
source + distinct-non-bijective; the target guard was live but
unpinned).

Verified: default 403/0; optional_dependency 43 passed / 3
xfailed / 0 failed; slow #79 lane 23/23; ruff/black-25.9.0 clean;
branch-wide grep for persona/review-process terms in *.py is
empty.

* #93 (partial): verified QIR lowering for SX/SXdg/SY/SYdg (executable-Clifford)

The user-chosen verified-safe subset of the 18 #88A fail-loud gates.
Methodology (no guessing): extract each gate's authoritative unitary
from the PECOS StateVec simulator, search the EXECUTABLE Clifford
primitive set for a sequence equal up to a global phase, then verify
end-to-end through qir_to_qis -> selene.

Key correctness finding: decomposing to rx/ry/rz is WRONG --
`__quantum__qis__rx__body` is a pinned build/exec failure
(`docs.rotation_rx`) and selene's Stim backend silently no-ops an
rx, so a rotation lowering is itself a silent miscompile (the
behavioral check caught this -- SX;SX gave 0 not 1). Correct
executable-Clifford lowering (verified up-to-phase vs PECOS sim AND
end-to-end): SX=H;S;H, SXdg=H;Sdg;H, SY=H;X, SYdg=H;Z. `_GATE_DECOMP`
+ a pre-fail-loud branch in `_process_gate` (re-emits the primitive
sequence; F-family/CY/CH/CR*/sqrt-2q stay fail-loud pending the
scoped workstream). test_sx_sxdg flipped raises->positive.
test_sqrt_clifford_gates_executable (slow+optional_dependency) pins
the end-to-end identities SX;SX==X / SXdg;SX==I / SY;SY==Y /
SYdg;SY==I + Z-randomness of the single gates.

#71/#79 corpus buckets UNCHANGED (no curated case uses SX/SY; the
CY-bearing generic_check programs stay BUILD_FAILED). Verified:
default 403/0; optional_dependency 43 passed / 3 xfailed / 0 failed;
slow 24 passed (22 #79 + manifest-cover + sqrt_clifford);
ruff/black-25.9.0 clean.

* Fix dead test module: rename tier2_semantic.py -> test_tier2_semantic.py so pytest collects it

Integrity finding: tier2_semantic.py did not match pytest's
test_*.py pattern and there is no python_files override, so it was
only ever imported as a helper -- its tests NEVER ran in any lane.
That silently killed #77's test_tier2_executable_differential
(the QIR->qir_to_qis->selene executable differential) AND a whole
module of #74/#80/#81 fail-loud regression guards
(test_varexpr/while/print/oversize/inline-creg/prep-stray-string
_raises_loud). A "verified" gate that never runs is faked-green.

Proper fix (no python_files hack): git mv to test_tier2_semantic.py
+ update the one real importer (test_qir_corpus_executable.py)
and the stale docstring prose in test_qir_spec_compliance.py.
The revived tests were NOT bit-rotted -- they pass once actually
collected.

Verified: default lane 403 -> 413 (+10 revived fast guards, all
green); optional_dependency 43 passed / 3 xfailed / 0 failed; slow
33 passed / 0 failed (incl. the revived test_tier2_semantic_b2 +
test_tier2_executable_differential + #93 sqrt_clifford);
ruff/black-25.9.0 clean.

* A2: fix the Permute silent-miscompile in the Stim + QuantumCircuit codegens (same class as #87 QIR)

Both AST codegens had the identical pre-#87 broken pattern: a
`allocator_offsets` swap that NEVER reached gate qubit-index
resolution (element-wise refs are keyed by element string, not reg
name -> no-op) and self-cancelled for a whole-register
(a,b)/(b,a) pair. Confirmed: `Permute([a[0],b[0]],[b[0],a[0]])`
then `Y(a[0]); Z(b[0])` emitted `Y 0; Z 2` (Stim) / `Y:{0},Z:{2}`
(QC) instead of the correct `Y 2; Z 0` -- a silent miscompile,
the exact class #87 fixed in QIR (QASM was already correct).

Fix: port the proven #87 `permutation_map` model to both -- a
static logical relabel consulted in `get_qubit` (the single
qubit-ref chokepoint), built by `_process_permute` from expanded
refs with the post-#87-fold validation (dup-source / dup-target /
src-set!=tgt-set all fail loud) and atomic compose. Whole-register
CReg Permute fails loud (Stim/QC have no classical-register
model). Mirrors the Guppy linearity tracker / QIR #87.

Regression: test_permute_realized_quantum_circuit (default lane)
+ test_permute_realized_stim (optional_dependency) pin element +
whole-register realized targeting and the non-bijective fail-loud.

Verified: default 413->414 (+1 QC regression); optional_dependency
44 passed / 3 xfailed / 0 failed (+1 Stim regression); slow 33/0
(no regression); ruff/black-25.9.0 clean. No pre-existing test had
pinned the broken behavior (unlike QIR's 14 #87 tests).

* A: Stim unsupported-gate silent-skip -> fail loud (#78 surfaced-not-changed analogue of #88A)

stim._process_gate `if stim_gate is None: return` silently dropped
12 gates (CH/CRX/CRY/CRZ/F/F4/F4dg/Fdg/RX/RY/RZ/RZZ) -> the
emitted Stim circuit ran with WRONG semantics (a #74-class silent
miscompile, uncatchable downstream). Now raises NotImplementedError
("has no Stim lowering"), same doctrine as #74/#78/#88A. Stim is
Clifford-only so non-Clifford rotations are fundamentally
unrepresentable; the bug was emitting the circuit without them.
Regression test_unsupported_gate_fails_loud added.

QC investigated, NOT changed: it does not silent-skip -- it
name-passes-through (`.get(gate, gate.name)`); the 7 non-param
fallbacks are valid PECOS-sim gates (correct), and parameterized
gates already FAIL LOUD via the QuantumCircuit container's
"requires N angle(s), got 0" ValueError (not a silent miscompile).
No scope creep / no capability regression.

Blast radius ZERO (the #78 "broad reliance" fear did not
materialise in the corpus, same as #88A for QIR): default 414->415
(+1 regression), optional_dependency 44 passed / 3 xfailed / 0
failed, ruff/black-25.9.0 clean.

* Post-review fold (Codex blocker): complete the branch-wide persona/process-name strip from .py comments

Codex post-review NO -- 1 blocker: the 663c0f77 claim (and my
session claim) of a *branch-wide* clean was false; ~30 added .py
comment/docstring lines outside that 5-commit range still named
reviewers/process ("Codex S2 review", "post-review blocker",
"re-confirm bug", "Codex #81 ...", etc.) in 10 files
(guppy.py/converter.py/nodes.py/check_1flag.py/migrate_implicit_
return.py + 5 test modules). Pickle YES (read the claim
narrowly as changed-files-only); Codex's branch-wide reading
matches the durable rule -- fold it.

Stripped every occurrence, replacing with the durable issue ref
+ technical reason (e.g. "#80 re-confirm bug" -> "#80
name-collision bug"; "(Codex S2 review)" -> dropped, surrounding
text already states the rationale; "confirmed Codex 2026-05-16"
-> "confirmed 2026-05-16"). Comments/docstrings ONLY -- zero
behavior change.

Verified: `git diff dev...HEAD -- '*.py'` added lines now match
none of Codex/Pickle/grug/Banana/pre-review/post-review/
re-confirm; default 415/0, optional_dependency 44 passed / 3
xfailed / 0 failed, slow 33/0, ruff/black-25.9.0 clean.

* #93 (cont.): verified QIR lowering for the F/Fdg/F4/F4dg face Cliffords

Same verification-first method as the SX/SY subset: extracted each
unitary from the PECOS StateVec sim, searched the EXECUTABLE
Clifford set for a sequence equal up to a global phase, verified
end-to-end through qir_to_qis -> selene. Results (circuit order):
F=SZdg;H, Fdg=H;SZ, F4=H;SZdg, F4dg=SZ;H.

_GATE_DECOMP entries (incl. the existing SX/SXdg) now use
GateKind.SZ/SZdg rather than the redundant S/Sdg aliases, per the
PECOS convention that S==SZ / Sdg==SZdg (both already map to
"s"/"s__adj" in every codegen -- zero functional change;
re-verified). test_face_clifford_gates_executable pins the
end-to-end identities: F;Fdg / Fdg;F / F4;F4dg / F4dg;F4 == I,
the F|0>=|+> non-no-op discriminator, and F;F;F == I (PECOS F is
the order-3 face Clifford).

#71/#79 corpus buckets unchanged. Verified: default 415/0,
optional_dependency 44 passed / 3 xfailed / 0 failed, slow
face+sqrt 2/2 (full slow unaffected), ruff/black-25.9.0 clean.
Remaining #93: 2q Clifford (CY/SXX/SYY/SXXdg/SYYdg/SZZdg) need a
2q decomposition path; CH (non-Clifford) + CRX/CRY/CRZ
(rotations, no executable primitive) stay fail-loud.

* #95: canonicalize GateKind on SZ/SZdg; remove redundant S/Sdg (user-directed)

PECOS convention: the phase gate S == SZ and Sdg == SZdg. GateKind
carried both as redundant members. Per user directive, removed
GateKind.S/Sdg; the SLR `S`/`Sdg` gate classes now map (converter
GATE_KIND_MAP) to the canonical GateKind.SZ/SZdg. Dropped the now
redundant S/Sdg rows from every codegen map (GATE_TO_QIR/STIM/QC/
QASM/guppy -- SZ/SZdg rows already present), type_checker arity,
gate_properties INVERSE_PAIRS (SZ<->SZdg pair remains), and
_prep_tail PY/PNY tails. _GATE_DECOMP already on SZ/SZdg.

Behavior delta (user-decided -- "keep SZ->rz(pi/2)"): the SLR `S`
gate's QASM lowering changes `s q;` -> `rz(pi/2) q;` (and `sdg` ->
`rz(-pi/2)`), since the canonical kind SZ maps to rz(pi/2) in
qasm.py (the only codegen where S != SZ; physically identical, s
== rz(pi/2) up to global phase). All other codegens unchanged
(SZ == S there). #81 PY/PNY prep-tail QASM accordingly flips
h;s -> h;rz(pi/2); test_prep_gate_emitted_tail[PY,PNY] re-pinned
honestly from the actual emitted output (QIR needles unchanged --
SZ->"s" in QIR).

18 GateKind.S/Sdg refs across 10 source files; 0 residual; no test
referenced GateKind.S/Sdg directly. Verified: default 415/0,
optional_dependency 44 passed / 3 xfailed / 0 failed, slow 34/0,
ruff/black-25.9.0 clean. Pending dual review (repo-wide rename +
deliberate QASM delta).

* test hygiene: dedupe the repeated QC expected-string into a local (behavior-identical; clears the dirty worktree the #93/#95 reviewers flagged)

* #93 cont.: verified QIR lowering for SZZ/SZZdg/SXX/SXXdg/SYY/SYYdg/CY (7 2q Cliffords)

Per ~/Repos/qir-qis/src/lib.rs:59 (ALLOWED_QIS_FNS, 23 functions):
the qir-qis native 2q gate is rzz (parameterized) -- there is NO
zz/szz in the allowlist (the prior `GATE_TO_QIR[SZZ]="zz"` emitted
`__quantum__qis__zz__body` which qir-qis rejects with
"Unsupported QIR QIS function"). Quantinuum supports ZZ-flavor
entanglement natively, just as `rzz`. Fixed:

- Removed the misleading `GateKind.SZZ: "zz"` from `GATE_TO_QIR`
  (and `SZZ` from `TWO_QUBIT_GATES`) -- no more misleading
  codegen surface.
- Extended `_GATE_DECOMP` to a 2q-capable shape: each step is
  (prim_kind, qubit_idx_tuple, params_tuple); `_process_gate`
  emits each step with the input gate's targets routed and
  constant params threaded.
- Added 7 verified decompositions:
    SZZ   = RZZ(pi/2)(q0,q1)
    SZZdg = RZZ(-pi/2)(q0,q1)
    SXX   = (H@H); RZZ(pi/2); (H@H)
    SXXdg = (H@H); RZZ(-pi/2); (H@H)
    SYY   = (Sdg@Sdg);(H@H); RZZ(pi/2); (H@H);(S@S)
    SYYdg = (Sdg@Sdg);(H@H); RZZ(-pi/2); (H@H);(S@S)
    CY    = Sdg(t); CX(c,t); S(t)
  Each VERIFIED up-to-global-phase against the PECOS StateVec
  unitary AND end-to-end via qir_to_qis -> selene (inverse pairs,
  CY|10> -> i|11> non-vacuity, SZZ^2 = Z discriminator).
- `test_sqrt_pauli_2q_gates_executable` (slow+optional_dependency)
  pins the end-to-end identities.

Methodology correction recorded earlier in memory: my prior "rx is
a silent no-op on the Stim backend" claim was wrong (probe-harness
bug). RX/RY/RZ/RZZ all execute correctly. The truly misleading
surface was only SZZ -> "zz" (now fixed).

#71 honest re-pin: BUILD_FAILED 7 -> 5, QIS_OK 22 -> 24
(generic_check_xyz / generic_check_1flag_ch use CY which now
lowers, so they re-enter QIS_OK). #79 manifest updated: both
classified X (XYZ stabiliser on |0..0> is not an eigenstate ->
single uniformly random syndrome bit; no hard invariant);
histogram pin D=3/P=8/X=11 -> X=13.

Verified: default 415/0, optional_dependency 44 passed / 3
xfailed / 0 failed, slow 28/0 (24 QIS_OK + manifest guard +
sqrt/face/sqrt_pauli_2q behavioral), ruff/black-25.9.0 clean.

Remaining #93 (still fail-loud, no PECOS sim oracle): CH (sim
doesn't support), CRX/CRY/CRZ (parameterized, sim doesn't
support) -- decomposition would require a convention pin without
a verification oracle. Surfaced for separate scope.

* #93 post-review fold (Codex blocker): add phase-sensitive CY interference assertion

Codex post-review NO -- 1 blocker: the committed CY executable
test (X q0; CY; M q1 -> 1; CY|00>; M q1 -> 0) does not
discriminate the CY target-phase decomposition. Mutation probe
(swap CY's `Sdg(t); CX; S(t)` -> `S(t); CX; S(t)`) SURVIVED the
test, violating the prompt's explicit non-vacuity requirement.
Pickle YES (the unitary verification covers it; the e2e is a
secondary guard) -- but the test should still fail under that
mutation per the prompt + branch discipline.

Folded: added the phase-sensitive interference assertion Codex
prescribed -- `H q1; CY(q0, q1); H q1; MZ q1` with q0=|0> must
give 0. Verified: committed PASSes; the mutated S;CX;S CY
decomposition now FAILS the test (the assertion message names
the exact mutation it catches). Mechanism: with q0=|0> the
correct CY is identity, so H;I;H = I -> 0; the mutated S;CX;S
applies S^2 = Z on q1 even when control=0 (since CX is identity
then), giving H;Z;H = X -> 1.

Verified: default 415/0, optional_dependency 44 passed / 3
xfailed / 0 failed, slow sqrt_pauli_2q PASSes, ruff/black clean.

* #93 re-confirm fold (Codex blocker): COM812 trailing comma on the new CY assertion

Codex re-confirm NO -- 1 trivial blocker: the folded CY
interference assertion's multi-line set literal `{\n (0,)\n}`
(black-reformatted) triggers ruff COM812. The assertion was
semantically correct (Codex verified it catches both the
prescribed `S;CX;S` mutation AND the symmetric `Sdg;CX;Sdg`),
but `uv run ruff` (the project version, the authoritative gate
-- distinct from `uvx ruff@latest` which didn't flag it) was
red. Added the trailing comma via `ruff --fix`. Lanes unchanged:
default 415/0, optional_dependency 44/3xf/0, slow sqrt_pauli_2q
1/0; ruff + black-25.9.0 clean.

* #93 cont.: verified 1-CX QIR lowering for CH (non-Clifford -- Quest backend)

* #93 cont.: verified 1-RZZ QIR lowering for CRX/CRY/CRZ + PECOS oracles

* #88B: Guppy slot-local name disambiguation against declared register names

* Cross-codegen: verified Stim decomposition for F/Fdg/F4/F4dg face Cliffords

* Cross-codegen: native CRX/CRY/CRZ in ArbitraryRotationGateable + QC param-threading

* Cross-codegen: CRX/CRY/CRZ in CuStateVec/CudaStateVec/MPS simulator backends

* Cross-codegen post-review fold (Codex blocker): CR* control-superposition phase test

* #94 B1: classical-variable (whole-CReg scalar) QIR lowering via shared i64 pack

* Cross-codegen Guppy Phase A: native SX/SXdg (v/vdg) + RX/RY/RZ/CRZ rotations

* Cross-codegen Guppy Phase B: decompose SY/SYdg/F-family/sqrt-Paulis/CRX/CRY (native zz_phase)

* Post-review folds (Codex non-blockers): Guppy decomposed-rotation missing-angle fails loud + B1 LoopVar comment accuracy

* #97: SLR API -- rotational gates use angle-first RX(theta, q), remove bracket form

* #97 followup: fix stale test_control_flow_qir comment (rx now lowers; build-only is the non-Clifford-on-Stim caveat)

* #97 post-review fold (Codex blocker): fail loud on mis-ordered angle-first calls (SLR __call__ type guard + QIR/QASM arity guards)

* #97 post-review fold: narrow qarg guard to quantum qubit types (Codex reconfirm blocker)

* Add SLR v2 typed-angle API: rotation gates take rad()/turns() over the exposed angle64 dtype

* Make just lint pass: typos false-positive exemptions, black 26.x reformat, awk-portable GHA pinning check

* Harden GHA pinning check: bind the 40-hex SHA to the uses: ref so a comment SHA can't mask an unpinned action (Codex blocker)

* Strip internal planning/issue references and persona names from code comments, keeping the technical reasons

* Strip remaining internal stage labels and string-literal planning references from comments and messages, keeping public-tracker refs

* Strip remaining internal milestone labels from comment and message strings (R4/O1-2/S2-S3/Phase 3a-b/M-B2/iter)

* Strip milestone Phase/iter references from comments and messages, keeping the two-phase BlockCall algorithm labels
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.

6 participants