diff --git a/.gitignore b/.gitignore index 53c95e1b..3b3463ee 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,7 @@ test/na/Output/ # Release packaging output (issue #234) /dist/ + +# insta pending-snapshot scratch files: never intended to be committed +# (review via `cargo insta review`, then the .snap itself is the artifact) +*.snap.new diff --git a/quon_na/src/movement/duals.rs b/quon_na/src/movement/duals.rs index 2bdaf90a..e48a0d12 100644 --- a/quon_na/src/movement/duals.rs +++ b/quon_na/src/movement/duals.rs @@ -12,7 +12,7 @@ use std::collections::BTreeSet; use crate::geometry::euclidean_um; use crate::layout::{AtomId, Position, SiteId}; -use crate::movement::bank::{OrderedF64, layout_from_sites, site_position_map}; +use crate::movement::bank::{OrderedF64, layout_from_sites}; use crate::movement::geometry::legs_conflict; use crate::movement::types::{CandidateLeg, InteractionPair, MovementParams, MovementPlanError}; @@ -198,6 +198,3 @@ pub(crate) fn pack_legs_greedy( } Ok(chosen) } - -// Re-export for site_position_map callers in plan.rs. -pub(crate) use std::collections::BTreeMap; diff --git a/quon_na/src/movement/emit.rs b/quon_na/src/movement/emit.rs index d5a9c210..c32bb3f3 100644 --- a/quon_na/src/movement/emit.rs +++ b/quon_na/src/movement/emit.rs @@ -12,12 +12,10 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::geometry::{euclidean_um, movement_duration_us, ordered_pair}; -use crate::layout::{AodTrapRef, AtomBinding, AtomId, Position, SiteId, TrapBinding}; +use crate::layout::{AodTrapRef, AtomId, Position, SiteId}; use crate::movement::bank::{pair_occupants, site_position_map}; use crate::movement::duals::{AodGrid, DualCandidate, pack_legs_greedy}; -use crate::movement::geometry::{ - POS_EPS_UM, check_entangling_geometry, legs_conflict, verify_aod_legality, -}; +use crate::movement::geometry::{POS_EPS_UM, check_entangling_geometry, verify_aod_legality}; use crate::movement::types::{ CandidateLeg, InteractionPair, MoveSpec, MovementParams, MovementPlanError, }; diff --git a/quon_na/src/movement/geometry.rs b/quon_na/src/movement/geometry.rs index 472aa2b8..1b27553e 100644 --- a/quon_na/src/movement/geometry.rs +++ b/quon_na/src/movement/geometry.rs @@ -16,7 +16,7 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::geometry::{euclidean_um, ordered_pair}; use crate::layout::{AtomId, Position, SiteId}; -use crate::movement::types::{CandidateLeg, MoveSpec, MovementPlanError}; +use crate::movement::types::{MoveSpec, MovementPlanError}; /// R1–R3 check with dialect-identical `≤` predicates over **all occupied** atoms (B11). pub fn check_entangling_geometry( diff --git a/quon_na/src/movement/plan.rs b/quon_na/src/movement/plan.rs index 6c859dd2..7c76a1e7 100644 --- a/quon_na/src/movement/plan.rs +++ b/quon_na/src/movement/plan.rs @@ -12,7 +12,7 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::geometry::ordered_pair; -use crate::layout::{AtomBinding, AtomId, NeutralAtomLayout, Position, SiteId, TrapBinding}; +use crate::layout::{AtomBinding, AtomId, SiteId, TrapBinding}; use crate::movement::bank::{ensure_interaction_pairs, site_position_map}; use crate::movement::duals::{AodGrid, generate_duals, select_duals_sortis}; use crate::movement::emit::{ @@ -20,9 +20,7 @@ use crate::movement::emit::{ layer_has_entangle_n, layers_have_entangle, max_entangle2_width, }; use crate::movement::geometry::check_entangling_geometry; -use crate::movement::types::{ - InteractionPair, MovementParams, MovementPlanError, MovementPlanResult, -}; +use crate::movement::types::{MovementParams, MovementPlanError, MovementPlanResult}; use crate::schedule::{NeutralAtomAction, ScheduleLayer}; use crate::schedule_entry::GraphScheduleRequest; diff --git a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap index 8f86567f..a849c746 100644 --- a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap +++ b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap @@ -22,6 +22,10 @@ expression: json "code_family": "repetition_code_toy", "distance": 3, "memory_rounds": 2, + "t_count": 0, + "tdag_count": 0, + "ccz_count": 0, + "magic_state_demand": 0, "estimated_cycles": 37, "bottleneck": "rearrangement", "error_budget": { diff --git a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap.new b/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap.new deleted file mode 100644 index 41ba91df..00000000 --- a/quon_na/tests/snapshots/report_snapshots__qec_repetition_d3_hybrid_json.snap.new +++ /dev/null @@ -1,40 +0,0 @@ ---- -source: quon_na/tests/report_snapshots.rs -assertion_line: 206 -expression: json ---- -{ - "evidence_kind": "analytic", - "evidence_disclaimer": "Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020).", - "rydberg_stages": 6, - "rearrangement_steps": 8, - "rearrangement_time_us": 1060, - "trap_transfers": 26, - "transfer_time_us": 390, - "entangle2_count": 8, - "entangle_n_count": 0, - "measurement_rounds": 3, - "reset_rounds": 2, - "wait_time_us": 2, - "total_time_us": 1313, - "logical_qubits": 1, - "physical_atoms": 5, - "atoms_per_logical": 5, - "code_family": "repetition_code_toy", - "distance": 3, - "memory_rounds": 2, - "t_count": 0, - "tdag_count": 0, - "ccz_count": 0, - "magic_state_demand": 0, - "estimated_cycles": 37, - "bottleneck": "rearrangement", - "error_budget": { - "rydberg": 0.012, - "measurement": 0.009, - "reset": 0.008, - "movement": 0.004, - "transfer": 0.0182, - "idle": 4e-9 - } -} diff --git a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap index 2966b8b1..4f18bd16 100644 --- a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap +++ b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap @@ -22,6 +22,10 @@ expression: json "code_family": "surface_code_like", "distance": 3, "memory_rounds": 2, + "t_count": 0, + "tdag_count": 0, + "ccz_count": 0, + "magic_state_demand": 0, "estimated_cycles": 165, "bottleneck": "rearrangement", "error_budget": { diff --git a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap.new b/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap.new deleted file mode 100644 index 592d4f96..00000000 --- a/quon_na/tests/snapshots/report_snapshots__qec_surface_d3_hybrid_json.snap.new +++ /dev/null @@ -1,40 +0,0 @@ ---- -source: quon_na/tests/report_snapshots.rs -assertion_line: 206 -expression: json ---- -{ - "evidence_kind": "analytic", - "evidence_disclaimer": "Compiler analytic metrics only — not fused with Python/Sinter sampled CSV; neither artifact is a threshold claim (ADR-0020).", - "rydberg_stages": 16, - "rearrangement_steps": 46, - "rearrangement_time_us": 6345, - "trap_transfers": 162, - "transfer_time_us": 2430, - "entangle2_count": 48, - "entangle_n_count": 0, - "measurement_rounds": 3, - "reset_rounds": 2, - "wait_time_us": 2, - "total_time_us": 7752, - "logical_qubits": 1, - "physical_atoms": 17, - "atoms_per_logical": 17, - "code_family": "surface_code_like", - "distance": 3, - "memory_rounds": 2, - "t_count": 0, - "tdag_count": 0, - "ccz_count": 0, - "magic_state_demand": 0, - "estimated_cycles": 165, - "bottleneck": "rearrangement", - "error_budget": { - "rydberg": 0.032, - "measurement": 0.009, - "reset": 0.008, - "movement": 0.023, - "transfer": 0.1134, - "idle": 4e-9 - } -} diff --git a/quon_qec/src/expand.rs b/quon_qec/src/expand.rs index 7fde5997..0a2655a4 100644 --- a/quon_qec/src/expand.rs +++ b/quon_qec/src/expand.rs @@ -1459,17 +1459,6 @@ mod tests { ); } - let mz_anc = expanded - .rounds - .iter() - .find(|r| r.kind == RoundKind::MeasureAncilla) - .expect("ancilla mz"); - assert_eq!( - mz_anc.terminal.len(), - 3, - "ancilla logical Z is top-row (d atoms), not all data" - ); - // L-shaped: control left of ancilla (L/R rough seam); target below ancilla // (top/bottom smooth seam). let c = &expanded.blocks[0]; @@ -1479,6 +1468,36 @@ mod tests { .iter() .find(|b| b.logical_id == LogicalQubitId(2)) .expect("ancilla"); + + let mz_anc = expanded + .rounds + .iter() + .find(|r| r.kind == RoundKind::MeasureAncilla) + .expect("ancilla mz"); + let measure_events = mz_anc + .terminal + .iter() + .filter(|t| matches!(t, RoundTerminal::Measure { .. })) + .count(); + let reset_events = mz_anc + .terminal + .iter() + .filter(|t| matches!(t, RoundTerminal::Reset { .. })) + .count(); + assert_eq!( + measure_events, 3, + "ancilla logical Z is top-row (d atoms), not all data" + ); + // The ancilla's own footprint (2d²−1 for a distance-d surface patch), + // not `a.check_atoms.len()` — that field is read post-expansion and + // by then also holds the rough/smooth seam atoms attached afterward + // for check-graph accounting (see below), which the MeasureAncilla + // reset (emitted earlier, before that attachment) never touches. + let ancilla_footprint = 2 * (a.distance * a.distance) as usize - 1; + assert_eq!( + reset_events, ancilla_footprint, + "ancilla is fully retired (all data+check atoms reset) after its logical Z readout" + ); let c_max_x = c.coords.iter().map(|(x, _)| *x).max().unwrap(); let a_min_x = a .coords diff --git a/quon_qec/src/lattice_surgery.rs b/quon_qec/src/lattice_surgery.rs index b07dd1ac..9d445091 100644 --- a/quon_qec/src/lattice_surgery.rs +++ b/quon_qec/src/lattice_surgery.rs @@ -157,7 +157,27 @@ pub fn expand_logical_cx( Some(target), )?); - // 6. Measure ancilla logical Z (top-row product — not all data) + // 6. Measure ancilla logical Z (top-row product — not all data), then + // reset the whole ancilla footprint. The gadget leaves the ancilla fully + // disentangled from control/target at this point (byproducts corrected + // via the frame updates below), so every ancilla atom — not just the + // measured top row — is safe to reset; without this the un-reset + // bulk/check atoms stay physically "in the way" and trip the dialect + // verifier the first time a later op has to move them. + let mut ancilla_mz_terminal: Vec = ancilla_logical_z + .iter() + .map(|&atom| RoundTerminal::Measure { + atom, + basis: LogicalBasis::Z, + }) + .collect(); + ancilla_mz_terminal.extend( + ancilla + .data_atoms + .iter() + .chain(ancilla.check_atoms.iter()) + .map(|&atom| RoundTerminal::Reset { atom }), + ); rounds.push(PhysicalRound { kind: RoundKind::MeasureAncilla, logical_id: ancilla_id, @@ -166,13 +186,7 @@ pub fn expand_logical_cx( z_cnot_count: 0, local_mid: Vec::new(), local_after: Vec::new(), - terminal: ancilla_logical_z - .iter() - .map(|&atom| RoundTerminal::Measure { - atom, - basis: LogicalBasis::Z, - }) - .collect(), + terminal: ancilla_mz_terminal, partner_logical_id: None, frame_updates: Vec::new(), }); diff --git a/quon_qec/src/patch_ops.rs b/quon_qec/src/patch_ops.rs index 0823e7de..b7867ea3 100644 --- a/quon_qec/src/patch_ops.rs +++ b/quon_qec/src/patch_ops.rs @@ -807,6 +807,30 @@ pub fn lower_patch_plan( let layout = find_layout(layouts, *logical_id)?; let top_row = top_row_data(layout)?; ancilla_mz_atoms = top_row.clone(); + // The ancilla is fully retired here: by the Horsman gadget's + // design, the rough/smooth seam measurements plus this + // logical-Z readout leave it disentangled from control/target + // (byproducts are corrected via the frame updates below), so + // every ancilla atom — not just the measured top row — is + // safe to reset. Without this, the un-reset bulk/check atoms + // stay physically "in the way" and trip the dialect verifier + // (measured-then-reused-without-reset) the first time a later + // op's movement/geometry pass has to touch them, e.g. a + // second logical_cx allocating the next ancilla nearby. + let mut terminal: Vec = top_row + .iter() + .map(|&atom| RoundTerminal::Measure { + atom, + basis: LogicalBasis::Z, + }) + .collect(); + terminal.extend( + layout + .data_atoms + .iter() + .chain(layout.check_atoms.iter()) + .map(|&atom| RoundTerminal::Reset { atom }), + ); rounds.push(PhysicalRound { kind: RoundKind::MeasureAncilla, logical_id: *logical_id, @@ -815,13 +839,7 @@ pub fn lower_patch_plan( z_cnot_count: 0, local_mid: Vec::new(), local_after: Vec::new(), - terminal: top_row - .iter() - .map(|&atom| RoundTerminal::Measure { - atom, - basis: LogicalBasis::Z, - }) - .collect(), + terminal, partner_logical_id: None, frame_updates: Vec::new(), }); diff --git a/quon_qec/tests/patch_ops.rs b/quon_qec/tests/patch_ops.rs index f732f3e8..915b86a2 100644 --- a/quon_qec/tests/patch_ops.rs +++ b/quon_qec/tests/patch_ops.rs @@ -274,11 +274,27 @@ fn cx_plan_ancilla_measurement_is_top_row() { .find(|r| r.kind == quon_qec::expand::RoundKind::MeasureAncilla) .expect("ancilla mz"); // d=3 → top row has 3 data atoms + let measure_events = mz_anc + .terminal + .iter() + .filter(|t| matches!(t, quon_qec::expand::RoundTerminal::Measure { .. })) + .count(); assert_eq!( - mz_anc.terminal.len(), - 3, + measure_events, 3, "ancilla logical Z is top-row (d atoms), not all data" ); + // The ancilla is fully retired here: 9 data + 8 check atoms reset (d=3 + // intrinsic footprint, before the seam atoms get attached afterward for + // check-graph accounting — see cx_plan_seam_atoms_attached_to_ancilla_block). + let reset_events = mz_anc + .terminal + .iter() + .filter(|t| matches!(t, quon_qec::expand::RoundTerminal::Reset { .. })) + .count(); + assert_eq!( + reset_events, 17, + "ancilla is fully retired (all data+check atoms reset) after its logical Z readout" + ); } #[test]