Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion glue/crumble/draw/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,10 @@ const rad = 10;
const OFFSET_X = -pitch + Math.floor(pitch / 4) + 0.5;
const OFFSET_Y = -pitch + Math.floor(pitch / 4) + 0.5;

export {pitch, rad, OFFSET_X, OFFSET_Y};
const MIN_ZOOM = 0.25;
const MAX_ZOOM = 4;

const MAX_QUBIT_COORDINATE = 100;
const LABEL_GAP = 20;

export {pitch, rad, OFFSET_X, OFFSET_Y, MIN_ZOOM, MAX_ZOOM, MAX_QUBIT_COORDINATE, LABEL_GAP};
82 changes: 60 additions & 22 deletions glue/crumble/draw/main_draw.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {pitch, rad, OFFSET_X, OFFSET_Y} from "./config.js"
import {pitch, rad, OFFSET_X, OFFSET_Y, MAX_QUBIT_COORDINATE, LABEL_GAP} from "./config.js"
import {marker_placement} from "../gates/gateset_markers.js";
import {drawTimeline} from "./timeline_viewer.js";
import {PropagatedPauliFrames} from "../circuit/propagated_pauli_frames.js";
Expand Down Expand Up @@ -193,6 +193,18 @@ function defensiveDraw(ctx, body) {
}
}

function switchToScreenCoordinates(ctx) {
const devicePixelRatio = window.devicePixelRatio || 1;
ctx.setTransform(devicePixelRatio, 0, 0, devicePixelRatio, 0, 0);
}

function switchToTransformationCoordinates(ctx, snap) {
const devicePixelRatio = window.devicePixelRatio || 1;
const zoom = snap.viewportZoom;
ctx.setTransform(zoom * devicePixelRatio, 0, 0, zoom * devicePixelRatio,
snap.viewportX * devicePixelRatio, snap.viewportY * devicePixelRatio);
}

