Skip to content

Commit

Permalink
g reverse layers, f flip R/M, show/hide examples, color code example,…
Browse files Browse the repository at this point in the history
… timeline viewer left justified
  • Loading branch information
Strilanc committed May 30, 2024
1 parent 64595f4 commit 5491029
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 44 deletions.
8 changes: 4 additions & 4 deletions glue/crumble/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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.
Expand Down
29 changes: 23 additions & 6 deletions glue/crumble/crumble.html

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion glue/crumble/draw/main_draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
39 changes: 27 additions & 12 deletions glue/crumble/draw/timeline_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,9 @@ function drawTimelineMarkers(ctx, ds, qubitTimeCoordFunc, propagatedMarkers, mi,
* @param {!StateSnapshot} snap
* @param {!Map<!int, !PropagatedPauliFrames>} 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();
Expand Down Expand Up @@ -133,58 +134,72 @@ 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';
ctx.fillStyle = 'black';

// 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);
ctx.lineTo(x1, y1);
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) {
Expand All @@ -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();
Expand Down
7 changes: 4 additions & 3 deletions glue/crumble/editor/editor_state.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -244,7 +245,7 @@ class EditorState {
this.applyCoordinateTransform((x, y) => {
[x, y] = t1(x, y);
return t2(x, y);
}, preview);
}, preview, true);
}

/**
Expand Down
30 changes: 26 additions & 4 deletions glue/crumble/keyboard/toolbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
78 changes: 64 additions & 14 deletions glue/crumble/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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()) {
Expand All @@ -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<!string>} chords
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
};
}

0 comments on commit 5491029

Please sign in to comment.