diff --git a/glue/crumble/README.md b/glue/crumble/README.md
index 0afbc545..16ae6ab6 100644
--- a/glue/crumble/README.md
+++ b/glue/crumble/README.md
@@ -105,8 +105,8 @@ button (now labelled "Hide Import/Export") again.
- `e`: Move to next layer.
- `q`: Move to previous layer.
-- `shift+e`: Move forward 10 layers.
-- `shift+q`: Move backward 10 layers.
+- `shift+e`: Move forward 5 layers.
+- `shift+q`: Move backward 5 layers.
- `escape`: Unselect. Set current selection to the empty set.
- `delete`: Delete gates at current selection.
- `backspace`: Delete gates at current selection.
@@ -119,8 +119,8 @@ button (now labelled "Hide Import/Export") again.
- `ctrl+c`: Copy selection to clipboard (or entire layer if nothing selected).
- `ctrl+v`: Past clipboard contents at current selection (or entire layer if nothing selected).
- `ctrl+x`: Cut selection to clipboard (or entire layer if nothing selected).
-- `f`: Flip qubit order of selected operations (e.g. flip the control-to-target direction of a CNOT).
-- `shift+f`: Flip order of all circuit layers, starting from the current layer until the next empty layer.
+- `f`: Reverse direction of selected two qubit gates (e.g. exchange the controls and targets of a CNOT).
+- `g`: Reverse order of circuit layers, from the current layer to the next empty layer.
- `home`: Jump to the first layer of the circuit.
- `end`: Jump to the last layer of the circuit.
- `t`: Rotate circuit 45 degrees clockwise.
diff --git a/glue/crumble/crumble.html b/glue/crumble/crumble.html
index 5dfda322..f8831009 100644
--- a/glue/crumble/crumble.html
+++ b/glue/crumble/crumble.html
@@ -10,17 +10,34 @@
diff --git a/glue/crumble/draw/main_draw.js b/glue/crumble/draw/main_draw.js
index 608a5f11..c0580ca5 100644
--- a/glue/crumble/draw/main_draw.js
+++ b/glue/crumble/draw/main_draw.js
@@ -369,7 +369,7 @@ function draw(ctx, snap) {
});
});
- drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords);
+ drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords, circuit.layers.length);
// Draw scrubber.
ctx.save();
diff --git a/glue/crumble/draw/timeline_viewer.js b/glue/crumble/draw/timeline_viewer.js
index 36ed924f..2d7640f8 100644
--- a/glue/crumble/draw/timeline_viewer.js
+++ b/glue/crumble/draw/timeline_viewer.js
@@ -96,8 +96,9 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi,
* @param {!StateSnapshot} snap
* @param {!Map} propagatedMarkerLayers
* @param {!function(!int): ![!number, !number]} timesliceQubitCoordsFunc
+ * @param {!int} numLayers
*/
-function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFunc) {
+function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFunc, numLayers) {
let w = Math.floor(ctx.canvas.width / 2);
let qubits = snap.timelineQubits();
@@ -133,32 +134,45 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
}
let x_pitch = TIMELINE_PITCH + Math.ceil(rad*max_run*0.25);
+ let num_cols_half = Math.floor(ctx.canvas.width / 4 / x_pitch);
+ let min_t_free = snap.curLayer - num_cols_half + 1;
+ let min_t_clamp = Math.max(0, Math.min(min_t_free, numLayers - num_cols_half*2 + 1));
+ let max_t = Math.min(min_t_clamp + num_cols_half*2 + 2, numLayers);
+ let t2t = t => {
+ let dt = t - snap.curLayer;
+ dt -= min_t_clamp - min_t_free;
+ return dt*x_pitch;
+ }
let coordTransform_t = ([x, y, t]) => {
let key = `${x},${y}`;
if (!base_y2xy.has(key)) {
return [undefined, undefined];
}
let [xb, yb] = base_y2xy.get(key);
- return [xb + (t - snap.curLayer)*x_pitch, yb];
+ return [xb + t2t(t), yb];
};
let qubitTimeCoords = (q, t) => {
let [x, y] = timesliceQubitCoordsFunc(q);
return coordTransform_t([x, y, t]);
}
- let num_cols_half = Math.floor(ctx.canvas.width / 4 / x_pitch);
- let min_t = Math.max(0, snap.curLayer - num_cols_half + 1);
- let max_t = snap.curLayer + num_cols_half + 2;
ctx.save();
try {
ctx.clearRect(w, 0, w, ctx.canvas.height);
+
+ // Draw colored indicators showing Pauli propagation.
let hitCounts = new Map();
for (let [mi, p] of propagatedMarkerLayers.entries()) {
- drawTimelineMarkers(ctx, snap, qubitTimeCoords, p, mi, min_t, max_t, x_pitch, hitCounts);
+ drawTimelineMarkers(ctx, snap, qubitTimeCoords, p, mi, min_t_clamp, max_t, x_pitch, hitCounts);
}
+
+ // Draw highlight of current layer.
ctx.globalAlpha *= 0.5;
ctx.fillStyle = 'black';
- ctx.fillRect(w*1.5 - rad*1.3, 0, x_pitch, ctx.canvas.height);
+ {
+ let x1 = t2t(snap.curLayer) + w * 1.5 - x_pitch / 2;
+ ctx.fillRect(x1, 0, x_pitch, ctx.canvas.height);
+ }
ctx.globalAlpha *= 2;
ctx.strokeStyle = 'black';
@@ -166,7 +180,7 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
// Draw wire lines.
for (let q of qubits) {
- let [x0, y0] = qubitTimeCoords(q, min_t - 1);
+ let [x0, y0] = qubitTimeCoords(q, min_t_clamp - 1);
let [x1, y1] = qubitTimeCoords(q, max_t + 1);
ctx.beginPath();
ctx.moveTo(x0, y0);
@@ -174,17 +188,18 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
ctx.stroke();
}
- // Draw labels.
+ // Draw wire labels.
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let q of qubits) {
- let [x, y] = qubitTimeCoords(q, min_t - 1);
+ let [x, y] = qubitTimeCoords(q, min_t_clamp - 1);
let qx = snap.circuit.qubitCoordData[q * 2];
let qy = snap.circuit.qubitCoordData[q * 2 + 1];
ctx.fillText(`${qx},${qy}:`, x, y);
}
- for (let time = min_t; time <= max_t; time++) {
+ // Draw layers of gates.
+ for (let time = min_t_clamp; time <= max_t; time++) {
let qubitsCoordsFuncForLayer = q => qubitTimeCoords(q, time);
let layer = snap.circuit.layers[time];
if (layer === undefined) {
@@ -198,7 +213,7 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
// Draw links to timeslice viewer.
ctx.globalAlpha = 0.5;
for (let q of qubits) {
- let [x0, y0] = qubitTimeCoords(q, min_t - 1);
+ let [x0, y0] = qubitTimeCoords(q, min_t_clamp - 1);
let [x1, y1] = timesliceQubitCoordsFunc(q);
if (snap.curMouseX > ctx.canvas.width / 2 && snap.curMouseY >= y0 + OFFSET_Y - TIMELINE_PITCH * 0.55 && snap.curMouseY <= y0 + TIMELINE_PITCH * 0.55 + OFFSET_Y) {
ctx.beginPath();
diff --git a/glue/crumble/editor/editor_state.js b/glue/crumble/editor/editor_state.js
index 5a934392..bb72ec14 100644
--- a/glue/crumble/editor/editor_state.js
+++ b/glue/crumble/editor/editor_state.js
@@ -215,11 +215,12 @@ class EditorState {
/**
* @param {!function(!number, !number): ![!number, !number]} coordTransform
* @param {!boolean} preview
+ * @param {!boolean} moveFocus
*/
- applyCoordinateTransform(coordTransform, preview) {
+ applyCoordinateTransform(coordTransform, preview, moveFocus) {
let c = this.copyOfCurCircuit();
c = c.afterCoordTransform(coordTransform);
- if (!preview) {
+ if (!preview && moveFocus) {
let trans = m => {
let new_m = new Map();
for (let [x, y] of m.values()) {
@@ -244,7 +245,7 @@ class EditorState {
this.applyCoordinateTransform((x, y) => {
[x, y] = t1(x, y);
return t2(x, y);
- }, preview);
+ }, preview, true);
}
/**
diff --git a/glue/crumble/keyboard/toolbox.js b/glue/crumble/keyboard/toolbox.js
index 5852e019..cae74d36 100644
--- a/glue/crumble/keyboard/toolbox.js
+++ b/glue/crumble/keyboard/toolbox.js
@@ -161,12 +161,34 @@ function drawToolbox(ev) {
let cy = PAD + PITCH * p + DIAM / 2;
if (text.startsWith('P')) {
ctx.beginPath();
- ctx.moveTo(cx + PITCH * 0.25, cy + PITCH * 0.25);
- ctx.lineTo(cx, cy - PITCH * 0.25);
- ctx.lineTo(cx - PITCH * 0.25, cy + PITCH * 0.25);
+ let numPoints = 3;
+ if (text === 'PX') {
+ numPoints = 4;
+ ctx.fillStyle = 'red';
+ } else if (text === 'PY') {
+ numPoints = 5;
+ ctx.fillStyle = 'green';
+ cy += 1;
+ } else if (text === 'PZ') {
+ numPoints = 3;
+ ctx.fillStyle = 'blue';
+ cy += 2;
+ }
+ let pts = [];
+ for (let k = 0; k < numPoints; k++) {
+ let t = 2 * Math.PI / numPoints * (k + 0.5);
+ pts.push([
+ Math.round(cx + 0.3 * DIAM * Math.sin(t)),
+ Math.round(cy + 0.3 * DIAM * Math.cos(t)),
+ ]);
+ }
+ ctx.moveTo(pts[pts.length - 1][0], pts[pts.length - 1][1]);
+ for (let pt of pts) {
+ ctx.lineTo(pt[0], pt[1]);
+ }
+
ctx.closePath();
ctx.globalAlpha *= 0.25;
- ctx.fillStyle = text === 'PX' ? 'red' : text === 'PY' ? 'green' : 'blue';
ctx.fill();
ctx.globalAlpha *= 4;
continue;
diff --git a/glue/crumble/main.js b/glue/crumble/main.js
index eae09498..bfa1d8a8 100644
--- a/glue/crumble/main.js
+++ b/glue/crumble/main.js
@@ -27,6 +27,8 @@ const txtStimCircuit = /** @type {!HTMLTextAreaElement} */ document.getElementBy
const btnTimelineFocus = /** @type{!HTMLButtonElement} */ document.getElementById('btnTimelineFocus');
const btnClearTimelineFocus = /** @type{!HTMLButtonElement} */ document.getElementById('btnClearTimelineFocus');
const btnClearSelectedMarkers = /** @type{!HTMLButtonElement} */ document.getElementById('btnClearSelectedMarkers');
+const btnShowExamples = /** @type {!HTMLButtonElement} */ document.getElementById('btnShowExamples');
+const divExamples = /** @type{!HTMLDivElement} */ document.getElementById('examples-div');
// Prevent typing in the import/export text editor from causing changes in the main circuit editor.
txtStimCircuit.addEventListener('keyup', ev => ev.stopPropagation());
@@ -77,6 +79,16 @@ btnClearSelectedMarkers.addEventListener('click', _ev => {
editorState.force_redraw();
});
+btnShowExamples.addEventListener('click', _ev => {
+ if (divExamples.style.display === 'none') {
+ divExamples.style.display = 'block';
+ btnShowExamples.textContent = "Hide Example Circuits";
+ } else {
+ divExamples.style.display = 'none';
+ btnShowExamples.textContent = "Show Example Circuits";
+ }
+});
+
btnClearTimelineFocus.addEventListener('click', _ev => {
editorState.timelineSet = new Map();
editorState.force_redraw();
@@ -244,18 +256,42 @@ function makeChordHandlers() {
let newCircuit = editorState.copyOfCurCircuit();
let layer = newCircuit.layers[editorState.curLayer];
let flipped_op_first_targets = new Set();
+ let pairs = [
+ ['CX', 'reverse'],
+ ['CY', 'reverse'],
+ ['XCY', 'reverse'],
+ ['CXSWAP', 'reverse'],
+ ['XCZ', 'reverse'],
+ ['XCY', 'reverse'],
+ ['YCX', 'reverse'],
+ ['SWAPCX', 'reverse'],
+ ['RX', 'MX'],
+ ['R', 'M'],
+ ['RY', 'MY'],
+ ];
+ let rev = new Map();
+ for (let p of pairs) {
+ rev.set(p[0], p[1]);
+ rev.set(p[1], p[0]);
+ }
for (let q of editorState.focusedSet.keys()) {
let op = layer.id_ops.get(newCircuit.coordToQubitMap().get(q));
- if (op !== undefined && ['CX', 'XCZ', 'CY', 'XCY', 'YCX', 'YCZ', 'CXSWAP', 'SWAPCX'].indexOf(op.gate.name) !== -1) {
+ if (op !== undefined && rev.has(op.gate.name)) {
flipped_op_first_targets.add(op.id_targets[0]);
}
}
for (let q of flipped_op_first_targets) {
- layer.id_ops.get(q).id_targets.reverse();
+ let op = layer.id_ops.get(q);
+ let other = rev.get(op.gate.name);
+ if (other === 'reverse') {
+ layer.id_ops.get(q).id_targets.reverse();
+ } else {
+ op.gate = GATE_MAP.get(other);
+ }
}
editorState.commit_or_preview(newCircuit, preview);
});
- res.set('shift+f', preview => {
+ res.set('g', preview => {
let newCircuit = editorState.copyOfCurCircuit();
let end = editorState.curLayer;
while (end < newCircuit.layers.length && !newCircuit.layers[end].empty()) {
@@ -271,15 +307,15 @@ function makeChordHandlers() {
}
editorState.commit_or_preview(newCircuit, preview);
});
- res.set('shift+>', preview => editorState.applyCoordinateTransform((x, y) => [x + 1, y], preview));
- res.set('shift+<', preview => editorState.applyCoordinateTransform((x, y) => [x - 1, y], preview));
- res.set('shift+v', preview => editorState.applyCoordinateTransform((x, y) => [x, y + 1], preview));
- res.set('shift+^', preview => editorState.applyCoordinateTransform((x, y) => [x, y - 1], preview));
- res.set('>', preview => editorState.applyCoordinateTransform((x, y) => [x + 1, y], preview));
- res.set('<', preview => editorState.applyCoordinateTransform((x, y) => [x - 1, y], preview));
- res.set('v', preview => editorState.applyCoordinateTransform((x, y) => [x, y + 1], preview));
- res.set('^', preview => editorState.applyCoordinateTransform((x, y) => [x, y - 1], preview));
- res.set('.', preview => editorState.applyCoordinateTransform((x, y) => [x + 0.5, y + 0.5], preview));
+ res.set('shift+>', preview => editorState.applyCoordinateTransform((x, y) => [x + 1, y], preview, false));
+ res.set('shift+<', preview => editorState.applyCoordinateTransform((x, y) => [x - 1, y], preview, false));
+ res.set('shift+v', preview => editorState.applyCoordinateTransform((x, y) => [x, y + 1], preview, false));
+ res.set('shift+^', preview => editorState.applyCoordinateTransform((x, y) => [x, y - 1], preview, false));
+ res.set('>', preview => editorState.applyCoordinateTransform((x, y) => [x + 1, y], preview, false));
+ res.set('<', preview => editorState.applyCoordinateTransform((x, y) => [x - 1, y], preview, false));
+ res.set('v', preview => editorState.applyCoordinateTransform((x, y) => [x, y + 1], preview, false));
+ res.set('^', preview => editorState.applyCoordinateTransform((x, y) => [x, y - 1], preview, false));
+ res.set('.', preview => editorState.applyCoordinateTransform((x, y) => [x + 0.5, y + 0.5], preview, false));
/**
* @param {!Array} chords
@@ -436,12 +472,12 @@ function handleKeyboardEvent(ev) {
editorState.chorder.handleKeyEvent(ev);
if (ev.type === 'keydown') {
if (ev.key.toLowerCase() === 'q') {
- let d = ev.shiftKey ? 10 : 1;
+ let d = ev.shiftKey ? 5 : 1;
editorState.changeCurLayerTo(editorState.curLayer - d);
return;
}
if (ev.key.toLowerCase() === 'e') {
- let d = ev.shiftKey ? 10 : 1;
+ let d = ev.shiftKey ? 5 : 1;
editorState.changeCurLayerTo(editorState.curLayer + d);
return;
}
@@ -514,3 +550,17 @@ window.addEventListener('focus', () => {
window.addEventListener('blur', () => {
editorState.chorder.handleFocusChanged();
});
+
+// Intercept clicks on the example circuit links, and load them without actually reloading the page, to preserve undo history.
+for (let anchor of document.getElementById('examples-div').querySelectorAll('a')) {
+ anchor.onclick = ev => {
+ // Don't stop the user from e.g. opening the example in a new tab using ctrl+click.
+ if (ev.shiftKey || ev.ctrlKey || ev.altKey || ev.button !== 0) {
+ return undefined;
+ }
+ let circuitText = anchor.href.split('#circuit=')[1];
+
+ editorState.rev.commit(circuitText);
+ return false;
+ };
+}