Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 9 additions & 17 deletions quon_qec/src/experiment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -555,15 +555,15 @@ fn emit_stim_single_block_memory(expanded: &ExpandedWorkload) -> Result<String,
block.data_atoms.clone()
};

let mut data_ids = Vec::with_capacity(data_atoms.len());
for atom in &data_atoms {
data_ids.push(atom.0);
}
let measure_op = match measure_basis {
LogicalBasis::Z => "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<usize> = match measure_basis {
Expand Down Expand Up @@ -591,10 +591,6 @@ fn emit_stim_single_block_memory(expanded: &ExpandedWorkload) -> Result<String,
}

let obs_atoms = logical_observable_atoms(block, measure_basis);
let mut data_ids = Vec::with_capacity(data_atoms.len());
for atom in &data_atoms {
data_ids.push(atom.0);
}
stim_emit::emit_observable_include(&mut out, 0, &data_ids, &obs_atoms)?;
out.push('\n');

Expand Down Expand Up @@ -659,8 +655,8 @@ fn emit_stim_lattice_surgery_cx(expanded: &ExpandedWorkload) -> Result<String, E

// Round kinds append through [`stim_emit::StimRoundEmitter`]. Byproduct
// handles and deferred measure-logical rounds come back out so the
// builder can write measure lines and frame records around
// [`stim_emit::emit_observable_include`].
// builder can count records and append frame targets around
// [`stim_emit::emit_measure_line`] and [`stim_emit::emit_observable_include`].
let mut ctx = stim_emit::LatticeSurgeryCtx::new(expanded, control, target, ancilla, out);
for round in &expanded.rounds {
stim_emit::lattice_round_emitter(round.kind).emit(&mut ctx, round)?;
Expand All @@ -687,12 +683,8 @@ fn emit_stim_lattice_surgery_cx(expanded: &ExpandedWorkload) -> Result<String, E
if data_atoms.is_empty() {
continue;
}
out.push_str("MZ");
for id in &data_atoms {
out.push_str(&format!(" {id}"));
rec_count += 1;
}
out.push('\n');
stim_emit::emit_measure_line(&mut out, "MZ", &data_atoms);
rec_count += data_atoms.len() as i32;
let block = expanded
.blocks
.iter()
Expand Down
39 changes: 32 additions & 7 deletions quon_qec/src/experiment/stim_emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@
//! kind is a new impl plus one arm in [`lattice_round_emitter`].
//!
//! Both builders share [`emit_reset_tick`] for the prepare-`R` / `TICK` line,
//! [`emit_qubit_coords`] for `QUBIT_COORDS`, [`emit_single_block_header`] /
//! [`emit_qubit_coords`] for `QUBIT_COORDS`, [`emit_measure_line`] for the
//! final `MZ` / `MX` line, [`emit_single_block_header`] /
//! [`emit_lattice_surgery_header`] for the comment preamble, and
//! [`emit_observable_include`] for `OBSERVABLE_INCLUDE` record offsets.
//! Measure lines and closing detectors stay in the builders. Lattice surgery
//! still appends frame-byproduct records after the shared observable line.
//! Closing detectors stay in the single-block builder. Lattice surgery still
//! appends frame-byproduct records after the shared observable line.

use std::collections::HashMap;

Expand All @@ -38,6 +39,19 @@ pub(crate) fn emit_reset_tick(out: &mut String, atom_ids: &[u32]) {
out.push_str("\nTICK\n");
}

/// Write `{op} <ids>\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]) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve. I would merge this slice on b3d6572.

Both builders call stim_emit::emit_measure_line for {op} id id\n. The helper keeps caller order and does not skip an empty list. Single-block memory still chooses MZ or MX from the measure basis, then passes that op. Lattice surgery still passes MZ. Its record counter stays in the builder: after the line, rec_count increases by data_atoms.len(), the same total as the old per-id increments. An empty lattice window still continues before the call.

Closing detectors stay in the single-block builder, after this line. The checked-in Stim gold is not in the diff. The new function has no unwrap or expect. measure_line_keeps_caller_order locks MZ 4 0 4 and an empty MX line.

Title, body, and the commit mention the issue only as Refs #473. They do not use fixes/closes/resolves, and they do not say the issue stays open. This slice does not move closing detectors.

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.
///
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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();
Expand Down
Loading