/**
* @param {!CanvasRenderingContext2D} ctx
* @param {!StateSnapshot} snap
Expand Down Expand Up @@ -258,8 +270,49 @@ function draw(ctx, snap) {
}

defensiveDraw(ctx, () => {
ctx.fillStyle = 'white';
switchToScreenCoordinates(ctx);
ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);

// Draw grid tick-mark labels.
defensiveDraw(ctx, () => {
ctx.fillStyle = 'black';

let tickMarkInterval = 0.5;
if (snap.viewportZoom < 0.85) tickMarkInterval = 1;
if (snap.viewportZoom < 0.3) tickMarkInterval = 2;

ctx.save();
ctx.beginPath();
ctx.rect(LABEL_GAP, 0, ctx.canvas.clientWidth - LABEL_GAP, ctx.canvas.clientHeight);
ctx.clip();
for (let qx = 0; qx < MAX_QUBIT_COORDINATE; qx += tickMarkInterval) {
let [x, _] = c2dCoordTransform(qx, 0);
const screenX = x * snap.viewportZoom + snap.viewportX;
let s = `${qx}`;
ctx.fillText(s, screenX - ctx.measureText(s).width / 2, 15);
}
ctx.restore();

ctx.save();
ctx.beginPath();
ctx.rect(0, LABEL_GAP, ctx.canvas.clientWidth, ctx.canvas.clientHeight - LABEL_GAP);
ctx.clip();
for (let qy = 0; qy < MAX_QUBIT_COORDINATE; qy += tickMarkInterval) {
let [_, y] = c2dCoordTransform(0, qy);
const screenY = y * snap.viewportZoom + snap.viewportY;
let s = `${qy}`;
ctx.fillText(s, 18 - ctx.measureText(s).width, screenY);
}
ctx.restore();
});

// Apply clipping on all content so it doesn't overlap on tick labels.
ctx.save();
ctx.beginPath();
ctx.rect(LABEL_GAP, LABEL_GAP, ctx.canvas.clientWidth, ctx.canvas.clientHeight - LABEL_GAP);
ctx.clip();
switchToTransformationCoordinates(ctx, snap);

let [focusX, focusY] = xyToPos(snap.curMouseX, snap.curMouseY);

// Draw the background polygons.
Expand All @@ -282,26 +335,9 @@ function draw(ctx, snap) {

// Draw the grid of qubits.
defensiveDraw(ctx, () => {
for (let qx = 0; qx < 100; qx += 0.5) {
let [x, _] = c2dCoordTransform(qx, 0);
let s = `${qx}`;
ctx.fillStyle = 'black';
ctx.fillText(s, x - ctx.measureText(s).width / 2, 15);
}
for (let qy = 0; qy < 100; qy += 0.5) {
let [_, y] = c2dCoordTransform(0, qy);
let s = `${qy}`;
ctx.fillStyle = 'black';
ctx.fillText(s, 18 - ctx.measureText(s).width, y);
}

ctx.strokeStyle = 'black';
for (let qx = 0; qx < 100; qx += 0.5) {
let [x, _] = c2dCoordTransform(qx, 0);
let s = `${qx}`;
ctx.fillStyle = 'black';
ctx.fillText(s, x - ctx.measureText(s).width / 2, 15);
for (let qy = qx % 1; qy < 100; qy += 1) {
for (let qx = 0; qx < MAX_QUBIT_COORDINATE; qx += 0.5) {
for (let qy = qx % 1; qy < MAX_QUBIT_COORDINATE; qy += 1) {
let [x, y] = c2dCoordTransform(qx, qy);
ctx.fillStyle = 'white';
let isUnused = !usedQubitCoordSet.has(`${qx},${qy}`);
Expand Down Expand Up @@ -394,7 +430,8 @@ function draw(ctx, snap) {
});
});

drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords, circuit.layers.length);
switchToScreenCoordinates(ctx);
const timelineDrawSummary = drawTimeline(ctx, snap, propagatedMarkerLayers, qubitDrawCoords, circuit.layers.length);

// Draw scrubber.
ctx.save();
Expand Down Expand Up @@ -496,6 +533,7 @@ function draw(ctx, snap) {
ctx.restore();
}
ctx.restore(); // restore devicePixelRatio scale
return timelineDrawSummary;
}

export {xyToPos, draw, setDefensiveDrawEnabled, OFFSET_X, OFFSET_Y}
16 changes: 15 additions & 1 deletion glue/crumble/draw/state_snapshot.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ class StateSnapshot {
* @param {!number} mouseDownX
* @param {!number} mouseDownY
* @param {!Array<![!number, !number]>} boxHighlightPreview
* @param {!number} viewportX
* @param {!number} viewportY
* @param {!number} viewportZoom
* @param {!number} timelineOffsetX
* @param {!number} timelineOffsetY
* @param {!number} curMouseScreenX
* @param {!number} curMouseScreenY
*/
constructor(circuit, curLayer, focusedSet, timelineSet, curMouseX, curMouseY, mouseDownX, mouseDownY, boxHighlightPreview) {
constructor(circuit, curLayer, focusedSet, timelineSet, curMouseX, curMouseY, mouseDownX, mouseDownY, boxHighlightPreview, viewportX, viewportY, viewportZoom, timelineOffsetX, timelineOffsetY, curMouseScreenX, curMouseScreenY) {
this.circuit = circuit.copy();
this.curLayer = curLayer;
this.focusedSet = new Map(focusedSet.entries());
Expand All @@ -28,6 +35,13 @@ class StateSnapshot {
this.mouseDownX = mouseDownX;
this.mouseDownY = mouseDownY;
this.boxHighlightPreview = [...boxHighlightPreview];
this.viewportX = viewportX;
this.viewportY = viewportY;
this.viewportZoom = viewportZoom;
this.timelineOffsetX = timelineOffsetX;
this.timelineOffsetY = timelineOffsetY;
this.curMouseScreenX = curMouseScreenX;
this.curMouseScreenY = curMouseScreenY;

while (this.circuit.layers.length <= this.curLayer) {
this.circuit.layers.push(new Layer());
Expand Down
104 changes: 80 additions & 24 deletions glue/crumble/draw/timeline_viewer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import {OFFSET_Y, rad} from "./config.js";
import {rad} from "./config.js";
import {stroke_connector_to} from "../gates/gate_draw_util.js"
import {marker_placement} from '../gates/gateset_markers.js';

let TIMELINE_PITCH = 32;
const TIMELINE_PITCH = 32;
const QUBIT_HIGHLIGHT_SIZE = 40;

// Timeline panel changes size dynamically. These values are collected during draw and are used for restricting panning outside the relevant bounds.
class DrawSummary {
/**
* @param {!number} minOffsetX - Minimum allowed offsetX for timeline panel
* @param {!number} maxOffsetX - Maximum allowed offsetX for timeline panel
* @param {!number} maxOffsetY - Maximum Y offset for timeline panel
*/
constructor(minOffsetX, maxOffsetX, maxOffsetY) {
this.minOffsetX = minOffsetX;
this.maxOffsetX = maxOffsetX;
this.maxOffsetY = maxOffsetY;
}
}

/**
* @param {!CanvasRenderingContext2D} ctx
Expand Down Expand Up @@ -110,6 +125,7 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
return x1 - x2;
});

// Calculate base coordinates.
let base_y2xy = new Map();
let prev_y = undefined;
let cur_x = 0;
Expand All @@ -132,11 +148,30 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
base_y2xy.set(`${x},${y}`, [Math.round(cur_x) + 0.5, Math.round(cur_y) + 0.5]);
}

let x_pitch = TIMELINE_PITCH + Math.ceil(rad*max_run*0.25);
let num_cols_half = Math.floor(ctx.canvas.clientWidth / 4 / x_pitch);

const x_pitch = TIMELINE_PITCH + Math.ceil(rad*max_run*0.25);
const num_cols_half = Math.floor(ctx.canvas.clientWidth / 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);


const maxOffsetY = Math.max(0, cur_y - ctx.canvas.clientHeight + TIMELINE_PITCH);
const offsetY = Math.max(-maxOffsetY, Math.min(0, snap.timelineOffsetY ?? 0));

const lastLayerOffset = (numLayers - 1 - snap.curLayer - (min_t_clamp - min_t_free)) * x_pitch;
const minOffsetX = -Math.max(0, lastLayerOffset - 0.5 * w + 2*x_pitch);
const maxOffsetX = Math.max(0, (min_t_clamp - 1) * x_pitch);
const offsetX = Math.max(minOffsetX, Math.min(maxOffsetX, snap.timelineOffsetX ?? 0));

// Apply x/y offset to base coordinates
if (offsetY !== 0 || offsetX !== 0) {
for (let [key, [x, y]] of base_y2xy) {
base_y2xy.set(key, [x + offsetX, y + offsetY]);
}
}

const label_col_x = w * 1.5 + (min_t_free - 1 - snap.curLayer) * x_pitch;

let t2t = t => {
let dt = t - snap.curLayer;
dt -= min_t_clamp - min_t_free;
Expand All @@ -159,17 +194,23 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
try {
ctx.clearRect(w, 0, w, ctx.canvas.clientHeight);

// Apply clipping to prevent content from overlapping on labels and outside timeline panel.
ctx.save();
ctx.beginPath();
ctx.rect(label_col_x, 0, ctx.canvas.clientWidth - label_col_x, ctx.canvas.clientHeight);
ctx.clip();

// 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_clamp, max_t, x_pitch, hitCounts);
drawTimelineMarkers(ctx, snap, qubitTimeCoords, p, mi, 0, numLayers, x_pitch, hitCounts);
}

// Draw highlight of current layer.
ctx.globalAlpha *= 0.5;
ctx.fillStyle = 'black';
{
let x1 = t2t(snap.curLayer) + w * 1.5 - x_pitch / 2;
let x1 = t2t(snap.curLayer) + w * 1.5 - x_pitch / 2 + offsetX;
ctx.fillRect(x1, 0, x_pitch, ctx.canvas.clientHeight);
}
ctx.globalAlpha *= 2;
Expand All @@ -179,26 +220,16 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun

// Draw wire lines.
for (let q of qubits) {
let [x0, y0] = qubitTimeCoords(q, min_t_clamp - 1);
let [x1, y1] = qubitTimeCoords(q, max_t + 1);
let [x0, y0] = qubitTimeCoords(q, -1);
let [x1, y1] = qubitTimeCoords(q, numLayers);
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
}

// Draw wire labels.
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let q of qubits) {
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);
}

// Draw layers of gates.
for (let time = min_t_clamp; time <= max_t; time++) {
for (let time = 0; time < numLayers; time++) {
let qubitsCoordsFuncForLayer = q => qubitTimeCoords(q, time);
let layer = snap.circuit.layers[time];
if (layer === undefined) {
Expand All @@ -209,24 +240,49 @@ function drawTimeline(ctx, snap, propagatedMarkerLayers, timesliceQubitCoordsFun
}
}

ctx.restore(); // Stop clipping since labels and links to timeslice should be outside clipping area.

// Draw wire labels.
ctx.strokeStyle = 'black';
ctx.fillStyle = 'black';
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
for (let q of qubits) {
let [x, y] = qubitTimeCoords(q, min_t_clamp - 1);
x -= offsetX; // Labels are frozen on horizontal axis.
let qx = snap.circuit.qubitCoordData[q * 2];
let qy = snap.circuit.qubitCoordData[q * 2 + 1];
ctx.fillText(`${qx},${qy}:`, x, y);
}

// Draw links to timeslice viewer.
ctx.globalAlpha = 0.5;
const mouseScreenX = snap.curMouseScreenX;
const mouseScreenY = snap.curMouseScreenY;
const zoom = snap.viewportZoom;

for (let q of qubits) {
let [x0, y0] = qubitTimeCoords(q, min_t_clamp - 1);
let [x1, y1] = timesliceQubitCoordsFunc(q);
if (snap.curMouseX > ctx.canvas.clientWidth / 2 && snap.curMouseY >= y0 + OFFSET_Y - TIMELINE_PITCH * 0.55 && snap.curMouseY <= y0 + TIMELINE_PITCH * 0.55 + OFFSET_Y) {
x0 -= offsetX; // Lines start at frozen position to match labels.
const [wx1, wy1] = timesliceQubitCoordsFunc(q);
// Convert from world to screen coordinates for qubit highlight.
const x1 = wx1 * zoom + snap.viewportX;
const y1 = wy1 * zoom + snap.viewportY;
if (mouseScreenX > ctx.canvas.clientWidth / 2 && mouseScreenY >= y0 - TIMELINE_PITCH * 0.55 && mouseScreenY <= y0 + TIMELINE_PITCH * 0.55) {
ctx.beginPath();
ctx.moveTo(x0, y0);
ctx.lineTo(x1, y1);
ctx.stroke();
ctx.fillStyle = 'black';
ctx.fillRect(x1 - 20, y1 - 20, 40, 40);
ctx.fillRect(x1 - (QUBIT_HIGHLIGHT_SIZE/2) * zoom, y1 - (QUBIT_HIGHLIGHT_SIZE/2) * zoom, QUBIT_HIGHLIGHT_SIZE * zoom, QUBIT_HIGHLIGHT_SIZE * zoom);
ctx.fillRect(ctx.canvas.clientWidth / 2, y0 - TIMELINE_PITCH / 3, ctx.canvas.clientWidth / 2, TIMELINE_PITCH * 2 / 3);
}
}
} finally {
ctx.restore();
}

return new DrawSummary(minOffsetX, maxOffsetX, maxOffsetY);
}

export {drawTimeline}
export {drawTimeline, DrawSummary}
Loading
Loading