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

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions