Skip to content

Crumble: marker placed in same layer as a 2-qubit gate gets visually attributed to that gate's crossing #1076

Description

@yzcj105

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions