diff --git a/docs/adr/0027-flux-infer-iterator-closure-ice.md b/docs/adr/0027-flux-infer-iterator-closure-ice.md index 3753fee..79e9b60 100644 --- a/docs/adr/0027-flux-infer-iterator-closure-ice.md +++ b/docs/adr/0027-flux-infer-iterator-closure-ice.md @@ -12,15 +12,18 @@ The panic occurs in `flux-infer::projections` with the message ## Affected functions -Two functions in `quon_qec/src/experiment.rs` retain +These functions in `quon_qec` retain `#[cfg_attr(feature = "flux", flux_rs::trusted)]` because of this ICE: 1. **`emit_stim_single_block_memory`** — uses `.iter().filter().collect()` and `.iter().find()` chains. 2. **`emit_stim_lattice_surgery_cx`** — uses `.iter()`, `.find()`, - `.filter()`, `.map()` chains. + `.filter()`, `.map()` chains in the header and observable tail. +3. **`stim_emit::emit_memory_round`** — the lattice-surgery memory-round + impl moved out of (2); it still uses `.filter_map()`, `.find()`, and + `.position()` closures. -Neither function carries flux refinement specs, so marking them `trusted` +None of these functions carry flux refinement specs, so marking them `trusted` skips their bodies entirely — no verification coverage is lost. ## Reproduction @@ -35,7 +38,7 @@ closure-based iterator adapters. ## Workaround -Keep `#[cfg_attr(feature = "flux", flux_rs::trusted)]` on these two +Keep `#[cfg_attr(feature = "flux", flux_rs::trusted)]` on these functions with a comment referencing this document. Remove once the upstream bug is fixed. diff --git a/quon_qec/src/experiment.rs b/quon_qec/src/experiment.rs index 8f645cf..95dc045 100644 --- a/quon_qec/src/experiment.rs +++ b/quon_qec/src/experiment.rs @@ -15,6 +15,8 @@ use crate::expand::{ use crate::family::SourceFamily; use crate::workload::LogicalBasis; +mod stim_emit; + /// Wire schema version for `*.qec.json`. pub const QEC_EXPERIMENT_SCHEMA_VERSION: u32 = 1; @@ -695,124 +697,21 @@ fn emit_stim_lattice_surgery_cx(expanded: &ExpandedWorkload) -> Result = - std::collections::HashMap::new(); - let mut rec_count: i32 = 0; - let mut frame_updates: Vec<&crate::expand::PauliFrameUpdate> = Vec::new(); - let mut measure_logical_rounds = Vec::new(); - let mut memory_detector_i = 0u32; - + // Round kinds append through [`stim_emit::StimRoundEmitter`]. Byproduct + // handles and deferred measure-logical rounds come back out for observable + // assembly below. + let mut ctx = stim_emit::LatticeSurgeryCtx::new(expanded, control, target, ancilla, out); for round in &expanded.rounds { - match round.kind { - RoundKind::Construct => { - emit_local_ops(&mut out, &round.local_before); - } - RoundKind::Merge(crate::expand::MergeBoundary::Rough) => { - // Logical ZZ(control, ancilla) — one record for the joint parity. - let mut ops = Vec::new(); - for a in logical_observable_atoms(control, LogicalBasis::Z) { - ops.push(format!("Z{a}")); - } - for a in logical_observable_atoms(ancilla, LogicalBasis::Z) { - ops.push(format!("Z{a}")); - } - out.push_str("MPP "); - out.push_str(&ops.join("*")); - out.push('\n'); - byproduct_rec.insert("rough_merge", rec_count); - rec_count += 1; - out.push_str("TICK\n"); - } - RoundKind::Merge(crate::expand::MergeBoundary::Smooth) => { - // Logical XX(ancilla, target). - let mut ops = Vec::new(); - for a in logical_observable_atoms(ancilla, LogicalBasis::X) { - ops.push(format!("X{a}")); - } - for a in logical_observable_atoms(target, LogicalBasis::X) { - ops.push(format!("X{a}")); - } - out.push_str("MPP "); - out.push_str(&ops.join("*")); - out.push('\n'); - byproduct_rec.insert("smooth_merge", rec_count); - rec_count += 1; - out.push_str("TICK\n"); - } - RoundKind::Split(_) => { - // NA schedules full stabilizer restore on split; Stim uses logical - // MPP merges and must not scramble data with mid-protocol MR. - out.push_str("# split_restore (NA-scheduled; omitted from Stim MPP path)\n"); - } - RoundKind::MemoryRound => { - emit_round_body(&mut out, round)?; - let measured: Vec = round - .terminal - .iter() - .filter_map(|t| match t { - RoundTerminal::Measure { atom, .. } => Some(atom.0), - _ => None, - }) - .collect(); - if measured.is_empty() { - continue; - } - out.push_str("MR"); - for id in &measured { - out.push_str(&format!(" {id}")); - rec_count += 1; - } - out.push('\n'); - if let Some(block) = expanded - .blocks - .iter() - .find(|b| b.logical_id == round.logical_id) - { - let n = measured.len() as i32; - for stab in &block.stabilizers { - if stab.basis != LogicalBasis::Z { - continue; - } - let Some(pos) = measured.iter().position(|a| *a == stab.check.0) else { - continue; - }; - let cur = -(n - pos as i32); - out.push_str(&format!("DETECTOR({memory_detector_i}, 0) rec[{cur}]\n")); - memory_detector_i += 1; - } - } - out.push_str("TICK\n"); - } - RoundKind::MeasureAncilla => { - // Logical Z of ancilla (top-row product) as one MPP record. - let ops: Vec = logical_observable_atoms(ancilla, LogicalBasis::Z) - .into_iter() - .map(|a| format!("Z{a}")) - .collect(); - out.push_str("MPP "); - out.push_str(&ops.join("*")); - out.push('\n'); - byproduct_rec.insert("ancilla_mz", rec_count); - rec_count += 1; - out.push_str("TICK\n"); - } - RoundKind::FrameUpdate => { - frame_updates.extend(round.frame_updates.iter()); - } - RoundKind::MeasureLogical => { - measure_logical_rounds.push(round); - } - RoundKind::MagicT | RoundKind::MagicTdag | RoundKind::MagicCcz => { - // Magic-state-consuming ops are compiler model only (issue #283). - // No physical Stim gates — record as a comment for traceability. - out.push_str(&format!( - "# {} (magic-state consumption; compiler model, no Stim gate)\n", - round.kind.as_experiment_str() - )); - } - } + stim_emit::lattice_round_emitter(round.kind).emit(&mut ctx, round)?; } + let stim_emit::LatticeSurgeryCtx { + mut out, + byproduct_rec, + mut rec_count, + frame_updates, + measure_logical_rounds, + .. + } = ctx; let mut obs_id = 0u32; for round in measure_logical_rounds { @@ -1924,4 +1823,49 @@ print(f"ok dets={{c.num_detectors}} obs={{c.num_observables}}") Err(e) => eprintln!("skip stim correctness: python unavailable ({e})"), } } + + #[test] + fn stim_structure_matches_checked_in_gold() { + let surface_z = { + let mut b = WorkloadBuilder::new(); + b.construct(SourceFamily::Surface, 3, LogicalBasis::Z, LogicalQubitId(0)) + .expect("construct"); + b.memory_round(LogicalQubitId(0)).expect("r1"); + b.memory_round(LogicalQubitId(0)).expect("r2"); + b.measure_logical(LogicalQubitId(0), LogicalBasis::Z) + .expect("mz"); + expand_workload(&b.finish()).expect("expand") + }; + let surface_x = { + let mut b = WorkloadBuilder::new(); + b.construct(SourceFamily::Surface, 3, LogicalBasis::X, LogicalQubitId(0)) + .expect("construct"); + b.memory_round(LogicalQubitId(0)).expect("r1"); + b.memory_round(LogicalQubitId(0)).expect("r2"); + b.measure_logical(LogicalQubitId(0), LogicalBasis::X) + .expect("mx"); + expand_workload(&b.finish()).expect("expand") + }; + let cases = [ + ( + repetition_d3_two_rounds(), + include_str!("../testdata/stim/repetition_d3_z.stim"), + ), + ( + surface_z, + include_str!("../testdata/stim/surface_d3_z.stim"), + ), + ( + surface_x, + include_str!("../testdata/stim/surface_d3_x.stim"), + ), + ( + surface_d3_cx_expanded(), + include_str!("../testdata/stim/lattice_surgery_cx_d3.stim"), + ), + ]; + for (expanded, gold) in cases { + assert_eq!(emit_stim_structure(&expanded).expect("stim"), gold); + } + } } diff --git a/quon_qec/src/experiment/stim_emit.rs b/quon_qec/src/experiment/stim_emit.rs new file mode 100644 index 0000000..a62d9e0 --- /dev/null +++ b/quon_qec/src/experiment/stim_emit.rs @@ -0,0 +1,492 @@ +//! Lattice-surgery Stim round emitters. +//! +//! [`StimRoundEmitter`] is the extension point for a scheduled [`RoundKind`]. +//! [`lattice_round_emitter`] is the only match that selects an impl. The +//! circuit builder walks the schedule and calls [`StimRoundEmitter::emit`]; +//! it does not branch on the round kind. +//! +//! A new round kind is a new impl plus one arm in [`lattice_round_emitter`]. +//! Single-block memory Stim stays in `experiment.rs` for a later slice. + +use std::collections::HashMap; + +use crate::expand::{ + ExpandedBlock, ExpandedWorkload, MergeBoundary, PauliFrameUpdate, PhysicalRound, RoundKind, + RoundTerminal, +}; +use crate::workload::LogicalBasis; + +use super::{ExperimentError, emit_local_ops, emit_round_body, logical_observable_atoms}; + +/// Shared Stim state for one lattice-surgery circuit. +/// +/// Round impls append instructions and record byproduct handles. Observable +/// assembly after the schedule still lives in the circuit builder. +pub(crate) struct LatticeSurgeryCtx<'a> { + pub(crate) expanded: &'a ExpandedWorkload, + pub(crate) control: &'a ExpandedBlock, + pub(crate) target: &'a ExpandedBlock, + pub(crate) ancilla: &'a ExpandedBlock, + pub(crate) out: String, + pub(crate) byproduct_rec: HashMap<&'static str, i32>, + pub(crate) rec_count: i32, + pub(crate) frame_updates: Vec<&'a PauliFrameUpdate>, + pub(crate) measure_logical_rounds: Vec<&'a PhysicalRound>, + pub(crate) memory_detector_i: u32, +} + +impl<'a> LatticeSurgeryCtx<'a> { + pub(crate) fn new( + expanded: &'a ExpandedWorkload, + control: &'a ExpandedBlock, + target: &'a ExpandedBlock, + ancilla: &'a ExpandedBlock, + out: String, + ) -> Self { + Self { + expanded, + control, + target, + ancilla, + out, + byproduct_rec: HashMap::new(), + rec_count: 0, + frame_updates: Vec::new(), + measure_logical_rounds: Vec::new(), + memory_detector_i: 0, + } + } +} + +/// Stim instructions for one lattice-surgery round kind. +pub(crate) trait StimRoundEmitter { + /// Stable registry name. Tests lock [`lattice_round_emitter`] against it. + #[cfg(test)] + fn label(&self) -> &'static str; + + /// Append this round's structure Stim to `ctx`. + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, + ) -> Result<(), ExperimentError>; +} + +/// Select the impl for `kind`. +/// +/// This match is the registry. Emit logic for a kind lives on its impl. +pub(crate) fn lattice_round_emitter(kind: RoundKind) -> &'static dyn StimRoundEmitter { + match kind { + RoundKind::Construct => &CONSTRUCT, + RoundKind::MemoryRound => &MEMORY, + RoundKind::MeasureLogical => &MEASURE_LOGICAL, + RoundKind::Merge(MergeBoundary::Rough) => &MERGE_ROUGH, + RoundKind::Merge(MergeBoundary::Smooth) => &MERGE_SMOOTH, + RoundKind::Split(_) => &SPLIT, + RoundKind::MeasureAncilla => &MEASURE_ANCILLA, + RoundKind::FrameUpdate => &FRAME_UPDATE, + RoundKind::MagicT => &MAGIC_T, + RoundKind::MagicTdag => &MAGIC_TDAG, + RoundKind::MagicCcz => &MAGIC_CCZ, + } +} + +const CONSTRUCT: ConstructEmit = ConstructEmit; +const MEMORY: MemoryRoundEmit = MemoryRoundEmit; +const MEASURE_LOGICAL: MeasureLogicalEmit = MeasureLogicalEmit; +const MERGE_ROUGH: RoughMergeEmit = RoughMergeEmit; +const MERGE_SMOOTH: SmoothMergeEmit = SmoothMergeEmit; +const SPLIT: SplitEmit = SplitEmit; +const MEASURE_ANCILLA: MeasureAncillaEmit = MeasureAncillaEmit; +const FRAME_UPDATE: FrameUpdateEmit = FrameUpdateEmit; +const MAGIC_T: MagicConsumeEmit = MagicConsumeEmit; +const MAGIC_TDAG: MagicConsumeEmit = MagicConsumeEmit; +const MAGIC_CCZ: MagicConsumeEmit = MagicConsumeEmit; + +struct ConstructEmit; + +impl StimRoundEmitter for ConstructEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "construct" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + emit_local_ops(&mut ctx.out, &round.local_before); + Ok(()) + } +} + +struct MemoryRoundEmit; + +impl StimRoundEmitter for MemoryRoundEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "memory_round" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + emit_memory_round(ctx, round) + } +} + +struct MeasureLogicalEmit; + +impl StimRoundEmitter for MeasureLogicalEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "measure_logical" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + ctx.measure_logical_rounds.push(round); + Ok(()) + } +} + +struct RoughMergeEmit; + +impl StimRoundEmitter for RoughMergeEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "merge_rough" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + _round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + // Logical ZZ(control, ancilla) — one record for the joint parity. + let control = ctx.control; + let ancilla = ctx.ancilla; + let mut ops = Vec::new(); + for a in logical_observable_atoms(control, LogicalBasis::Z) { + ops.push(format!("Z{a}")); + } + for a in logical_observable_atoms(ancilla, LogicalBasis::Z) { + ops.push(format!("Z{a}")); + } + push_mpp(&mut ctx.out, &ops); + ctx.byproduct_rec.insert("rough_merge", ctx.rec_count); + ctx.rec_count += 1; + ctx.out.push_str("TICK\n"); + Ok(()) + } +} + +struct SmoothMergeEmit; + +impl StimRoundEmitter for SmoothMergeEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "merge_smooth" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + _round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + // Logical XX(ancilla, target). + let ancilla = ctx.ancilla; + let target = ctx.target; + let mut ops = Vec::new(); + for a in logical_observable_atoms(ancilla, LogicalBasis::X) { + ops.push(format!("X{a}")); + } + for a in logical_observable_atoms(target, LogicalBasis::X) { + ops.push(format!("X{a}")); + } + push_mpp(&mut ctx.out, &ops); + ctx.byproduct_rec.insert("smooth_merge", ctx.rec_count); + ctx.rec_count += 1; + ctx.out.push_str("TICK\n"); + Ok(()) + } +} + +struct SplitEmit; + +impl StimRoundEmitter for SplitEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "split" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + _round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + // NA schedules full stabilizer restore on split; Stim uses logical + // MPP merges and must not scramble data with mid-protocol MR. + ctx.out + .push_str("# split_restore (NA-scheduled; omitted from Stim MPP path)\n"); + Ok(()) + } +} + +struct MeasureAncillaEmit; + +impl StimRoundEmitter for MeasureAncillaEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "measure_ancilla" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + _round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + // Logical Z of ancilla (top-row product) as one MPP record. + let ancilla = ctx.ancilla; + let mut ops = Vec::new(); + for a in logical_observable_atoms(ancilla, LogicalBasis::Z) { + ops.push(format!("Z{a}")); + } + push_mpp(&mut ctx.out, &ops); + ctx.byproduct_rec.insert("ancilla_mz", ctx.rec_count); + ctx.rec_count += 1; + ctx.out.push_str("TICK\n"); + Ok(()) + } +} + +struct FrameUpdateEmit; + +impl StimRoundEmitter for FrameUpdateEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "frame_update" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + for upd in &round.frame_updates { + ctx.frame_updates.push(upd); + } + Ok(()) + } +} + +/// Magic-state consumption (T, T†, CCZ) is one comment shape (issue #283). +struct MagicConsumeEmit; + +impl StimRoundEmitter for MagicConsumeEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "magic_consume" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + // Compiler model only — no physical Stim gates. + ctx.out.push_str(&format!( + "# {} (magic-state consumption; compiler model, no Stim gate)\n", + round.kind.as_experiment_str() + )); + Ok(()) + } +} + +fn push_mpp(out: &mut String, ops: &[String]) { + out.push_str("MPP "); + out.push_str(&ops.join("*")); + out.push('\n'); +} + +// `trusted` (flux-infer ICE): `.filter_map()`, `.find()`, and `.position()` +// closures in this body trip the same projection ICE as +// `emit_stim_lattice_surgery_cx` (ADR-0027). No flux specs here. +#[cfg_attr(feature = "flux", flux_rs::trusted)] +fn emit_memory_round<'a>( + ctx: &mut LatticeSurgeryCtx<'a>, + round: &'a PhysicalRound, +) -> Result<(), ExperimentError> { + let expanded = ctx.expanded; + emit_round_body(&mut ctx.out, round)?; + let measured: Vec = round + .terminal + .iter() + .filter_map(|t| match t { + RoundTerminal::Measure { atom, .. } => Some(atom.0), + _ => None, + }) + .collect(); + if measured.is_empty() { + return Ok(()); + } + ctx.out.push_str("MR"); + for id in &measured { + ctx.out.push_str(&format!(" {id}")); + ctx.rec_count += 1; + } + ctx.out.push('\n'); + if let Some(block) = expanded + .blocks + .iter() + .find(|b| b.logical_id == round.logical_id) + { + let n = measured.len() as i32; + for stab in &block.stabilizers { + if stab.basis != LogicalBasis::Z { + continue; + } + let Some(pos) = measured.iter().position(|a| *a == stab.check.0) else { + continue; + }; + let cur = -(n - pos as i32); + let memory_detector_i = ctx.memory_detector_i; + ctx.out + .push_str(&format!("DETECTOR({memory_detector_i}, 0) rec[{cur}]\n")); + ctx.memory_detector_i += 1; + } + } + ctx.out.push_str("TICK\n"); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::expand::PhysicalRound; + use crate::family::{CodeFamily, SourceFamily}; + use crate::workload::{LogicalBasis, LogicalQubitId}; + + fn surface_block(id: u32) -> ExpandedBlock { + ExpandedBlock { + logical_id: LogicalQubitId(id), + family: SourceFamily::Surface, + code_family: CodeFamily::SurfaceCodeLike { distance: 3 }, + distance: 3, + init_basis: LogicalBasis::Z, + atoms: Vec::new(), + data_atoms: Vec::new(), + check_atoms: Vec::new(), + coords: Vec::new(), + stabilizers: Vec::new(), + } + } + + fn workload() -> ExpandedWorkload { + ExpandedWorkload { + blocks: vec![surface_block(0), surface_block(1), surface_block(2)], + rounds: Vec::new(), + } + } + + fn emit_kind(kind: RoundKind) -> String { + let expanded = workload(); + let round = PhysicalRound::bare(kind, LogicalQubitId(0)); + let mut ctx = LatticeSurgeryCtx::new( + &expanded, + &expanded.blocks[0], + &expanded.blocks[1], + &expanded.blocks[2], + String::new(), + ); + lattice_round_emitter(kind) + .emit(&mut ctx, &round) + .expect("emit"); + ctx.out + } + + #[test] + fn every_round_kind_selects_its_emitter() { + let cases = [ + (RoundKind::Construct, "construct"), + (RoundKind::MemoryRound, "memory_round"), + (RoundKind::MeasureLogical, "measure_logical"), + (RoundKind::Merge(MergeBoundary::Rough), "merge_rough"), + (RoundKind::Merge(MergeBoundary::Smooth), "merge_smooth"), + (RoundKind::Split(MergeBoundary::Rough), "split"), + (RoundKind::Split(MergeBoundary::Smooth), "split"), + (RoundKind::MeasureAncilla, "measure_ancilla"), + (RoundKind::FrameUpdate, "frame_update"), + (RoundKind::MagicT, "magic_consume"), + (RoundKind::MagicTdag, "magic_consume"), + (RoundKind::MagicCcz, "magic_consume"), + ]; + for (kind, label) in cases { + assert_eq!(lattice_round_emitter(kind).label(), label); + } + } + + #[test] + fn magic_and_split_emitters_write_comments_only() { + assert_eq!( + emit_kind(RoundKind::MagicT), + "# magic_t (magic-state consumption; compiler model, no Stim gate)\n" + ); + assert_eq!( + emit_kind(RoundKind::MagicTdag), + "# magic_tdag (magic-state consumption; compiler model, no Stim gate)\n" + ); + assert_eq!( + emit_kind(RoundKind::MagicCcz), + "# magic_ccz (magic-state consumption; compiler model, no Stim gate)\n" + ); + assert_eq!( + emit_kind(RoundKind::Split(MergeBoundary::Rough)), + "# split_restore (NA-scheduled; omitted from Stim MPP path)\n" + ); + assert_eq!( + emit_kind(RoundKind::Split(MergeBoundary::Smooth)), + emit_kind(RoundKind::Split(MergeBoundary::Rough)) + ); + } + + struct MarkerEmit; + + impl StimRoundEmitter for MarkerEmit { + #[cfg(test)] + fn label(&self) -> &'static str { + "marker" + } + + fn emit<'a>( + &self, + ctx: &mut LatticeSurgeryCtx<'a>, + _round: &'a PhysicalRound, + ) -> Result<(), ExperimentError> { + ctx.out.push_str("# plugged-in\n"); + Ok(()) + } + } + + #[test] + fn added_round_emitter_writes_through_the_trait() { + let expanded = workload(); + let round = PhysicalRound::bare(RoundKind::Construct, LogicalQubitId(0)); + let mut ctx = LatticeSurgeryCtx::new( + &expanded, + &expanded.blocks[0], + &expanded.blocks[1], + &expanded.blocks[2], + String::new(), + ); + MarkerEmit.emit(&mut ctx, &round).expect("custom emitter"); + assert_eq!(ctx.out, "# plugged-in\n"); + assert!(ctx.measure_logical_rounds.is_empty()); + } +} diff --git a/quon_qec/testdata/stim/lattice_surgery_cx_d3.stim b/quon_qec/testdata/stim/lattice_surgery_cx_d3.stim new file mode 100644 index 0000000..aac7f58 --- /dev/null +++ b/quon_qec/testdata/stim/lattice_surgery_cx_d3.stim @@ -0,0 +1,132 @@ +# Quon QEC experiment — lattice-surgery CX structure (no noise; ADR-0019/0024) +# family=surface distance=3 blocks=3 (L-shaped: control|ancilla / target) +# Merge/ancilla outcomes → OBSERVABLE_INCLUDE via frame; not bare DETECTORs. +# Stim merges use logical MPP (Horsman); NA schedules geometric seam CXs. +# Note: simplified merge/split model; not Stim FT-distance claim. +QUBIT_COORDS(1, 1) 0 +QUBIT_COORDS(3, 1) 1 +QUBIT_COORDS(5, 1) 2 +QUBIT_COORDS(1, 3) 3 +QUBIT_COORDS(3, 3) 4 +QUBIT_COORDS(5, 3) 5 +QUBIT_COORDS(1, 5) 6 +QUBIT_COORDS(3, 5) 7 +QUBIT_COORDS(5, 5) 8 +QUBIT_COORDS(2, 0) 9 +QUBIT_COORDS(2, 2) 10 +QUBIT_COORDS(4, 2) 11 +QUBIT_COORDS(6, 2) 12 +QUBIT_COORDS(0, 4) 13 +QUBIT_COORDS(2, 4) 14 +QUBIT_COORDS(4, 4) 15 +QUBIT_COORDS(4, 6) 16 +QUBIT_COORDS(15, 15) 17 +QUBIT_COORDS(17, 15) 18 +QUBIT_COORDS(19, 15) 19 +QUBIT_COORDS(15, 17) 20 +QUBIT_COORDS(17, 17) 21 +QUBIT_COORDS(19, 17) 22 +QUBIT_COORDS(15, 19) 23 +QUBIT_COORDS(17, 19) 24 +QUBIT_COORDS(19, 19) 25 +QUBIT_COORDS(16, 14) 26 +QUBIT_COORDS(16, 16) 27 +QUBIT_COORDS(18, 16) 28 +QUBIT_COORDS(20, 16) 29 +QUBIT_COORDS(14, 18) 30 +QUBIT_COORDS(16, 18) 31 +QUBIT_COORDS(18, 18) 32 +QUBIT_COORDS(18, 20) 33 +QUBIT_COORDS(15, 1) 34 +QUBIT_COORDS(17, 1) 35 +QUBIT_COORDS(19, 1) 36 +QUBIT_COORDS(15, 3) 37 +QUBIT_COORDS(17, 3) 38 +QUBIT_COORDS(19, 3) 39 +QUBIT_COORDS(15, 5) 40 +QUBIT_COORDS(17, 5) 41 +QUBIT_COORDS(19, 5) 42 +QUBIT_COORDS(16, 0) 43 +QUBIT_COORDS(16, 2) 44 +QUBIT_COORDS(18, 2) 45 +QUBIT_COORDS(20, 2) 46 +QUBIT_COORDS(14, 4) 47 +QUBIT_COORDS(16, 4) 48 +QUBIT_COORDS(18, 4) 49 +QUBIT_COORDS(18, 6) 50 +QUBIT_COORDS(7, 1) 51 +QUBIT_COORDS(7, 3) 52 +QUBIT_COORDS(7, 5) 53 +QUBIT_COORDS(15, 7) 54 +QUBIT_COORDS(17, 7) 55 +QUBIT_COORDS(19, 7) 56 +R 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 +TICK +CX 0 10 2 12 3 13 4 15 +TICK +CX 1 10 5 12 6 13 7 15 +TICK +CX 3 10 5 15 +TICK +CX 4 10 8 15 +TICK +H 9 11 14 16 +TICK +CX 9 0 11 1 14 3 16 7 +TICK +CX 9 1 11 2 14 4 16 8 +TICK +CX 11 4 14 6 +TICK +CX 11 5 14 7 +TICK +H 9 11 14 16 +TICK +MR 9 10 11 12 13 14 15 16 +DETECTOR(0, 0) rec[-7] +DETECTOR(1, 0) rec[-5] +DETECTOR(2, 0) rec[-4] +DETECTOR(3, 0) rec[-2] +TICK +CX 17 27 19 29 20 30 21 32 +TICK +CX 18 27 22 29 23 30 24 32 +TICK +CX 20 27 22 32 +TICK +CX 21 27 25 32 +TICK +H 26 28 31 33 +TICK +CX 26 17 28 18 31 20 33 24 +TICK +CX 26 18 28 19 31 21 33 25 +TICK +CX 28 21 31 23 +TICK +CX 28 22 31 24 +TICK +H 26 28 31 33 +TICK +MR 26 27 28 29 30 31 32 33 +DETECTOR(4, 0) rec[-7] +DETECTOR(5, 0) rec[-5] +DETECTOR(6, 0) rec[-4] +DETECTOR(7, 0) rec[-2] +TICK +H 34 35 36 37 38 39 40 41 42 +TICK +MPP Z0*Z1*Z2*Z34*Z35*Z36 +TICK +# split_restore (NA-scheduled; omitted from Stim MPP path) +MPP X34*X37*X40*X17*X20*X23 +TICK +# split_restore (NA-scheduled; omitted from Stim MPP path) +MPP Z34*Z35*Z36 +TICK +MZ 0 1 2 3 4 5 6 7 8 +OBSERVABLE_INCLUDE(0) rec[-9] rec[-8] rec[-7] +MZ 17 18 19 20 21 22 23 24 25 +OBSERVABLE_INCLUDE(1) rec[-9] rec[-8] rec[-7] rec[-21] rec[-19] +# frame_z source=smooth_merge rec[-20] +# frame_z source=ancilla_mz rec[-19] diff --git a/quon_qec/testdata/stim/repetition_d3_z.stim b/quon_qec/testdata/stim/repetition_d3_z.stim new file mode 100644 index 0000000..0a99f35 --- /dev/null +++ b/quon_qec/testdata/stim/repetition_d3_z.stim @@ -0,0 +1,30 @@ +# Quon QEC experiment — structure only (no noise; ADR-0024) +# family=repetition distance=3 memory_rounds=2 measure_basis=z +# Note: surface uses serial Z-then-X expand (not Stim 4-layer FT schedule). +QUBIT_COORDS(0, 0) 0 +QUBIT_COORDS(1, 0) 1 +QUBIT_COORDS(2, 0) 2 +QUBIT_COORDS(3, 0) 3 +QUBIT_COORDS(4, 0) 4 +R 0 1 2 3 4 +TICK +CX 0 1 2 3 +TICK +CX 2 1 4 3 +TICK +MR 1 3 +DETECTOR(0, 0) rec[-2] +DETECTOR(1, 0) rec[-1] +TICK +CX 0 1 2 3 +TICK +CX 2 1 4 3 +TICK +MR 1 3 +DETECTOR(0, 1) rec[-2] rec[-4] +DETECTOR(1, 1) rec[-1] rec[-3] +TICK +MZ 0 2 4 +DETECTOR(0, 2) rec[-5] rec[-3] rec[-2] +DETECTOR(1, 2) rec[-4] rec[-2] rec[-1] +OBSERVABLE_INCLUDE(0) rec[-3] rec[-2] rec[-1] diff --git a/quon_qec/testdata/stim/surface_d3_x.stim b/quon_qec/testdata/stim/surface_d3_x.stim new file mode 100644 index 0000000..f3d0f55 --- /dev/null +++ b/quon_qec/testdata/stim/surface_d3_x.stim @@ -0,0 +1,86 @@ +# Quon QEC experiment — structure only (no noise; ADR-0024) +# family=surface distance=3 memory_rounds=2 measure_basis=x +# Note: surface uses serial Z-then-X expand (not Stim 4-layer FT schedule). +QUBIT_COORDS(1, 1) 0 +QUBIT_COORDS(3, 1) 1 +QUBIT_COORDS(5, 1) 2 +QUBIT_COORDS(1, 3) 3 +QUBIT_COORDS(3, 3) 4 +QUBIT_COORDS(5, 3) 5 +QUBIT_COORDS(1, 5) 6 +QUBIT_COORDS(3, 5) 7 +QUBIT_COORDS(5, 5) 8 +QUBIT_COORDS(2, 0) 9 +QUBIT_COORDS(2, 2) 10 +QUBIT_COORDS(4, 2) 11 +QUBIT_COORDS(6, 2) 12 +QUBIT_COORDS(0, 4) 13 +QUBIT_COORDS(2, 4) 14 +QUBIT_COORDS(4, 4) 15 +QUBIT_COORDS(4, 6) 16 +R 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +TICK +H 0 1 2 3 4 5 6 7 8 +TICK +CX 0 10 2 12 3 13 4 15 +TICK +CX 1 10 5 12 6 13 7 15 +TICK +CX 3 10 5 15 +TICK +CX 4 10 8 15 +TICK +H 9 11 14 16 +TICK +CX 9 0 11 1 14 3 16 7 +TICK +CX 9 1 11 2 14 4 16 8 +TICK +CX 11 4 14 6 +TICK +CX 11 5 14 7 +TICK +H 9 11 14 16 +TICK +MR 9 10 11 12 13 14 15 16 +DETECTOR(0, 0) rec[-8] +DETECTOR(2, 0) rec[-6] +DETECTOR(5, 0) rec[-3] +DETECTOR(7, 0) rec[-1] +TICK +CX 0 10 2 12 3 13 4 15 +TICK +CX 1 10 5 12 6 13 7 15 +TICK +CX 3 10 5 15 +TICK +CX 4 10 8 15 +TICK +H 9 11 14 16 +TICK +CX 9 0 11 1 14 3 16 7 +TICK +CX 9 1 11 2 14 4 16 8 +TICK +CX 11 4 14 6 +TICK +CX 11 5 14 7 +TICK +H 9 11 14 16 +TICK +MR 9 10 11 12 13 14 15 16 +DETECTOR(0, 1) rec[-8] rec[-16] +DETECTOR(1, 1) rec[-7] rec[-15] +DETECTOR(2, 1) rec[-6] rec[-14] +DETECTOR(3, 1) rec[-5] rec[-13] +DETECTOR(4, 1) rec[-4] rec[-12] +DETECTOR(5, 1) rec[-3] rec[-11] +DETECTOR(6, 1) rec[-2] rec[-10] +DETECTOR(7, 1) rec[-1] rec[-9] +TICK +MX 0 1 2 3 4 5 6 7 8 +DETECTOR(0, 2) rec[-17] rec[-9] rec[-8] +DETECTOR(2, 2) rec[-15] rec[-8] rec[-7] rec[-5] rec[-4] +DETECTOR(5, 2) rec[-12] rec[-6] rec[-5] rec[-3] rec[-2] +DETECTOR(7, 2) rec[-10] rec[-2] rec[-1] +OBSERVABLE_INCLUDE(0) rec[-9] rec[-6] rec[-3] diff --git a/quon_qec/testdata/stim/surface_d3_z.stim b/quon_qec/testdata/stim/surface_d3_z.stim new file mode 100644 index 0000000..eaf8ffb --- /dev/null +++ b/quon_qec/testdata/stim/surface_d3_z.stim @@ -0,0 +1,84 @@ +# Quon QEC experiment — structure only (no noise; ADR-0024) +# family=surface distance=3 memory_rounds=2 measure_basis=z +# Note: surface uses serial Z-then-X expand (not Stim 4-layer FT schedule). +QUBIT_COORDS(1, 1) 0 +QUBIT_COORDS(3, 1) 1 +QUBIT_COORDS(5, 1) 2 +QUBIT_COORDS(1, 3) 3 +QUBIT_COORDS(3, 3) 4 +QUBIT_COORDS(5, 3) 5 +QUBIT_COORDS(1, 5) 6 +QUBIT_COORDS(3, 5) 7 +QUBIT_COORDS(5, 5) 8 +QUBIT_COORDS(2, 0) 9 +QUBIT_COORDS(2, 2) 10 +QUBIT_COORDS(4, 2) 11 +QUBIT_COORDS(6, 2) 12 +QUBIT_COORDS(0, 4) 13 +QUBIT_COORDS(2, 4) 14 +QUBIT_COORDS(4, 4) 15 +QUBIT_COORDS(4, 6) 16 +R 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 +TICK +CX 0 10 2 12 3 13 4 15 +TICK +CX 1 10 5 12 6 13 7 15 +TICK +CX 3 10 5 15 +TICK +CX 4 10 8 15 +TICK +H 9 11 14 16 +TICK +CX 9 0 11 1 14 3 16 7 +TICK +CX 9 1 11 2 14 4 16 8 +TICK +CX 11 4 14 6 +TICK +CX 11 5 14 7 +TICK +H 9 11 14 16 +TICK +MR 9 10 11 12 13 14 15 16 +DETECTOR(1, 0) rec[-7] +DETECTOR(3, 0) rec[-5] +DETECTOR(4, 0) rec[-4] +DETECTOR(6, 0) rec[-2] +TICK +CX 0 10 2 12 3 13 4 15 +TICK +CX 1 10 5 12 6 13 7 15 +TICK +CX 3 10 5 15 +TICK +CX 4 10 8 15 +TICK +H 9 11 14 16 +TICK +CX 9 0 11 1 14 3 16 7 +TICK +CX 9 1 11 2 14 4 16 8 +TICK +CX 11 4 14 6 +TICK +CX 11 5 14 7 +TICK +H 9 11 14 16 +TICK +MR 9 10 11 12 13 14 15 16 +DETECTOR(0, 1) rec[-8] rec[-16] +DETECTOR(1, 1) rec[-7] rec[-15] +DETECTOR(2, 1) rec[-6] rec[-14] +DETECTOR(3, 1) rec[-5] rec[-13] +DETECTOR(4, 1) rec[-4] rec[-12] +DETECTOR(5, 1) rec[-3] rec[-11] +DETECTOR(6, 1) rec[-2] rec[-10] +DETECTOR(7, 1) rec[-1] rec[-9] +TICK +MZ 0 1 2 3 4 5 6 7 8 +DETECTOR(1, 2) rec[-16] rec[-9] rec[-8] rec[-6] rec[-5] +DETECTOR(3, 2) rec[-14] rec[-7] rec[-4] +DETECTOR(4, 2) rec[-13] rec[-6] rec[-3] +DETECTOR(6, 2) rec[-11] rec[-5] rec[-4] rec[-2] rec[-1] +OBSERVABLE_INCLUDE(0) rec[-9] rec[-8] rec[-7]