Summary
When a MARK<basis>(k) q instruction shares a layer with a 2-qubit gate (e.g. CX, CZ), the marker's basis change on qubit q is rendered as a colored bond on the 2-qubit gate, even though the marker was placed independently and the gate did not propagate any Pauli through q. This makes the crossing color misleading as a visualization of Pauli propagation.
Reproduction
Bug case — empty slot, marker drops X on q1 in same layer as CX_0_1
URL: https://algassert.com/crumble#circuit=Q(0,0)0;Q(1,0)1;TICK;R_0_1;TICK;CX_0_1;MARKX(0)1;TICK;M_0_1
Q(0,0) 0
Q(1,0) 1
R 0 1
TICK
CX 0 1
MARKX(0) 1 # marker placed in SAME layer as CX
TICK
M 0 1
Slot 0 starts empty going into the CX layer. id_pauliFrameAfter applies the CX first (no-op, since before.keys() is empty for slot 0) then applies the marker, ending the layer with slot 0 = {1: X}.
Expected (intuition for "crossing = Pauli propagation through gate"): no crossing on the CX bond, because CX did not propagate anything; only a marker indicator on q1.
Actual: CX bond is rendered with strokeStyle = 'red' (X-colored crossing). This implies CX propagated X to q1, but it did not.
Comparison — marker placed in its own layer (no spurious crossing)
URL: https://algassert.com/crumble#circuit=Q(0,0)0;Q(1,0)1;TICK;R_0_1;TICK;CX_0_1;TICK;MARKX(0)1;TICK;M_0_1
Same circuit but with an extra TICK between CX 0 1 and MARKX(0) 1. CX bond renders uncolored (correct: nothing propagated through it).
Root cause
The crossings computation in glue/crumble/circuit/propagated_pauli_frames.js (around lines 130–170 on the current main branch):
```js
let prevBases = bases;
bases = layer.id_pauliFrameAfter(bases, marker_index);
// ...
for (let op of layer.iter_gates_and_markers()) {
if (op.gate.num_qubits === 2 && !op.gate.is_marker) {
let [q1, q2] = op.id_targets;
let differences = new Set();
for (let t of op.id_targets) {
let b1 = bases.get(t); // post-layer (includes markers)
let b2 = prevBases.get(t); // pre-layer
if (b1 !== b2) {
if (b1 !== undefined) differences.add(b1);
if (b2 !== undefined) differences.add(b2);
}
}
// ... color assignment from differences
}
}
```
The bases here is the layer-end state, which id_pauliFrameAfter produces by applying gates first, then markers (see glue/crumble/circuit/layer.js):
```js
id_pauliFrameAfter(before, marker_index) {
let after = new Map();
// ... apply gates from before.keys() ...
for (let op of this.markers) {
// ... apply MARK AFTER all gates ...
}
return after;
}
```
So bases legitimately contains marker-induced changes, but the crossings code treats every diff between prevBases and bases on a gate's target qubit as if the gate caused it. Same-layer MARK<basis> placements therefore get attributed to a co-located 2-qubit gate.
Why this is misleading
Users learning Pauli propagation read the crossing color as "this gate propagated this Pauli basis through these qubits". When MARK shares a layer with a CX, that inference is wrong:
- In the repro above, the rendered red bond on CX_0_1 reads as "X propagated to q1 via CX", but the X came from
MARKX(0) 1, not CX.
- The same misattribution applies to any same-layer combination of
MARK<basis> with CX/CZ/XCX/XCY/etc.
This does not affect:
- Stim simulation (
MARK<basis> is #!pragma-wrapped and ignored by the simulator).
- Pauli frame propagation results visible on qubit support lines.
- Detector / observable computations.
It only affects Crumble's bond-color visualization on 2-qubit gates.
Workaround for current users
Put MARK<basis> in its own layer (separate TICK boundaries from any 2-qubit gate):
TICK
CX 0 1
TICK
MARKX(0) 1 # dedicated layer
TICK
Environment
- Crumble on
algassert.com/crumble as observed 2026-06-20
- Also reproducible in self-hosted
glue/crumble/crumble.html from the current main branch
- Affects all marker basis variants (
MARKX, MARKY, MARKZ) and all 2-qubit gates with is_marker === false.
Co-authored by Claude Code.
Summary
When a
MARK<basis>(k) qinstruction shares a layer with a 2-qubit gate (e.g.CX,CZ), the marker's basis change on qubitqis rendered as a colored bond on the 2-qubit gate, even though the marker was placed independently and the gate did not propagate any Pauli throughq. This makes the crossing color misleading as a visualization of Pauli propagation.Reproduction
Bug case — empty slot, marker drops X on q1 in same layer as CX_0_1
URL: https://algassert.com/crumble#circuit=Q(0,0)0;Q(1,0)1;TICK;R_0_1;TICK;CX_0_1;MARKX(0)1;TICK;M_0_1
Slot 0 starts empty going into the CX layer.
id_pauliFrameAfterapplies the CX first (no-op, sincebefore.keys()is empty for slot 0) then applies the marker, ending the layer withslot 0 = {1: X}.Expected (intuition for "crossing = Pauli propagation through gate"): no crossing on the CX bond, because CX did not propagate anything; only a marker indicator on q1.
Actual: CX bond is rendered with
strokeStyle = 'red'(X-colored crossing). This implies CX propagated X to q1, but it did not.Comparison — marker placed in its own layer (no spurious crossing)
URL: https://algassert.com/crumble#circuit=Q(0,0)0;Q(1,0)1;TICK;R_0_1;TICK;CX_0_1;TICK;MARKX(0)1;TICK;M_0_1
Same circuit but with an extra
TICKbetweenCX 0 1andMARKX(0) 1. CX bond renders uncolored (correct: nothing propagated through it).Root cause
The crossings computation in
glue/crumble/circuit/propagated_pauli_frames.js(around lines 130–170 on the currentmainbranch):```js
let prevBases = bases;
bases = layer.id_pauliFrameAfter(bases, marker_index);
// ...
for (let op of layer.iter_gates_and_markers()) {
if (op.gate.num_qubits === 2 && !op.gate.is_marker) {
let [q1, q2] = op.id_targets;
let differences = new Set();
for (let t of op.id_targets) {
let b1 = bases.get(t); // post-layer (includes markers)
let b2 = prevBases.get(t); // pre-layer
if (b1 !== b2) {
if (b1 !== undefined) differences.add(b1);
if (b2 !== undefined) differences.add(b2);
}
}
// ... color assignment from differences
}
}
```
The
baseshere is the layer-end state, whichid_pauliFrameAfterproduces by applying gates first, then markers (seeglue/crumble/circuit/layer.js):```js
id_pauliFrameAfter(before, marker_index) {
let after = new Map();
// ... apply gates from before.keys() ...
for (let op of this.markers) {
// ... apply MARK AFTER all gates ...
}
return after;
}
```
So
baseslegitimately contains marker-induced changes, but the crossings code treats every diff betweenprevBasesandbaseson a gate's target qubit as if the gate caused it. Same-layerMARK<basis>placements therefore get attributed to a co-located 2-qubit gate.Why this is misleading
Users learning Pauli propagation read the crossing color as "this gate propagated this Pauli basis through these qubits". When MARK shares a layer with a CX, that inference is wrong:
MARKX(0) 1, not CX.MARK<basis>withCX/CZ/XCX/XCY/etc.This does not affect:
MARK<basis>is#!pragma-wrapped and ignored by the simulator).It only affects Crumble's bond-color visualization on 2-qubit gates.
Workaround for current users
Put
MARK<basis>in its own layer (separateTICKboundaries from any 2-qubit gate):Environment
algassert.com/crumbleas observed 2026-06-20glue/crumble/crumble.htmlfrom the currentmainbranchMARKX,MARKY,MARKZ) and all 2-qubit gates withis_marker === false.Co-authored by Claude Code.