Key the annotation record map on MeasId instead of storage order - #390
Key the annotation record map on MeasId instead of storage order#390ciaranra wants to merge 1 commit into
Conversation
|
Two independent deep reviews, Fable and Sol, disagreed on the central question. I adjudicated by running both claims rather than choosing a side, and Sol is right. This should not merge as-is. The disagreementBoth were asked specifically to attack the mixed id-bearing / id-less case, which I had flagged as untested.
ReproducedOne measurement at positional record 0 carrying So this PR trades one silent failure for another. It fixes storage-order misattribution (annotation binds to the wrong measurement) and introduces a drop (annotation binds to nothing) whenever an annotation references a positional ordinal while the gate carries a non-positional id. Why this cannot be fixed by picking a key space
Sol's conclusion is the correct one: settle whether annotation references are record ordinals or identities, consistently, across both conversions and the annotation storage. That is #387 item 4, and it needs the positional ordinal stored at allocation rather than inferred. Also confirmed by Sol and worth recording: duplicate Where that leaves things
This PR would fix that but introduce a silent drop on a different construction. Neither state is clean, and I do not think a sixth incremental attempt at this invariant is the right move: five have now been made, four shipped a defect, and three of those were introduced while fixing the previous one. Recommendation: close this and do the measurement-identity work in #387 as one deliberate change -- store the positional ordinal, make both conversions agree on what an annotation reference means, enforce id uniqueness, and replace the silent |
|
Closing per the adjudication above: this fixes storage-order misattribution but introduces a silent drop when an annotation references a positional ordinal while the gate carries a non-positional The underlying problem is that annotation references and gate identities live in two different identifier spaces that coincide only when Folding into #387 to be done as one deliberate change. Everything learned is recorded there and in the comment above, so that work starts from evidence. |
Fixes a defect that landed on
devwith #386: the Tick→DAG conversion binds annotations to the wrong measurement when stored order differs from allocation order.Both reviewers of #386 identified this independently, each by running it, and it was reproduced a third time before that PR merged. #386 merged before the fix was applied.
The defect
TickCircuit::meas_refreads theMeasIdeach measurement was given at allocation time, so it is allocation-order-correct. The conversion did not: it rebuiltmeas_record_to_nodewith a running counter over stored ticks and batches, ignoring the ids sitting on each split gate.Storage order is not allocation order. A later measurement can be appended to an earlier compatible batch (
Tick::try_add_gate_piece->compatible_empty_attr_batch->append_batch), andtick_atfills ticks out of sequence.Reproduced on
devwithmz(&[0]),mz_free(&[1]),mz(&[2])-- allocation records 0, 1, 2, stored order[MZ(q0,q2), MeasureFree(q1)]:Wrong qubit and wrong gate type, silently.
DagFaultAnalyzertreatsMeasureFreeas a real measurement column, so the DEM observable tracks the wrong measurement rather than erroring. Reachable from the plain Python API with notick_atat all:t.mz([0]); t.mz_free([1]); t.mz([2])on one tick handle.Note the interaction: before #386 the same scenario silently dropped record 2 under MZ-only counting. #386 fixed the counter, which turned a drop into a wrong answer.
The fix
Key
meas_record_to_nodeon each measurement'sMeasIdrather than a positional counter. That is whatmeas_refreads and what the reverse conversion writes, so all three now agree. Gates carrying no ids are externally constructed and fall back to the counter, which is the best available for them.This is the fix both reviewers proposed independently.
What was already correct and stays correct
cxandszzwith physical prefixes, Z and X basis), verified by building the extension into an isolated venv.Testing
annotation_reaches_the_named_measurement_regardless_of_storage_orderbuilds the interleaved circuit and asserts the observable resolves to exactly(MZ, [2])-- not merely to some node, and not merely to a distinct one. That precision matters: #386's tests asserted only that two nodes were distinct, which is why they missed this.Mutation-verified: restoring the positional counter kills it.
cargo test -p pecos-quantum -p pecos-qecgreen: 775pecos-quantumlib tests, 0 failed across both.cargo clippy -p pecos-quantum -p pecos-rslib --all-targetsclean, forced cold;cargo fmt --all --checkclean.Still open, tracked in #387
MeasureLeakedis excluded from record counting here and inmeas_ref(consistently, in the same direction), the remaining silentfilter_mapdrops, the DAG→Tick identity/ordinal conflation, and themz_with_idsnon-positional case. Also #387 item 6: the Pythonmzbindings advance the record counter before the gate add can fail.