From 2dbbd147fb97ceae851643663dd870aeb06773a9 Mon Sep 17 00:00:00 2001 From: Frederik Wilde Date: Wed, 8 Jul 2026 14:18:03 +0200 Subject: [PATCH 1/2] Handle Measure and Reset separately when processing markers and operators. --- glue/crumble/circuit/layer.js | 61 ++++++++++++++++++++++++++++++----- 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/glue/crumble/circuit/layer.js b/glue/crumble/circuit/layer.js index 256e48f3f..37e7f1b9d 100644 --- a/glue/crumble/circuit/layer.js +++ b/glue/crumble/circuit/layer.js @@ -2,6 +2,15 @@ import {Operation} from "./operation.js" import {GATE_MAP} from "../gates/gateset.js"; import {groupBy} from "../base/seq.js"; +/** + * @type {!Map} + */ +const DEMOLITION_MEASURE_BASIS = new Map([ + ['MR', 'Z'], + ['MRX', 'X'], + ['MRY', 'Y'], +]); + class Layer { constructor() { this.id_ops = /** @type {!Map} */ new Map(); @@ -220,6 +229,16 @@ class Layer { let after = new Map(); let handled = new Set(); + // Record qubits involved in demolition measurements (measure + reset) + let demolitionResetQubits = new Set(); + for (let op of this.id_ops.values()) { + if (DEMOLITION_MEASURE_BASIS.has(op.gate.name)) { + for (let q of op.id_targets) { + demolitionResetQubits.add(q); + } + } + } + for (let k of before.keys()) { let v = before.get(k); let op = this.id_ops.get(k); @@ -237,14 +256,22 @@ class Layer { } b += r; } - let a = op.pauliFrameAfter(b); - let hasErr = a.startsWith('ERR:'); - for (let qi = 0; qi < op.id_targets.length; qi++) { - let q = op.id_targets[qi]; - if (hasErr) { - after.set(q, 'ERR:' + a[4 + qi]); - } else { - after.set(q, a[qi]); + let demolitionBasis = DEMOLITION_MEASURE_BASIS.get(op.gate.name); + if (demolitionBasis !== undefined) { + // Only measure now. Defer reset until after markers are handled + let q = op.id_targets[0]; + let c = b[0]; + after.set(q, (c === 'I' || c === demolitionBasis) ? c : 'ERR:' + c); + } else { + let a = op.pauliFrameAfter(b); + let hasErr = a.startsWith('ERR:'); + for (let qi = 0; qi < op.id_targets.length; qi++) { + let q = op.id_targets[qi]; + if (hasErr) { + after.set(q, 'ERR:' + a[4 + qi]); + } else { + after.set(q, a[qi]); + } } } } else { @@ -252,6 +279,12 @@ class Layer { } } + // Snapshot operators before reset to distinguish incoming from outgoing markers later + let demolitionIncoming = new Map(); + for (let q of demolitionResetQubits) { + demolitionIncoming.set(q, after.get(q)); + } + for (let op of this.markers) { if (op.gate.name === 'MARKX' && op.args[0] === marker_index) { let key = op.id_targets[0]; @@ -295,6 +328,18 @@ class Layer { } } + // Apply deferred resets + for (let q of demolitionResetQubits) { + let incoming = demolitionIncoming.get(q); + if (incoming === undefined || incoming === 'I') { + // Nothing at reset: a marker here denotes an outgoing operator + continue; + } + // Operator at reset: clean if a marker captured it (now I), else lost (ERR) + let post = after.get(q); + after.set(q, (post === undefined || post === 'I') ? 'I' : 'ERR:I'); + } + return after; } From 67f7545d1abf705a234d5e6f56b2817164de1509 Mon Sep 17 00:00:00 2001 From: Frederik Wilde Date: Wed, 8 Jul 2026 14:18:39 +0200 Subject: [PATCH 2/2] Add tests --- .../circuit/propagated_pauli_frames.test.js | 151 ++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/glue/crumble/circuit/propagated_pauli_frames.test.js b/glue/crumble/circuit/propagated_pauli_frames.test.js index 769c37e85..87a2658a1 100644 --- a/glue/crumble/circuit/propagated_pauli_frames.test.js +++ b/glue/crumble/circuit/propagated_pauli_frames.test.js @@ -125,3 +125,154 @@ test("propagated_pauli_frames.fromMeasurements", () => { )], ]))); }); + +test("propagated_pauli_frames.fromCircuit_demolitionMeasurementMarkers", () => { + // Operator processing for demolition measurements (MR/MRX/MRY) + + // Uncaptured: the Z reaches the MR's reset and is reported as lost at that layer. + let propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + R 0 + MARKZ(0) 0 + TICK + MR 0 + TICK + M 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + [1, new PropagatedPauliFrameLayer( + new Map(), + new Set([0]), + [], + )], + ]))); + + // Captured: a co-located MARKZ (measured basis) captures the incoming Z, so it ends cleanly. + propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + R 0 + MARKZ(0) 0 + TICK + MR 0 + MARKZ(0) 0 + TICK + M 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + ]))); + + // Outgoing: with nothing flowing in, a MARKZ at the MR introduces a Z on the reset's output, + // which propagates forward (here it passes through the following M). + propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + MR 0 + MARKZ(0) 0 + TICK + M 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + [1.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + ]))); + + // The measured basis is per gate: MRX captures an X. + propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + RX 0 + MARKX(0) 0 + TICK + MRX 0 + MARKX(0) 0 + TICK + MX 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X']]), + new Set(), + [], + )], + ]))); + + // MRY reports an uncaptured Y. + propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + RY 0 + MARKY(0) 0 + TICK + MRY 0 + TICK + MY 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Y']]), + new Set(), + [], + )], + [1, new PropagatedPauliFrameLayer( + new Map(), + new Set([0]), + [], + )], + ]))); + + // A marker in the wrong basis does not capture the incoming operator: it is still reported. + propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + R 0 + MARKZ(0) 0 + TICK + MR 0 + MARKX(0) 0 + TICK + M 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + [1, new PropagatedPauliFrameLayer( + new Map(), + new Set([0]), + [], + )], + ]))); + + // An operator that anticommutes with the measured basis (X into an MR) cannot be captured and + // is reported at the measurement. + propagated = PropagatedPauliFrames.fromCircuit(Circuit.fromStimCircuit(` + RX 0 + MARKX(0) 0 + TICK + MR 0 + TICK + M 0 + `), 0); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X']]), + new Set(), + [], + )], + [1, new PropagatedPauliFrameLayer( + new Map(), + new Set([0]), + [], + )], + ]))); +});