From b3d657293206bdb535084504b424fffc13841385 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Thu, 1 Oct 2026 13:40:07 +0000 Subject: [PATCH] [qec] Share Stim measure-line emission in the emitter module Both builders call stim_emit::emit_measure_line. Single-block memory still chooses MZ or MX. Lattice surgery still passes MZ and keeps its record count. Closing detectors stay in the builder. Checked-in Stim gold stays byte-identical. Refs #473 Co-authored-by: Arnab Ghosh --- quon_qec/src/experiment.rs | 26 +++++++------------ quon_qec/src/experiment/stim_emit.rs | 39 +++++++++++++++++++++++----- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/quon_qec/src/experiment.rs b/quon_qec/src/experiment.rs index 255e6e0..a8d7425 100644 --- a/quon_qec/src/experiment.rs +++ b/quon_qec/src/experiment.rs @@ -555,15 +555,15 @@ fn emit_stim_single_block_memory(expanded: &ExpandedWorkload) -> Result "MZ", LogicalBasis::X => "MX", }; - out.push_str(measure_op); - for atom in &data_atoms { - out.push_str(&format!(" {}", atom.0)); - } - out.push('\n'); + stim_emit::emit_measure_line(&mut out, measure_op, &data_ids); // Close detectors in the measure basis against final data measurements. let closing_indices: Vec = match measure_basis { @@ -591,10 +591,6 @@ fn emit_stim_single_block_memory(expanded: &ExpandedWorkload) -> Result Result Result\n` in caller order. +/// +/// Single-block memory passes `MZ` or `MX`. Lattice surgery passes `MZ` and +/// keeps its own record counter. This function does not reorder or skip an +/// empty id list: an empty list is `{op}\n`. +pub(crate) fn emit_measure_line(out: &mut String, op: &str, atom_ids: &[u32]) { + out.push_str(op); + for id in atom_ids { + out.push_str(&format!(" {id}")); + } + out.push('\n'); +} + /// Write `OBSERVABLE_INCLUDE({obs_id})` and one `rec[-(d - pos)]` per /// observable atom. /// @@ -150,10 +164,11 @@ impl<'a> LatticeSurgeryCtx<'a> { /// Stim state for one single-block memory circuit. /// -/// The builder still writes the final measure line and closing detectors. -/// [`emit_single_block_header`] writes the comment preamble before this -/// context exists. Round impls append construct locals, memory rounds, and -/// the measure-logical record. [`emit_observable_include`] writes the observable. +/// The builder still writes closing detectors. [`emit_single_block_header`] +/// writes the comment preamble before this context exists. Round impls append +/// construct locals, memory rounds, and the measure-logical record. +/// [`emit_measure_line`] writes the final measure line. +/// [`emit_observable_include`] writes the observable. pub(crate) struct SingleBlockCtx<'a> { pub(crate) out: String, pub(crate) n_checks: usize, @@ -805,6 +820,16 @@ mod tests { ); } + #[test] + fn measure_line_keeps_caller_order() { + let mut out = String::new(); + emit_measure_line(&mut out, "MZ", &[4, 0, 4]); + assert_eq!(out, "MZ 4 0 4\n"); + let mut empty = String::new(); + emit_measure_line(&mut empty, "MX", &[]); + assert_eq!(empty, "MX\n"); + } + #[test] fn qubit_coords_keep_caller_order() { let mut out = String::new();