Skip to content

First-class measure-and-prepare: GateType::MPZ core (#412 phase 1) - #416

Merged
ciaranra merged 9 commits into
devfrom
mpz-core
Aug 4, 2026
Merged

First-class measure-and-prepare: GateType::MPZ core (#412 phase 1)#416
ciaranra merged 9 commits into
devfrom
mpz-core

Conversation

@ciaranra

@ciaranra ciaranra commented Aug 3, 2026

Copy link
Copy Markdown
Member

Phase 1 of #412: first-class measure-and-prepare in the core gate set. GateType::MPZ (measure +Z, then prepare |0>) exists end to end -- gate type, builders, propagation, execution -- so circuits no longer spell the fused hardware operation as two gates, and CliffordGateable::mpz is reachable from the circuit layer for the first time.

What MPZ is, at every layer

  • Gate set: GateType::MPZ = 107, consumes_measurement_record() == true (it records like MZ), Gate::mpz, validate rules, display/diagram entries.
  • Builders: DagCircuit::mpz/try_mpz and TickCircuit tick handle mpz(), returning measurement refs exactly like mz() -- ids mint through the shared predicate, so annotations, conversions, and resolution work unchanged.
  • Propagation (cross_measurement): nothing crosses in either direction -- the record flip is taken by the walker before the crossing, then the built-in preparation absorbs the error forward and blocks observables backward. Discriminators pin it: an X before an MPZ flips its readout and nothing after the reset; the checker flip-ledger keeps the recorded flip readable (its built-in preparation deliberately does not clear the ledger, mirroring MeasureFree).
  • Execution: all three engine gate loops (state-vector, Clifford-message, general-message) execute sim.mpz; ByteMessage gains the mpz builder; the symbolic replay, hidden-measurement synthesis, and fault-catalog history lower it as measure-then-prepare; eeg's expansion lowers it to its measurement-plus-projection form. The pecos-neo runtime and HUGR/QASM translators refuse it loudly (their vocabularies are later First-class measure-and-prepare (MP*) across the gate sets #412 phases).
  • Semantics anchor: mpz and the mz; pz idiom build byte-identical eeg DEMs at p_prep = 0, and a three-engine execution test pins X; MPZ; MZ -> [1, 0].

Noise: consistency over fidelity, deliberately

Every noise path models MPZ as the measurement-half fault only (a fault before the readout). The engines runtime, both DEM builders, and eeg therefore agree with each other; the prepare-half fault arrives for all of them at once with #412's dedicated measure-prepare channel, which also needs the after-location the location model does not yet give measurements. Until then, the split spelling's explicit reset draws preparation noise the fused gate does not -- the equivalence test documents this and tests at p_prep = 0.

A simulator bug this surfaced and fixes

Making mpz reachable exposed that StabVec had the #394 defect at the simulator level: its diagonal-frame measurement shortcut flipped the reported outcome while collapsing the stored, unflipped eigenstate and discarding the frame -- so X; MZ; MZ read [1, 0] where the state-vector ground truth reads [1, 1], and the default measure-and-prepare correction composed against the wrong state. Latent for the usual reason: re-prepared ancillas never re-measure. The projection now re-aligns the state with the report (collapse projects; it does not reset), pinned by regressions for both the bare repeated measurement and the mpz correction.

Scope and follow-ups (recorded on #412)

Vocabulary surfaces deliberately out of phase 1: Python bindings (run_gate dispatch, tick/dag binding methods), SLR/PHIR/QIS/Guppy/QASM translators, the pecos-neo gate set, the dedicated measure-prepare noise channel, and the MPX/MPY family plus the MeasureFree -> MZFree rename candidate. Everything unreachable refuses loudly rather than guessing.

Verification

Cold cargo clippy --locked --workspace --all-targets -- -D warnings on a fresh target dir, cargo fmt --check, the CI-shaped cargo test set, both extensions rebuilt, the full Python lane with CARGO_TARGET_DIR unset, the #391 DEM ordering harness, and pre-commit -- all green at the pushed commit. Mutations killed by named tests: MPZ crossing behaves like MZ (keeps X), eeg passes MPZ through unlowered, and all three executor arms run it as a plain mz (no reset) -- each caught, plus the StabVec collapse regressions.

@ciaranra ciaranra added the enhancement New feature or request label Aug 3, 2026
@ciaranra

ciaranra commented Aug 3, 2026

Copy link
Copy Markdown
Member Author

Two independent adversarial reviews (blind arms), both REVISE, with heavily convergent findings -- and one arm's 400-seed differential fuzz validated the StabVec collapse fix decisively (0/400 failures on this branch versus 197/400 on dev, including the exact X; MZ; MZ witness). Every finding was verified against the code; all fixes are in and mutation-tested.

Confirmed and fixed

The checker flip-ledger retained MPZ entries on a live wire (both arms, HIGH). MPZ hands back a usable |0> wire, so a retained ledger X spread through later gates as a phantom -- and the fused gate disagreed with its own mz; pz lowering, whose explicit PZ clears. MPZ now clears the ledger like the reset it contains; a lowering-equivalence witness (fused == split, plus nothing-spreads-through-the-CX) pins it. Its mid-circuit flips are invisible to end-reads exactly as the lowering's always were; readable mid-circuit flips belong to the residual/record separation already filed on #411.

The dispatch sweep's blind spot was real (both arms; the review prompt asked for exactly this hunt). Sites keyed on explicit MZ-lists rather than MeasureFree-membership were missed and are now fixed, each with the review's named line: TickFaultAnalyzer::extract_measurements (silently dropped MPZ records -- run-verified witness, now pinned by a test), the three DEM noise lanes (dem_sampler, mem_builder, and the sampler's p_meas bucket -- MPZ records were noiseless or got 1q-depolarizing rates), SpacetimeLocation::is_measurement, the unitary_rep diagram family, GeneralNoiseModel (tracked MPZ as a 1q-unitary then died on an outcome-count mismatch; now tracked as a measurement with post-engine noise and prepared-qubit bookkeeping), the fourth public CircuitExecutor, and GateType::FromStr (which also gained the pre-existing missing MeasureFree/MeasureLeaked arms, closing the Python silent-custom-gate path for the whole family).

Fixing the sampler lane surfaced one more real defect: the InfluenceBuilder detector walk backward-crossed the seed's own measurement node, so an MPZ's reset killed its own seed before the before-location recorded -- its records had no fault mechanisms at all. The walk now treats the seed's own node as the readout it represents (record the before-location, no crossing); behavior-identical for MZ (the suite proves it) and correct for MPZ, pinned by the p_meas mechanism test.

QASM export mangled MPZ into literal unknown q[0]; -- it now lowers to its measure-then-reset spelling (with the classical target when mapped).

StabVec::mnz was broken pre-existing (one arm read it, the other ran it: the Z-frame compose commutes with a Z readout and can never flip the outcome). Fixed via the trait's reference decomposition; a differential test against SparseStab pins mnz/pnz including the state-after check. The fuzz numbers showing widespread mnz disagreement were pre-existing on dev and orthogonal to this PR's eager-X fix, which the same fuzz validated.

Docs: the mpz/mz_free rustdoc splice is repaired; the stale fault-sampler comment updated.

Deliberately unchanged, with reasons

AbsorbBasisGates' is_z_prep/is_z_measure exclude MPZ -- a conservative miss (skipped optimization, never wrong output); widening them safely needs its own analysis and rides #412. The Python bindings still cannot construct MPZ (no mpz method) -- that is the planned bindings phase of #412; what this round closed is the silent-custom-gate hole when naming it. The prepare-half noise remains deferred to #412's dedicated channel, unchanged from the PR's documented decision.

Verification

At the pushed commit: cold cargo clippy --locked --workspace --all-targets -- -D warnings on a fresh target dir, cargo fmt --check, the CI-shaped cargo test set, both extensions rebuilt, the full Python lane with CARGO_TARGET_DIR unset, the #391 DEM ordering harness, and pre-commit -- all green. Round mutations, each killed by its named test: tick extraction dropping MPZ, the sampler p_meas bucket reverting, the own-node readout rule removed, the ledger retaining MPZ entries, and mnz reverting to the Z-frame shortcut -- plus the original PR's mutations, which remain killed.

@ciaranra
ciaranra merged commit 8ad150c into dev Aug 4, 2026
86 checks passed
@ciaranra
ciaranra deleted the mpz-core branch August 4, 2026 00:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant