From 4146f9d80ac32f857aadad5b8dab1de6026b2f77 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 24 May 2024 02:13:36 -0700 Subject: [PATCH] [WIP] Add UX for creating detectors in crumble --- glue/crumble/base/revision.js | 4 +- glue/crumble/circuit/circuit.js | 149 +++++++++++++++-- glue/crumble/circuit/circuit.test.js | 28 ++++ glue/crumble/circuit/layer.js | 13 ++ glue/crumble/circuit/operation.js | 16 ++ glue/crumble/circuit/pauli_frame.js | 78 ++++++--- glue/crumble/circuit/pauli_frame.test.js | 12 +- .../circuit/propagated_pauli_frames.js | 151 +++++++++++++++++- .../circuit/propagated_pauli_frames.test.js | 127 +++++++++++++++ glue/crumble/draw/main_draw.js | 53 +++--- glue/crumble/draw/timeline_viewer.js | 36 +++-- glue/crumble/editor/editor_state.js | 23 ++- glue/crumble/editor/sync_url_to_state.js | 7 - glue/crumble/gates/gate.js | 4 + glue/crumble/gates/gateset.js | 1 - .../gates/gateset_controlled_paulis.js | 6 + .../gates/gateset_demolition_measurements.js | 3 + glue/crumble/gates/gateset_hadamard_likes.js | 3 + glue/crumble/gates/gateset_markers.js | 23 ++- glue/crumble/gates/gateset_mpp.js | 2 + .../gates/gateset_pair_measurements.js | 3 + glue/crumble/gates/gateset_paulis.js | 4 + glue/crumble/gates/gateset_quarter_turns.js | 6 + glue/crumble/gates/gateset_resets.js | 3 + .../gates/gateset_solo_measurements.js | 3 + .../crumble/gates/gateset_sqrt_pauli_pairs.js | 6 + glue/crumble/gates/gateset_swaps.js | 5 + glue/crumble/gates/gateset_third_turns.js | 2 + glue/crumble/main.js | 26 ++- glue/crumble/test/test_import_all.js | 1 + glue/crumble/test/test_util.js | 6 +- 31 files changed, 706 insertions(+), 98 deletions(-) create mode 100644 glue/crumble/circuit/propagated_pauli_frames.test.js diff --git a/glue/crumble/base/revision.js b/glue/crumble/base/revision.js index 73929fb29..ad4cc595b 100644 --- a/glue/crumble/base/revision.js +++ b/glue/crumble/base/revision.js @@ -109,8 +109,8 @@ class Revision { * the previous state. * @returns {void} */ - startedWorkingOnCommit() { - this.isWorkingOnCommit = true; + startedWorkingOnCommit(newCheckpoint) { + this.isWorkingOnCommit = newCheckpoint !== this.history[this.index]; this._changes.send(undefined); } diff --git a/glue/crumble/circuit/circuit.js b/glue/crumble/circuit/circuit.js index 8fde43d03..1bfc2f191 100644 --- a/glue/crumble/circuit/circuit.js +++ b/glue/crumble/circuit/circuit.js @@ -190,6 +190,7 @@ class Circuit { replaceAll(' DAG ', '_DAG '). replaceAll('C ZYX', 'C_ZYX').split('\n'); let layers = [new Layer()]; + let num_detectors = 0; let i2q = new Map(); let used_positions = new Set(); @@ -233,6 +234,7 @@ class Circuit { } }; + let measurement_locs = []; let processLine = line => { let args = []; let targets = []; @@ -257,6 +259,9 @@ class Circuit { if (name === '') { return; } + if (args.length > 0 && ['M', 'MX', 'MY', 'MZ', 'MR', 'MRX', 'MRY', 'MRZ', 'MPP', 'MPAD'].indexOf(name) !== -1) { + args = []; + } let alias = GATE_ALIAS_MAP.get(name); if (alias !== undefined) { if (alias.ignore) { @@ -274,14 +279,36 @@ class Circuit { let combinedTargets = splitUncombinedTargets(targets); let layer = layers[layers.length - 1] for (let combo of combinedTargets) { + let op = simplifiedMPP(new Float32Array(args), combo); try { - layer.put(simplifiedMPP(new Float32Array(args), combo), false); + layer.put(op, false); } catch (_) { layers.push(new Layer()); layer = layers[layers.length - 1]; - layer.put(simplifiedMPP(new Float32Array(args), combo), false); + layer.put(op, false); + } + measurement_locs.push({layer: layers.length - 1, targets: op.id_targets}); + } + return; + } else if (name === 'DETECTOR') { + for (let target of targets) { + if (!target.startsWith("rec[-") || ! target.endsWith("]")) { + console.warn("Ignoring bad detector " + line); + return; + } + let index = measurement_locs.length + Number.parseInt(target.substring(4, target.length - 1)); + if (index < 0 || index >= measurement_locs.length) { + console.warn("Ignoring bad detector " + line); + return; } + let loc = measurement_locs[index]; + layers[loc.layer].markers.push( + new Operation(GATE_MAP.get('DETECTOR'), + new Float32Array([num_detectors]), + new Uint32Array([loc.targets[0]]), + )); } + num_detectors += 1; return; } else if (name === 'SPP' || name === 'SPP_DAG') { let dag = name === 'SPP_DAG'; @@ -314,26 +341,35 @@ class Circuit { return; } - let ignored = false; + let has_feedback = false; for (let targ of targets) { if (targ.startsWith("rec[")) { if (name === "CX" || name === "CY" || name === "CZ" || name === "ZCX" || name === "ZCY") { - ignored = true; - break; + has_feedback = true; } - } - if (typeof parseInt(targ) !== 'number') { + } else if (typeof parseInt(targ) !== 'number') { throw new Error(line); } } - if (ignored) { - console.warn("IGNORED", name); - return; + if (has_feedback) { + let clean_targets = []; + for (let k = 0; k < targets.length; k += 2) { + if (targets[k].startsWith("rec[") || targets[k + 1].startsWith("rec[")) { + console.warn("IGNORED", name, targets[k], targets[k + 1]); + } else { + clean_targets.push(targets[k]); + clean_targets.push(targets[k + 1]); + } + } + targets = clean_targets; + if (targets.length === 0) { + return; + } } let gate = GATE_MAP.get(name); if (gate === undefined) { - console.warn("Unrecognized gate name in " + line); + console.warn("Ignoring unrecognized instruction: " + line); return; } let a = new Float32Array(args); @@ -351,12 +387,16 @@ class Circuit { sub_targets.reverse(); } let qs = new Uint32Array(sub_targets); + let op = new Operation(gate, a, qs); try { - layer.put(new Operation(gate, a, qs), false); + layer.put(op, false); } catch (_) { layers.push(new Layer()); layer = layers[layers.length - 1]; - layer.put(new Operation(gate, a, qs), false); + layer.put(op, false); + } + if (op.countMeasurements() > 0) { + measurement_locs.push({layer: layers.length - 1, targets: op.id_targets}); } } } @@ -506,6 +546,53 @@ class Circuit { return new Circuit(newCoords, newLayers); } + /** + * @returns {!Array.>} + */ + collectDetectors() { + let detectors = []; + let m2d = new Map(); + for (let k = 0; k < this.layers.length; k++) { + let layer = this.layers[k]; + for (let [target_id, op] of layer.id_ops.entries()) { + if (op.id_targets[0] === target_id) { + if (op.countMeasurements() > 0) { + m2d.set(`${k}:${target_id}`, m2d.size); + } + } + } + } + for (let k = 0; k < this.layers.length; k++) { + let layer = this.layers[k]; + for (let op of layer.markers) { + if (op.gate.name === 'DETECTOR') { + let d = Math.round(op.args[0]); + while (detectors.length <= d) { + detectors.push([]); + } + let key = `${k}:${op.id_targets[0]}`; + if (m2d.has(key)) { + detectors[d].push(m2d.get(key) - m2d.size); + } + } + } + } + let seen = new Set(); + let keptDetectors = []; + for (let ds of detectors) { + if (ds.length > 0) { + ds.sort((a, b) => b - a); + let key = ds.join(':'); + if (!seen.has(key)) { + seen.add(key); + keptDetectors.push(ds); + } + } + } + keptDetectors.sort((a, b) => a[0] - b[0]); + return keptDetectors; + } + /** * @returns {!string} */ @@ -519,6 +606,11 @@ class Circuit { } } + let remainingDetectors = this.collectDetectors(); + remainingDetectors.reverse(); + let seenMeasurements = 0; + let totalMeasurements = this.countMeasurements(); + let packedQubitCoords = []; for (let q of usedQubits) { let x = this.qubitCoordData[2*q]; @@ -574,11 +666,15 @@ class Circuit { let targetGroups = []; let gateName = nameWithArgs.split('(')[0]; + if (gateName === 'DETECTOR') { + continue; + } let gate = GATE_MAP.get(gateName); if (gate === undefined && (gateName === 'MPP' || gateName === 'SPP' || gateName === 'SPP_DAG')) { let line = [gateName + ' ']; for (let op of group) { + seenMeasurements += op.countMeasurements(); let bases = op.gate.name.substring(gateName.length + 1); for (let k = 0; k < op.id_targets.length; k++) { line.push(bases[k] + old2new.get(op.id_targets[k])); @@ -592,11 +688,13 @@ class Circuit { if (gate !== undefined && gate.can_fuse) { let flatTargetGroups = []; for (let op of group) { + seenMeasurements += op.countMeasurements(); flatTargetGroups.push(...op.id_targets) } targetGroups.push(flatTargetGroups); } else { for (let op of group) { + seenMeasurements += op.countMeasurements(); targetGroups.push([...op.id_targets]) } } @@ -610,6 +708,20 @@ class Circuit { } } } + while (remainingDetectors.length > 0) { + let candidate = remainingDetectors[remainingDetectors.length - 1]; + let offset = totalMeasurements - seenMeasurements; + if (candidate[0] + offset >= 0) { + break; + } + remainingDetectors.pop(); + let line = ['DETECTOR']; + for (let d of candidate) { + line.push(`rec[${d + offset}]`) + } + out.push(line.join(' ')); + } + out.push(`TICK`); } while (out.length > 0 && out[out.length - 1] === 'TICK') { @@ -619,6 +731,17 @@ class Circuit { return out.join('\n'); } + /** + * @returns {!int} + */ + countMeasurements() { + let total = 0; + for (let layer of this.layers) { + total += layer.countMeasurements(); + } + return total; + } + /** * @param {!Iterable} coords */ diff --git a/glue/crumble/circuit/circuit.test.js b/glue/crumble/circuit/circuit.test.js index 9757f26d5..9b98589f6 100644 --- a/glue/crumble/circuit/circuit.test.js +++ b/glue/crumble/circuit/circuit.test.js @@ -47,6 +47,34 @@ S 1 2 `.trim()); }); +test("circuit.fromStimCircuit_strip_measurement_noise", () => { + let c1 = Circuit.fromStimCircuit(` + M(0.1) 0 + `); + assertThat(c1.toStimCircuit()).isEqualTo(` +QUBIT_COORDS(0, 0) 0 +M 0 + `.trim()); +}); + +test("circuit.fromStimCircuit_detector", () => { + let c = Circuit.fromStimCircuit(` + R 0 + M 0 + DETECTOR rec[-1] + R 0 + `); + assertThat(c.toStimCircuit()).isEqualTo(` +QUBIT_COORDS(0, 0) 0 +R 0 +TICK +M 0 +TICK +R 0 +DETECTOR rec[-1] + `.trim()); +}) + test("circuit.fromStimCircuit_mpp", () => { let c1 = Circuit.fromStimCircuit(` QUBIT_COORDS(1, 2) 0 diff --git a/glue/crumble/circuit/layer.js b/glue/crumble/circuit/layer.js index 958a449e5..7265a8fc3 100644 --- a/glue/crumble/circuit/layer.js +++ b/glue/crumble/circuit/layer.js @@ -16,6 +16,19 @@ class Layer { return result; } + /** + * @returns {!int} + */ + countMeasurements() { + let total = 0; + for (let [target_id, op] of this.id_ops.entries()) { + if (op.id_targets[0] === target_id) { + total += op.countMeasurements(); + } + } + return total; + } + /** * @return {!boolean} */ diff --git a/glue/crumble/circuit/operation.js b/glue/crumble/circuit/operation.js index c50b7693d..f1b40240b 100644 --- a/glue/crumble/circuit/operation.js +++ b/glue/crumble/circuit/operation.js @@ -40,6 +40,22 @@ class Operation { this.id_targets = targets; } + /** + * @returns {!int} + */ + countMeasurements() { + if (this.gate.name === 'M' || this.gate.name === 'MX' || this.gate.name === 'MY' || this.gate.name === 'MR' || this.gate.name === 'MRX' || this.gate.name === 'MRY') { + return this.id_targets.length; + } + if (this.gate.name === 'MXX' || this.gate.name === 'MYY' || this.gate.name === 'MZZ') { + return this.id_targets.length / 2; + } + if (this.gate.name.startsWith('MPP:')) { + return 1; + } + return 0; + } + /** * @param {!string} before * @returns {!string} diff --git a/glue/crumble/circuit/pauli_frame.js b/glue/crumble/circuit/pauli_frame.js index 6fb0cd9c5..6ac63a406 100644 --- a/glue/crumble/circuit/pauli_frame.js +++ b/glue/crumble/circuit/pauli_frame.js @@ -358,8 +358,8 @@ class PauliFrame { */ do_swap(targets) { for (let k = 0; k < targets.length; k += 2) { - let a = k; - let b = k + 1; + let a = targets[k]; + let b = targets[k + 1]; let xa = this.xs[a]; let za = this.zs[a]; let xb = this.xs[b]; @@ -376,8 +376,8 @@ class PauliFrame { */ do_iswap(targets) { for (let k = 0; k < targets.length; k += 2) { - let a = k; - let b = k + 1; + let a = targets[k]; + let b = targets[k + 1]; let xa = this.xs[a]; let za = this.zs[a]; @@ -396,8 +396,8 @@ class PauliFrame { */ do_sqrt_xx(targets) { for (let k = 0; k < targets.length; k += 2) { - let a = k; - let b = k + 1; + let a = targets[k]; + let b = targets[k + 1]; let zab = this.zs[a] ^ this.zs[b]; this.xs[a] ^= zab; this.xs[b] ^= zab; @@ -409,8 +409,8 @@ class PauliFrame { */ do_sqrt_yy(targets) { for (let k = 0; k < targets.length; k += 2) { - let a = k; - let b = k + 1; + let a = targets[k]; + let b = targets[k + 1]; let xa = this.xs[a]; let za = this.zs[a]; @@ -436,8 +436,8 @@ class PauliFrame { */ do_sqrt_zz(targets) { for (let k = 0; k < targets.length; k += 2) { - let a = k; - let b = k + 1; + let a = targets[k]; + let b = targets[k + 1]; let xab = this.xs[a] ^ this.xs[b]; this.zs[a] ^= xab; this.zs[b] ^= xab; @@ -449,8 +449,8 @@ class PauliFrame { */ do_xcx(targets) { for (let k = 0; k < targets.length; k += 2) { - let control = k; - let target = k + 1; + let control = targets[k]; + let target = targets[k + 1]; this.xs[target] ^= this.zs[control]; this.xs[control] ^= this.zs[target]; } @@ -461,8 +461,8 @@ class PauliFrame { */ do_xcy(targets) { for (let k = 0; k < targets.length; k += 2) { - let control = k; - let target = k + 1; + let control = targets[k]; + let target = targets[k + 1]; this.xs[target] ^= this.zs[control]; this.zs[target] ^= this.zs[control]; this.xs[control] ^= this.xs[target]; @@ -475,8 +475,8 @@ class PauliFrame { */ do_ycy(targets) { for (let k = 0; k < targets.length; k += 2) { - let control = k; - let target = k + 1; + let control = targets[k]; + let target = targets[k + 1]; let y = this.xs[control] ^ this.zs[control]; this.xs[target] ^= y; this.zs[target] ^= y; @@ -491,8 +491,8 @@ class PauliFrame { */ do_cx(targets) { for (let k = 0; k < targets.length; k += 2) { - let control = k; - let target = k + 1; + let control = targets[k]; + let target = targets[k + 1]; this.xs[target] ^= this.xs[control]; this.zs[control] ^= this.zs[target]; } @@ -503,8 +503,8 @@ class PauliFrame { */ do_cx_swap(targets) { for (let k = 0; k < targets.length; k += 2) { - let c = k; - let t = k + 1; + let c = targets[k]; + let t = targets[k + 1]; let xc = this.xs[c]; let zc = this.zs[c]; let xt = this.xs[t]; @@ -516,13 +516,31 @@ class PauliFrame { } } + /** + * @param {!Array} targets + */ + do_swap_cx(targets) { + for (let k = 0; k < targets.length; k += 2) { + let c = targets[k]; + let t = targets[k + 1]; + let xc = this.xs[c]; + let zc = this.zs[c]; + let xt = this.xs[t]; + let zt = this.zs[t]; + this.xs[c] = xt; + this.zs[c] = zc ^ zt; + this.xs[t] = xt ^ xc; + this.zs[t] = zc; + } + } + /** * @param {!Array} targets */ do_cz_swap(targets) { for (let k = 0; k < targets.length; k += 2) { - let c = k; - let t = k + 1; + let c = targets[k]; + let t = targets[k + 1]; let xc = this.xs[c]; let zc = this.zs[c]; let xt = this.xs[t]; @@ -539,8 +557,8 @@ class PauliFrame { */ do_cy(targets) { for (let k = 0; k < targets.length; k += 2) { - let control = k; - let target = k + 1; + let control = targets[k]; + let target = targets[k + 1]; this.xs[target] ^= this.xs[control]; this.zs[target] ^= this.xs[control]; this.zs[control] ^= this.zs[target]; @@ -553,8 +571,8 @@ class PauliFrame { */ do_cz(targets) { for (let k = 0; k < targets.length; k += 2) { - let control = k; - let target = k + 1; + let control = targets[k]; + let target = targets[k + 1]; this.zs[target] ^= this.xs[control]; this.zs[control] ^= this.xs[target]; } @@ -568,6 +586,14 @@ class PauliFrame { gate.frameDo(this, targets); } + /** + * @param {!Gate} gate + * @param {!Array} targets + */ + undo_gate(gate, targets) { + gate.frameUndo(this, targets); + } + /** * @param {*} other * @returns {!boolean} diff --git a/glue/crumble/circuit/pauli_frame.test.js b/glue/crumble/circuit/pauli_frame.test.js index 3f8005523..6d68ea317 100644 --- a/glue/crumble/circuit/pauli_frame.test.js +++ b/glue/crumble/circuit/pauli_frame.test.js @@ -33,13 +33,18 @@ test("pauli_frame.to_from_dicts", () => { test("pauli_frame.do_gate_vs_old_frame_updates", () => { let gates = [...GATE_MAP.values(), make_mpp_gate("XYY"), make_spp_gate("XYY")]; for (let g of gates) { - let before, after; + if (g.name === 'DETECTOR') { + continue; + } + let before, after, returned; if (g.num_qubits === 1) { before = new PauliFrame(4, g.num_qubits); before.xs[0] = 0b0011; before.zs[0] = 0b0101; after = before.copy(); after.do_gate(g, [0]); + returned = after.copy(); + returned.undo_gate(g, [0]); } else { before = new PauliFrame(16, g.num_qubits); before.xs[0] = 0b0000000011111111; @@ -52,6 +57,11 @@ test("pauli_frame.do_gate_vs_old_frame_updates", () => { } after = before.copy(); after.do_gate(g, targets); + returned = after.copy(); + returned.undo_gate(g, targets); + } + if (!returned.flags[0]) { + assertThat(returned).withInfo({'gate': g.name}).isEqualTo(before); } let before_strings = before.to_strings(); diff --git a/glue/crumble/circuit/propagated_pauli_frames.js b/glue/crumble/circuit/propagated_pauli_frames.js index 737f47e6f..40f238d41 100644 --- a/glue/crumble/circuit/propagated_pauli_frames.js +++ b/glue/crumble/circuit/propagated_pauli_frames.js @@ -1,3 +1,7 @@ +import {PauliFrame} from './pauli_frame.js'; +import {equate} from '../base/equate.js'; +import {Layer} from './layer.js'; + class PropagatedPauliFrameLayer { /** * @param {!Map} bases @@ -9,6 +13,48 @@ class PropagatedPauliFrameLayer { this.errors = errors; this.crossings = crossings; } + + /** + * @param {!PropagatedPauliFrameLayer} other + * @returns {!PropagatedPauliFrameLayer} + */ + mergedWith(other) { + return new PropagatedPauliFrameLayer( + new Map([...this.bases.entries(), ...other.bases.entries()]), + new Set([...this.errors, ...other.errors]), + [...this.crossings, ...other.crossings], + ); + } + + /** + * @returns {!string} + */ + toString() { + let num_qubits = 0; + for (let q of this.bases.keys()) { + num_qubits = Math.max(num_qubits, q + 1); + } + for (let q of this.errors) { + num_qubits = Math.max(num_qubits, q + 1); + } + for (let [q1, q2] of this.crossings) { + num_qubits = Math.max(num_qubits, q1 + 1); + num_qubits = Math.max(num_qubits, q2 + 1); + } + let result = '"'; + for (let q = 0; q < num_qubits; q++) { + let b = this.bases.get(q); + if (b === undefined) { + b = '_'; + } + if (this.errors.has(q)) { + b = 'E'; + } + result += b; + } + result += '"'; + return result; + } } class PropagatedPauliFrames { @@ -19,6 +65,28 @@ class PropagatedPauliFrames { this.id_layers = layers; } + /** + * @param {*} other + * @returns {!boolean} + */ + isEqualTo(other) { + return other instanceof PropagatedPauliFrames && equate(this.id_layers, other.id_layers); + } + + /** + * @returns {!string} + */ + toString() { + let layers = [...this.id_layers.keys()]; + layers.sort((a, b) => a - b); + let lines = ['PropagatedPauliFrames {']; + for (let k of layers) { + lines.push(` ${k}: ${this.id_layers.get(k)}`); + } + lines.push('}'); + return lines.join('\n'); + } + /** * @param {!int} layer */ @@ -83,12 +151,89 @@ class PropagatedPauliFrames { } } - if (bases.size > 0 || errors.size > 0 || crossings.size > 0) { - result.id_layers.set(k, new PropagatedPauliFrameLayer(bases, errors, crossings)); + if (bases.size > 0) { + result.id_layers.set(k + 0.5, new PropagatedPauliFrameLayer(bases, new Set(), [])); + } + if (errors.size > 0 || crossings.length > 0) { + result.id_layers.set(k, new PropagatedPauliFrameLayer(new Map(), errors, crossings)); } } return result; } + + /** + * @param {!Circuit} circuit + * @param {!Array.} measurements + * @returns {!PropagatedPauliFrames} + */ + static fromMeasurements(circuit, measurements) { + let result = new PropagatedPauliFrames(new Map()); + let frame = new PauliFrame(1, circuit.allQubits().size); + let measurementsBack = 0; + + for (let k = circuit.layers.length - 1; k >= -1; k--) { + let layer = k >= 0 ? circuit.layers[k] : new Layer(); + let targets = [...layer.id_ops.keys()]; + targets.reverse(); + + for (let id of targets) { + let op = layer.id_ops.get(id); + if (op.id_targets[0] === id) { + let m = op.countMeasurements(); + frame.undo_gate(op.gate, [...op.id_targets]); + measurementsBack -= m; + if (m > 0 && measurements.indexOf(measurementsBack) !== -1) { + for (let t_id = 0; t_id < op.id_targets.length; t_id++) { + let t = op.id_targets[t_id]; + let basis; + if (op.gate.name === 'MX' || op.gate.name === 'MRX' || op.gate.name === 'MXX') { + basis = 'X'; + } else if (op.gate.name === 'MY' || op.gate.name === 'MRY' || op.gate.name === 'MYY') { + basis = 'Y'; + } else if (op.gate.name === 'M' || op.gate.name === 'MR' || op.gate.name === 'MZZ') { + basis = 'Z'; + } else if (op.gate.name === 'MPAD') { + continue; + } else if (op.gate.name.startsWith('MPP:')) { + basis = op.gate.name[t_id + 4]; + } else { + throw new Error('Unhandled measurement gate: ' + op.gate.name); + } + if (basis === 'X') { + frame.xs[t] ^= 1; + } else if (basis === 'Y') { + frame.xs[t] ^= 1; + frame.zs[t] ^= 1; + } else if (basis === 'Z') { + frame.zs[t] ^= 1; + } else { + throw new Error('Unhandled measurement gate: ' + op.gate.name); + } + } + } + } + } + + let bases = new Map(); + let errors = new Set(); + for (let q = 0; q < frame.xs.length; q++) { + if (frame.xs[q] || frame.zs[q]) { + bases.set(q, '_XZY'[frame.xs[q] + 2*frame.zs[q]]); + } + if (frame.flags[q]) { + frame.flags[q] = 0; + errors.add(q); + } + } + if (bases.size > 0) { + result.id_layers.set(k - 0.5, new PropagatedPauliFrameLayer(bases, new Set(), [])); + } + if (errors.size > 0) { + result.id_layers.set(k, new PropagatedPauliFrameLayer(new Map(), errors, [])); + } + } + return result; + } } -export {PropagatedPauliFrames}; +export {PropagatedPauliFrames, PropagatedPauliFrameLayer}; diff --git a/glue/crumble/circuit/propagated_pauli_frames.test.js b/glue/crumble/circuit/propagated_pauli_frames.test.js new file mode 100644 index 000000000..769c37e85 --- /dev/null +++ b/glue/crumble/circuit/propagated_pauli_frames.test.js @@ -0,0 +1,127 @@ +import {test, assertThat} from "../test/test_util.js" +import {Circuit} from "./circuit.js"; +import {PropagatedPauliFrames, PropagatedPauliFrameLayer} from './propagated_pauli_frames.js'; + +test("propagated_pauli_frames.fromMeasurements", () => { + let propagated = PropagatedPauliFrames.fromMeasurements(Circuit.fromStimCircuit(` + R 0 + TICK + H 0 + TICK + MX 0 + `), [-1]); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + [1.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X']]), + new Set(), + [], + )], + ]))); + + propagated = PropagatedPauliFrames.fromMeasurements(Circuit.fromStimCircuit(` + RX 0 + TICK + H 0 + TICK + MX 0 + `), [-1]); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0, new PropagatedPauliFrameLayer( + new Map(), + new Set([0]), + [], + )], + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + [1.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X']]), + new Set(), + [], + )], + ]))); + + propagated = PropagatedPauliFrames.fromMeasurements(Circuit.fromStimCircuit(` + MX 0 + TICK + H 0 + TICK + MX 0 + `), [-1]); + 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(), + [], + )], + [0, new PropagatedPauliFrameLayer( + new Map(), + new Set([0]), + [], + )], + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'Z']]), + new Set(), + [], + )], + [1.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X']]), + new Set(), + [], + )], + ]))); + + propagated = PropagatedPauliFrames.fromMeasurements(Circuit.fromStimCircuit(` + RX 0 1 + TICK + CX 0 1 + TICK + MX 0 + `), [-1]); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X'], [1, 'X']]), + new Set(), + [], + )], + [1.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X']]), + new Set(), + [], + )], + ]))); + + propagated = PropagatedPauliFrames.fromMeasurements(Circuit.fromStimCircuit(` + CX 1 0 + MX 1 + `), [-1]); + assertThat(propagated).isEqualTo(new PropagatedPauliFrames(new Map([ + [-1.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X'], [1, 'X']]), + new Set(), + [], + )], + [-0.5, new PropagatedPauliFrameLayer( + new Map([[0, 'X'], [1, 'X']]), + new Set(), + [], + )], + [0.5, new PropagatedPauliFrameLayer( + new Map([[1, 'X']]), + new Set(), + [], + )], + ]))); +}); diff --git a/glue/crumble/draw/main_draw.js b/glue/crumble/draw/main_draw.js index 0b00fcbe4..687605503 100644 --- a/glue/crumble/draw/main_draw.js +++ b/glue/crumble/draw/main_draw.js @@ -67,7 +67,7 @@ function drawCrossMarkers(ctx, snap, qubitCoordsFunc, propagatedMarkers, mi) { */ function drawMarkers(ctx, snap, qubitCoordsFunc, propagatedMarkers, mi) { let {dx, dy, wx, wy} = marker_placement(mi); - let basesQubitMap = propagatedMarkers.atLayer(snap.curLayer).bases; + let basesQubitMap = propagatedMarkers.atLayer(snap.curLayer + 0.5).bases; // Convert qubit indices to draw coordinates. let basisCoords = []; @@ -76,7 +76,7 @@ function drawMarkers(ctx, snap, qubitCoordsFunc, propagatedMarkers, mi) { } // Draw a polygon for the marker set. - if (basisCoords.length > 0) { + if (mi >= 0 && basisCoords.length > 0) { if (basisCoords.every(e => e[0] === 'X')) { ctx.fillStyle = 'red'; } else if (basisCoords.every(e => e[0] === 'Y')) { @@ -198,9 +198,13 @@ function draw(ctx, snap) { let y = circuit.qubitCoordData[2 * q + 1]; return c2dCoordTransform(x, y); }; - let propagatedMarkerLayers = /** @type {!Array} */ []; + let propagatedMarkerLayers = /** @type {!Map} */ new Map(); for (let mi = 0; mi < numPropagatedLayers; mi++) { - propagatedMarkerLayers.push(PropagatedPauliFrames.fromCircuit(circuit, mi)); + propagatedMarkerLayers.set(mi, PropagatedPauliFrames.fromCircuit(circuit, mi)); + } + let dets = circuit.collectDetectors(); + for (let mi = 0; mi < dets.length; mi++) { + propagatedMarkerLayers.set(~mi, PropagatedPauliFrames.fromMeasurements(circuit, dets[mi])); } let usedQubitCoordSet = new Set(); @@ -258,8 +262,8 @@ function draw(ctx, snap) { } }); - for (let mi = 0; mi < propagatedMarkerLayers.length; mi++) { - drawCrossMarkers(ctx, snap, qubitDrawCoords, propagatedMarkerLayers[mi], mi); + for (let [mi, p] of propagatedMarkerLayers.entries()) { + drawCrossMarkers(ctx, snap, qubitDrawCoords, p, mi); } for (let op of circuit.layers[snap.curLayer].iter_gates_and_markers()) { @@ -286,8 +290,8 @@ function draw(ctx, snap) { } }); - for (let mi = 0; mi < propagatedMarkerLayers.length; mi++) { - drawMarkers(ctx, snap, qubitDrawCoords, propagatedMarkerLayers[mi], mi); + for (let [mi, p] of propagatedMarkerLayers.entries()) { + drawMarkers(ctx, snap, qubitDrawCoords, p, mi); } if (focusX !== undefined) { @@ -324,30 +328,41 @@ function draw(ctx, snap) { }); }); + drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords); + // Scrubber. ctx.save(); try { ctx.strokeStyle = 'black'; + ctx.translate(ctx.canvas.width / 2, 0); for (let k = 0; k < circuit.layers.length; k++) { - let has_errors = !propagatedMarkerLayers.every(p => p.atLayer(k).errors.size === 0); + if (circuit.layers[k].id_ops.size > 0) { + ctx.strokeStyle = 'black'; + } else { + ctx.strokeStyle = '#EEE'; + } + ctx.strokeRect(k*8 + 0.5, 0.5, 7, 20); if (k === snap.curLayer) { - ctx.fillStyle = 'red'; - ctx.fillRect(0, k*5, 10, 4); - } else if (has_errors) { - ctx.fillStyle = 'magenta'; - ctx.fillRect(-2, k*5-2, 10+4, 4+4); ctx.fillStyle = 'black'; - ctx.fillRect(0, k*5, 10, 4); + } else if (circuit.layers[k].countMeasurements() > 0) { + ctx.fillStyle = '#DDD'; } else { - ctx.fillStyle = 'black'; - ctx.fillRect(0, k*5, 10, 4); + ctx.fillStyle = 'white'; + } + ctx.fillRect(k * 8 + 0.5, 0, 7, 20); + } + for (let k = 0; k < circuit.layers.length; k++) { + let has_errors = ![...propagatedMarkerLayers.values()].every(p => p.atLayer(k).errors.size === 0); + if (has_errors) { + ctx.strokeStyle = 'magenta'; + ctx.lineWidth = 4; + ctx.strokeRect(k*8 + 0.5 - 1, 0.5 - 1, 7 + 2, 20 + 2); + ctx.lineWidth = 1; } } } finally { ctx.restore(); } - - drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords); } export {xyToPos, draw, setDefensiveDrawEnabled, OFFSET_X, OFFSET_Y} diff --git a/glue/crumble/draw/timeline_viewer.js b/glue/crumble/draw/timeline_viewer.js index 1894953c6..81663d964 100644 --- a/glue/crumble/draw/timeline_viewer.js +++ b/glue/crumble/draw/timeline_viewer.js @@ -18,7 +18,12 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi, dx = 0; wx = x_pitch; wy = 5; - if (mi === 0) { + if (mi < 0) { + dx = -10.5 - ~mi % 4 * 5; + dy = 10.5 - Math.floor(~mi / 4) % 4 * 5; + wx = 3; + wy = 3; + } else if (mi === 0) { dy = 10; } else if (mi === 1) { dy = 5; @@ -28,8 +33,9 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi, dy = -5; } for (let t = min_t; t <= max_t; t++) { - let p = propagatedMarkers.atLayer(t); - for (let [q, b] of p.bases.entries()) { + let p1 = propagatedMarkers.atLayer(t + 0.5); + let p0 = propagatedMarkers.atLayer(t); + for (let [q, b] of p1.bases.entries()) { let [x, y] = qubitTimeCoordFunc(q, t); if (x === undefined || y === undefined) { continue; @@ -45,7 +51,7 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi, } ctx.fillRect(x - dx, y - dy, wx, wy); } - for (let q of p.errors) { + for (let q of p0.errors) { let [x, y] = qubitTimeCoordFunc(q, t - 0.5); if (x === undefined || y === undefined) { continue; @@ -55,7 +61,7 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi, ctx.fillStyle = 'black' ctx.fillRect(x - dx, y - dy, wx, wy); } - for (let {q1, q2, color} of p.crossings) { + for (let {q1, q2, color} of p0.crossings) { let [x1, y1] = qubitTimeCoordFunc(q1, t); let [x2, y2] = qubitTimeCoordFunc(q2, t); if (color === 'X') { @@ -77,11 +83,11 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi, /** * @param {!CanvasRenderingContext2D} ctx * @param {!StateSnapshot} snap - * @param {!Array} propagatedMarkerLayers + * @param {!Map} propagatedMarkerLayers * @param {!function(!int): ![!number, !number]} timesliceQubitCoordsFunc */ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFunc) { - let w = ctx.canvas.width / 2; + let w = Math.floor(ctx.canvas.width / 2); let qubits = snap.timelineQubits(); qubits.sort((a, b) => { @@ -112,10 +118,10 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun cur_x += rad * 0.25; cur_run++; } - base_y2xy.set(`${x},${y}`, [cur_x, cur_y]); + base_y2xy.set(`${x},${y}`, [Math.round(cur_x) + 0.5, Math.round(cur_y) + 0.5]); } - let x_pitch = TIMELINE_PITCH + rad*max_run*0.25; + let x_pitch = TIMELINE_PITCH + Math.ceil(rad*max_run*0.25); let coordTransform_t = ([x, y, t]) => { let key = `${x},${y}`; if (!base_y2xy.has(key)) { @@ -135,9 +141,9 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun ctx.save(); try { ctx.clearRect(w, 0, w, ctx.canvas.height); - for (let mi = 0; mi < propagatedMarkerLayers.length; mi++) { - if (mi < 4) { - drawTimelineMarkers(ctx, snap, qubitTimeCoords, propagatedMarkerLayers[mi], mi, min_t, max_t, x_pitch); + for (let [mi, p] of propagatedMarkerLayers.entries()) { + if (mi >= 0 && mi < 4) { + drawTimelineMarkers(ctx, snap, qubitTimeCoords, p, mi, min_t, max_t, x_pitch); } } ctx.globalAlpha *= 0.5; @@ -179,6 +185,12 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun } } + for (let [mi, p] of propagatedMarkerLayers.entries()) { + if (mi < 0) { + drawTimelineMarkers(ctx, snap, qubitTimeCoords, p, mi, min_t, max_t, x_pitch); + } + } + // Draw links to timeslice viewer. ctx.globalAlpha = 0.25; ctx.setLineDash([1, 1]); diff --git a/glue/crumble/editor/editor_state.js b/glue/crumble/editor/editor_state.js index 57eaae655..2c6034dfe 100644 --- a/glue/crumble/editor/editor_state.js +++ b/glue/crumble/editor/editor_state.js @@ -128,7 +128,7 @@ class EditorState { * @param {!Circuit} newCircuit */ preview(newCircuit) { - this.rev.startedWorkingOnCommit(); + this.rev.startedWorkingOnCommit(newCircuit.toStimCircuit()); this.obs_val_draw_state.set(this.toSnapshot(newCircuit)); } @@ -297,7 +297,7 @@ class EditorState { for (let q of op.id_targets) { inferredBases.set(q, opBasis); } - } else if (op.gate.name.startsWith('MPP:') && op.gate.tableau_map === undefined && op.id_targets.length === op.gate.name.length - 1) { + } else if (op.gate.name.startsWith('MPP:') && op.gate.tableau_map === undefined && op.id_targets.length === op.gate.name.length - 4) { // MPP special case. let bases = op.gate.name.substring(4); for (let k = 0; k < op.id_targets.length; k++) { @@ -500,6 +500,25 @@ class EditorState { this._writeVariableQubitGateToFocus(preview, gate, gate_args); } } + + writeMarkerToDetector(preview, marker_index) { + let newCircuit = this.copyOfCurCircuit().withCoordsIncluded(this.focusedSet.values()); + let d = newCircuit.collectDetectors().length; + for (let k = 0; k < newCircuit.layers.length; k++) { + let layer = newCircuit.layers[k]; + for (let k2 = 0; k2 < layer.markers.length; k2++) { + let op = /** @type {!Operation} */ layer.markers[k2]; + if (op.args[0] === marker_index && ['MARKX', 'MARKY', 'MARKZ'].indexOf(op.gate.name) !== -1) { + layer.markers[k2] = new Operation( + GATE_MAP.get('DETECTOR'), + new Float32Array([d]), + op.id_targets, + ); + } + } + } + this.commit_or_preview(newCircuit, preview); + } } export {EditorState, StateSnapshot} diff --git a/glue/crumble/editor/sync_url_to_state.js b/glue/crumble/editor/sync_url_to_state.js index 83bc79a34..b92f781e6 100644 --- a/glue/crumble/editor/sync_url_to_state.js +++ b/glue/crumble/editor/sync_url_to_state.js @@ -58,9 +58,6 @@ function initUrlCircuitSync(revision) { const historyPusher = new HistoryPusher(); const loadCircuitFromUrl = () => { - // Leave a sign that the URL shouldn't be overwritten if we don't understand it. - historyPusher.currentStateIsMemorableButUnknown(); - try { // Extract the circuit parameter. let params = getHashParameters(); @@ -73,10 +70,6 @@ function initUrlCircuitSync(revision) { } } - // We only want to change the browser URL if we end up with a circuit state - // that's different from the one from the initial URL. - historyPusher.currentStateIsMemorableAndEqualTo(params.get("circuit")); - // Repack the circuit data, so we can tell if there are round trip changes. let circuit = Circuit.fromStimCircuit(params.get("circuit")); let cleanedState = circuit.toStimCircuit(); diff --git a/glue/crumble/gates/gate.js b/glue/crumble/gates/gate.js index adba4b093..e299c297b 100644 --- a/glue/crumble/gates/gate.js +++ b/glue/crumble/gates/gate.js @@ -18,6 +18,7 @@ class Gate { * @param {!boolean} is_marker * @param {!Map|undefined} tableau_map * @param {!function(!PauliFrame, !Array)} frameDo, + * @param {!function(!PauliFrame, !Array)} frameUndo, * @param {!gateDrawCallback} drawer * @param {undefined|!number=undefined} defaultArgument */ @@ -27,6 +28,7 @@ class Gate { is_marker, tableau_map, frameDo, + frameUndo, drawer, defaultArgument = undefined) { this.name = name; @@ -35,6 +37,7 @@ class Gate { this.can_fuse = can_fuse; this.tableau_map = tableau_map; this.frameDo = frameDo; + this.frameUndo = frameUndo; this.drawer = drawer; this.defaultArgument = defaultArgument; } @@ -50,6 +53,7 @@ class Gate { this.is_marker, this.tableau_map, this.frameDo, + this.frameUndo, this.drawer, newDefaultArgument); } diff --git a/glue/crumble/gates/gateset.js b/glue/crumble/gates/gateset.js index b35f8919f..e13d404e6 100644 --- a/glue/crumble/gates/gateset.js +++ b/glue/crumble/gates/gateset.js @@ -78,7 +78,6 @@ function make_gate_alias_map() { // Annotations. result.set("MPAD", {ignore: true}); - result.set("DETECTOR", {ignore: true}); result.set("OBSERVABLE_INCLUDE", {ignore: true}); result.set("SHIFT_COORDS", {ignore: true}); diff --git a/glue/crumble/gates/gateset_controlled_paulis.js b/glue/crumble/gates/gateset_controlled_paulis.js index 881ed9967..7480d83f9 100644 --- a/glue/crumble/gates/gateset_controlled_paulis.js +++ b/glue/crumble/gates/gateset_controlled_paulis.js @@ -21,6 +21,7 @@ function *iter_gates_controlled_paulis() { ['ZI', 'ZI'], ]), (frame, targets) => frame.do_cx(targets), + (frame, targets) => frame.do_cx(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -41,6 +42,7 @@ function *iter_gates_controlled_paulis() { ['ZI', 'ZI'], ]), (frame, targets) => frame.do_cy(targets), + (frame, targets) => frame.do_cy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -61,6 +63,7 @@ function *iter_gates_controlled_paulis() { ['ZI', 'ZX'], ]), (frame, targets) => frame.do_xcx(targets), + (frame, targets) => frame.do_xcx(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -81,6 +84,7 @@ function *iter_gates_controlled_paulis() { ['ZI', 'ZY'], ]), (frame, targets) => frame.do_xcy(targets), + (frame, targets) => frame.do_xcy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -101,6 +105,7 @@ function *iter_gates_controlled_paulis() { ['ZI', 'ZY'], ]), (frame, targets) => frame.do_ycy(targets), + (frame, targets) => frame.do_ycy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -121,6 +126,7 @@ function *iter_gates_controlled_paulis() { ['ZI', 'ZI'], ]), (frame, targets) => frame.do_cz(targets), + (frame, targets) => frame.do_cz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); diff --git a/glue/crumble/gates/gateset_demolition_measurements.js b/glue/crumble/gates/gateset_demolition_measurements.js index d9ea6f09e..5060b8620 100644 --- a/glue/crumble/gates/gateset_demolition_measurements.js +++ b/glue/crumble/gates/gateset_demolition_measurements.js @@ -13,6 +13,7 @@ function *iter_gates_demolition_measurements() { ['Z', 'I'], ]), (frame, targets) => frame.do_demolition_measure('Z', targets), + (frame, targets) => frame.do_demolition_measure('Z', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; @@ -36,6 +37,7 @@ function *iter_gates_demolition_measurements() { ['Z', 'ERR:I'], ]), (frame, targets) => frame.do_demolition_measure('Y', targets), + (frame, targets) => frame.do_demolition_measure('Y', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; @@ -59,6 +61,7 @@ function *iter_gates_demolition_measurements() { ['Z', 'ERR:I'], ]), (frame, targets) => frame.do_demolition_measure('X', targets), + (frame, targets) => frame.do_demolition_measure('X', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; diff --git a/glue/crumble/gates/gateset_hadamard_likes.js b/glue/crumble/gates/gateset_hadamard_likes.js index a71689011..f9de9402d 100644 --- a/glue/crumble/gates/gateset_hadamard_likes.js +++ b/glue/crumble/gates/gateset_hadamard_likes.js @@ -12,6 +12,7 @@ function *iter_gates_hadamard_likes() { ['Z', 'X'], ]), (frame, targets) => frame.do_exchange_xz(targets), + (frame, targets) => frame.do_exchange_xz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -34,6 +35,7 @@ function *iter_gates_hadamard_likes() { ['Z', 'Z'], // -Z technically ]), (frame, targets) => frame.do_exchange_xy(targets), + (frame, targets) => frame.do_exchange_xy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -57,6 +59,7 @@ function *iter_gates_hadamard_likes() { ['Z', 'Y'], ]), (frame, targets) => frame.do_exchange_yz(targets), + (frame, targets) => frame.do_exchange_yz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; diff --git a/glue/crumble/gates/gateset_markers.js b/glue/crumble/gates/gateset_markers.js index 6474d8014..d652a109e 100644 --- a/glue/crumble/gates/gateset_markers.js +++ b/glue/crumble/gates/gateset_markers.js @@ -8,7 +8,12 @@ import {beginPathPolygon} from '../draw/draw_util.js'; */ function marker_placement(mi) { let dx, dy, wx, wy; - if (mi === 0) { + if (mi < 0) { + dx = 10 - ~mi % 4 * 5; + dy = 10 - Math.floor(~mi / 4) % 4 * 5; + wx = 3; + wy = 3; + } else if (mi === 0) { dx = rad; dy = rad + 5; wx = rad * 2; @@ -45,6 +50,7 @@ function *iter_gates_markers() { true, undefined, () => {}, + () => {}, (op, coordFunc, ctx) => { let transformedCoords = [] for (let t of op.id_targets) { @@ -58,6 +64,17 @@ function *iter_gates_markers() { ctx.stroke(); }, ); + yield new Gate( + 'DETECTOR', + undefined, + false, + true, + undefined, + () => {}, + () => {}, + (op, coordFunc, ctx) => { + }, + ); yield new Gate( 'MARKX', 1, @@ -65,6 +82,7 @@ function *iter_gates_markers() { true, undefined, () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); if (x1 === undefined || y1 === undefined) { @@ -99,6 +117,7 @@ function *iter_gates_markers() { true, undefined, () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); if (x1 === undefined || y1 === undefined) { @@ -133,6 +152,7 @@ function *iter_gates_markers() { true, undefined, () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); if (x1 === undefined || y1 === undefined) { @@ -167,6 +187,7 @@ function *iter_gates_markers() { true, undefined, () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); if (x1 === undefined || y1 === undefined) { diff --git a/glue/crumble/gates/gateset_mpp.js b/glue/crumble/gates/gateset_mpp.js index 4d597e121..d1749688e 100644 --- a/glue/crumble/gates/gateset_mpp.js +++ b/glue/crumble/gates/gateset_mpp.js @@ -14,6 +14,7 @@ function make_mpp_gate(bases) { false, undefined, (frame, targets) => frame.do_mpp(bases, targets), + (frame, targets) => frame.do_mpp(bases, targets), (op, coordFunc, ctx) => { let prev_x = undefined; let prev_y = undefined; @@ -60,6 +61,7 @@ function make_spp_gate(bases, dag) { false, undefined, (frame, targets) => frame.do_spp(bases, targets), + (frame, targets) => frame.do_spp(bases, targets), (op, coordFunc, ctx) => { let prev_x = undefined; let prev_y = undefined; diff --git a/glue/crumble/gates/gateset_pair_measurements.js b/glue/crumble/gates/gateset_pair_measurements.js index 4c90d2bb3..071e53624 100644 --- a/glue/crumble/gates/gateset_pair_measurements.js +++ b/glue/crumble/gates/gateset_pair_measurements.js @@ -30,6 +30,7 @@ function *iter_gates_pair_measurements() { ['ZZ', 'ZZ'], ]), (frame, targets) => frame.do_measure('XX', targets), + (frame, targets) => frame.do_measure('XX', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -76,6 +77,7 @@ function *iter_gates_pair_measurements() { ['ZZ', 'ZZ'], ]), (frame, targets) => frame.do_measure('YY', targets), + (frame, targets) => frame.do_measure('YY', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -122,6 +124,7 @@ function *iter_gates_pair_measurements() { ['ZZ', 'ZZ'], ]), (frame, targets) => frame.do_measure('ZZ', targets), + (frame, targets) => frame.do_measure('ZZ', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); diff --git a/glue/crumble/gates/gateset_paulis.js b/glue/crumble/gates/gateset_paulis.js index e4fcb9681..2a4383c4b 100644 --- a/glue/crumble/gates/gateset_paulis.js +++ b/glue/crumble/gates/gateset_paulis.js @@ -12,6 +12,7 @@ function *iter_gates_paulis() { ['Z', 'Z'], ]), () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'white'; @@ -34,6 +35,7 @@ function *iter_gates_paulis() { ['Z', 'Z'], ]), () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'white'; @@ -56,6 +58,7 @@ function *iter_gates_paulis() { ['Z', 'Z'], ]), () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'white'; @@ -78,6 +81,7 @@ function *iter_gates_paulis() { ['Z', 'Z'], ]), () => {}, + () => {}, (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'white'; diff --git a/glue/crumble/gates/gateset_quarter_turns.js b/glue/crumble/gates/gateset_quarter_turns.js index f4904623d..491c44432 100644 --- a/glue/crumble/gates/gateset_quarter_turns.js +++ b/glue/crumble/gates/gateset_quarter_turns.js @@ -12,6 +12,7 @@ function *iter_gates_quarter_turns() { ['Z', 'Z'], ]), (frame, targets) => frame.do_exchange_xy(targets), + (frame, targets) => frame.do_exchange_xy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -34,6 +35,7 @@ function *iter_gates_quarter_turns() { ['Z', 'Z'], ]), (frame, targets) => frame.do_exchange_xy(targets), + (frame, targets) => frame.do_exchange_xy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -57,6 +59,7 @@ function *iter_gates_quarter_turns() { ['Z', 'Y'], ]), (frame, targets) => frame.do_exchange_yz(targets), + (frame, targets) => frame.do_exchange_yz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -79,6 +82,7 @@ function *iter_gates_quarter_turns() { ['Z', 'Y'], ]), (frame, targets) => frame.do_exchange_yz(targets), + (frame, targets) => frame.do_exchange_yz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -102,6 +106,7 @@ function *iter_gates_quarter_turns() { ['Z', 'X'], ]), (frame, targets) => frame.do_exchange_xz(targets), + (frame, targets) => frame.do_exchange_xz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; @@ -124,6 +129,7 @@ function *iter_gates_quarter_turns() { ['Z', 'X'], ]), (frame, targets) => frame.do_exchange_xz(targets), + (frame, targets) => frame.do_exchange_xz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'yellow'; diff --git a/glue/crumble/gates/gateset_resets.js b/glue/crumble/gates/gateset_resets.js index dbedafffe..8f294fea4 100644 --- a/glue/crumble/gates/gateset_resets.js +++ b/glue/crumble/gates/gateset_resets.js @@ -13,6 +13,7 @@ function *iter_gates_resets() { ['Z', 'ERR:I'], ]), (frame, targets) => frame.do_discard(targets), + (frame, targets) => frame.do_demolition_measure('Z', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; @@ -36,6 +37,7 @@ function *iter_gates_resets() { ['Z', 'ERR:I'], ]), (frame, targets) => frame.do_discard(targets), + (frame, targets) => frame.do_demolition_measure('X', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; @@ -59,6 +61,7 @@ function *iter_gates_resets() { ['Z', 'ERR:I'], ]), (frame, targets) => frame.do_discard(targets), + (frame, targets) => frame.do_demolition_measure('Y', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; diff --git a/glue/crumble/gates/gateset_solo_measurements.js b/glue/crumble/gates/gateset_solo_measurements.js index 1f832a15e..b80e11f12 100644 --- a/glue/crumble/gates/gateset_solo_measurements.js +++ b/glue/crumble/gates/gateset_solo_measurements.js @@ -13,6 +13,7 @@ function *iter_gates_solo_measurements() { ['Z', 'Z'], ]), (frame, targets) => frame.do_measure('Z', targets), + (frame, targets) => frame.do_measure('Z', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; @@ -37,6 +38,7 @@ function *iter_gates_solo_measurements() { ['Z', 'ERR:Z'], ]), (frame, targets) => frame.do_measure('X', targets), + (frame, targets) => frame.do_measure('X', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; @@ -61,6 +63,7 @@ function *iter_gates_solo_measurements() { ['Z', 'ERR:Z'], ]), (frame, targets) => frame.do_measure('Y', targets), + (frame, targets) => frame.do_measure('Y', targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'gray'; diff --git a/glue/crumble/gates/gateset_sqrt_pauli_pairs.js b/glue/crumble/gates/gateset_sqrt_pauli_pairs.js index 154113717..44c889170 100644 --- a/glue/crumble/gates/gateset_sqrt_pauli_pairs.js +++ b/glue/crumble/gates/gateset_sqrt_pauli_pairs.js @@ -15,6 +15,7 @@ function *iter_gates_sqrt_pauli_pairs() { ['ZI', 'YX'], ]), (frame, targets) => frame.do_sqrt_xx(targets), + (frame, targets) => frame.do_sqrt_xx(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -45,6 +46,7 @@ function *iter_gates_sqrt_pauli_pairs() { ['ZI', 'YX'], ]), (frame, targets) => frame.do_sqrt_xx(targets), + (frame, targets) => frame.do_sqrt_xx(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -76,6 +78,7 @@ function *iter_gates_sqrt_pauli_pairs() { ['ZI', 'XY'], ]), (frame, targets) => frame.do_sqrt_yy(targets), + (frame, targets) => frame.do_sqrt_yy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -106,6 +109,7 @@ function *iter_gates_sqrt_pauli_pairs() { ['ZI', 'XY'], ]), (frame, targets) => frame.do_sqrt_yy(targets), + (frame, targets) => frame.do_sqrt_yy(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -137,6 +141,7 @@ function *iter_gates_sqrt_pauli_pairs() { ['ZI', 'ZI'], ]), (frame, targets) => frame.do_sqrt_zz(targets), + (frame, targets) => frame.do_sqrt_zz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -167,6 +172,7 @@ function *iter_gates_sqrt_pauli_pairs() { ['ZI', 'ZI'], ]), (frame, targets) => frame.do_sqrt_zz(targets), + (frame, targets) => frame.do_sqrt_zz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); diff --git a/glue/crumble/gates/gateset_swaps.js b/glue/crumble/gates/gateset_swaps.js index dda20ad39..630fd6b98 100644 --- a/glue/crumble/gates/gateset_swaps.js +++ b/glue/crumble/gates/gateset_swaps.js @@ -20,6 +20,7 @@ function *iter_gates_swaps() { ['ZI', 'IZ'], ]), (frame, targets) => frame.do_iswap(targets), + (frame, targets) => frame.do_iswap(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -40,6 +41,7 @@ function *iter_gates_swaps() { ['ZI', 'IZ'], ]), (frame, targets) => frame.do_iswap(targets), + (frame, targets) => frame.do_iswap(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -60,6 +62,7 @@ function *iter_gates_swaps() { ['ZI', 'IZ'], ]), (frame, targets) => frame.do_swap(targets), + (frame, targets) => frame.do_swap(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -80,6 +83,7 @@ function *iter_gates_swaps() { ['ZI', 'IZ'], ]), (frame, targets) => frame.do_cx_swap(targets), + (frame, targets) => frame.do_swap_cx(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); @@ -100,6 +104,7 @@ function *iter_gates_swaps() { ['ZI', 'IZ'], ]), (frame, targets) => frame.do_cz_swap(targets), + (frame, targets) => frame.do_cz_swap(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); let [x2, y2] = coordFunc(op.id_targets[1]); diff --git a/glue/crumble/gates/gateset_third_turns.js b/glue/crumble/gates/gateset_third_turns.js index 259f1d190..e84510d45 100644 --- a/glue/crumble/gates/gateset_third_turns.js +++ b/glue/crumble/gates/gateset_third_turns.js @@ -12,6 +12,7 @@ function *iter_gates_third_turns() { ['Z', 'X'], ]), (frame, targets) => frame.do_cycle_xyz(targets), + (frame, targets) => frame.do_cycle_zyx(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'teal'; @@ -35,6 +36,7 @@ function *iter_gates_third_turns() { ['Z', 'Y'], ]), (frame, targets) => frame.do_cycle_zyx(targets), + (frame, targets) => frame.do_cycle_xyz(targets), (op, coordFunc, ctx) => { let [x1, y1] = coordFunc(op.id_targets[0]); ctx.fillStyle = 'teal'; diff --git a/glue/crumble/main.js b/glue/crumble/main.js index 01802a4f9..dcdf78cd1 100644 --- a/glue/crumble/main.js +++ b/glue/crumble/main.js @@ -38,7 +38,7 @@ btnExport.addEventListener('click', _ev => { }); btnImport.addEventListener('click', _ev => { let text = txtStimCircuit.value; - let circuit = Circuit.fromStimCircuit(text.replaceAll('\n#!pragma ', '\n')); + let circuit = Circuit.fromStimCircuit(text.replaceAll('#!pragma ', '')); editorState.commit(circuit); }); @@ -124,24 +124,30 @@ editorState.canvas.addEventListener('mousemove', ev => { editorState.curMouseY = ev.offsetY + OFFSET_Y; // Scrubber. - if (editorState.mouseDownX - OFFSET_X < 10 && editorState.curMouseX - OFFSET_X < 10 && ev.buttons === 1) { - editorState.changeCurLayerTo(Math.floor(ev.offsetY / 5)); - ev.preventDefault(); + let w = editorState.canvas.width / 2; + if (isInScrubber && ev.buttons === 1) { + editorState.changeCurLayerTo(Math.floor((ev.offsetX - w) / 8)); return; } editorState.force_redraw(); }); +let isInScrubber = false; editorState.canvas.addEventListener('mousedown', ev => { editorState.curMouseX = ev.offsetX + OFFSET_X; editorState.curMouseY = ev.offsetY + OFFSET_Y; editorState.mouseDownX = ev.offsetX + OFFSET_X; editorState.mouseDownY = ev.offsetY + OFFSET_Y; - if (editorState.mouseDownX - OFFSET_X < 10 && ev.buttons === 1) { - editorState.changeCurLayerTo(Math.floor(ev.offsetY / 5)); + + // Scrubber. + let w = editorState.canvas.width / 2; + if (ev.offsetY < 20 && ev.offsetX > w && ev.buttons === 1) { + isInScrubber = true; + editorState.changeCurLayerTo(Math.floor((ev.offsetX - w) / 8)); return; } + editorState.force_redraw(); }); @@ -152,6 +158,9 @@ editorState.canvas.addEventListener('mouseup', ev => { editorState.curMouseX = ev.offsetX + OFFSET_X; editorState.curMouseY = ev.offsetY + OFFSET_Y; editorState.changeFocus(highlightedArea, ev.shiftKey, ev.ctrlKey); + if (ev.buttons === 1) { + isInScrubber = false; + } }); /** @@ -205,6 +214,7 @@ function makeChordHandlers() { res.set(`${key}+x`, preview => editorState.writeGateToFocus(preview, GATE_MAP.get('MARKX').withDefaultArgument(val))); res.set(`${key}+y`, preview => editorState.writeGateToFocus(preview, GATE_MAP.get('MARKY').withDefaultArgument(val))); res.set(`${key}+z`, preview => editorState.writeGateToFocus(preview, GATE_MAP.get('MARKZ').withDefaultArgument(val))); + res.set(`${key}+d`, preview => editorState.writeMarkerToDetector(preview, val)); } res.set('p', preview => editorState.writeGateToFocus(preview, GATE_MAP.get("POLYGON"), [1, 0, 0, 0.5])); @@ -369,11 +379,11 @@ const CHORD_HANDLERS = makeChordHandlers(); function handleKeyboardEvent(ev) { editorState.chorder.handleKeyEvent(ev); if (ev.type === 'keydown') { - if (ev.key === 'q') { + if (ev.key === 'q' || ev.key === 'Q') { editorState.changeCurLayerTo(editorState.curLayer - 1); return; } - if (ev.key === 'e') { + if (ev.key === 'e' || ev.key === 'E') { editorState.changeCurLayerTo(editorState.curLayer + 1); return; } diff --git a/glue/crumble/test/test_import_all.js b/glue/crumble/test/test_import_all.js index 97f896155..921110243 100644 --- a/glue/crumble/test/test_import_all.js +++ b/glue/crumble/test/test_import_all.js @@ -5,6 +5,7 @@ import "../base/revision.test.js" import "../circuit/circuit.test.js" import "../circuit/layer.test.js" import "../circuit/pauli_frame.test.js" +import "../circuit/propagated_pauli_frames.test.js" import "../draw/main_draw.test.js" import "../editor/editor_state.test.js" import "../gates/gateset.test.js" diff --git a/glue/crumble/test/test_util.js b/glue/crumble/test/test_util.js index 350ce9a62..5a8817c03 100644 --- a/glue/crumble/test/test_util.js +++ b/glue/crumble/test/test_util.js @@ -187,7 +187,7 @@ class AssertSubject { /** * @param {*} value * @param {!int} index - * @param {!string} info + * @param {!*} info */ constructor(value, index, info) { this.value = value; @@ -196,7 +196,7 @@ class AssertSubject { } /** - * @param {!string} info + * @param {!*} info * @returns {!AssertSubject} */ withInfo(info) { @@ -224,7 +224,7 @@ class AssertSubject { if (this.info === undefined) { return `Assertion #${this.index}`; } - return `Assertion #${this.index} with info '${this.info}'`; + return `Assertion #${this.index} with info ${describe(this.info)}`; } runsWithoutThrowingAnException() {