src/ui/DisplayedCircuit.js
/**
* Copyright 2017 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {CachablePainting} from "../draw/CachablePainting.js"
import {CircuitDefinition} from "../circuit/CircuitDefinition.js"
import {CircuitStats} from "../circuit/CircuitStats.js"
import {Config} from "../Config.js"
import {DetailedError} from "../base/DetailedError.js"
import {equate} from "../base/Equate.js"
import {Format} from "../base/Format.js"
import {GateColumn} from "../circuit/GateColumn.js"
import {GateDrawParams} from "../draw/GateDrawParams.js"
import {GatePainting} from "../draw/GatePainting.js"
import {Hand} from "../ui/Hand.js"
import {MathPainter} from "../draw/MathPainter.js"
import {Point} from "../math/Point.js"
import {Matrix} from "../math/Matrix.js"
import {Rect} from "../math/Rect.js"
import {Util} from "../base/Util.js"
import {seq, Seq} from "../base/Seq.js"
import {paintBlochSphereDisplay} from "../gates/BlochSphereDisplay.js"
/** @type {!number} */
let CIRCUIT_OP_HORIZONTAL_SPACING = 10;
/** @type {!number} */
let CIRCUIT_OP_LEFT_SPACING = 35;
const SUPERPOSITION_GRID_LABEL_SPAN = 50;
const EXTRA_COLS_FOR_SINGLE_QUBIT_DISPLAYS = 2;
class DisplayedCircuit {
/**
*
* @param {!number} top
* @param {!CircuitDefinition} circuitDefinition
* @param {undefined|!int} compressedColumnIndex
* @param {undefined|!{col: !int, row: undefined|!int, resizeStyle: !boolean}} highlightedSlot
* @param {undefined|!int} extraWireStartIndex
* @private
*/
constructor(top, circuitDefinition, compressedColumnIndex, highlightedSlot, extraWireStartIndex) {
if (!Number.isFinite(top)) {
throw new DetailedError("Bad top", {top, circuitDefinition});
}
if (!(circuitDefinition instanceof CircuitDefinition)) {
throw new DetailedError("Bad circuitDefinition", {top, circuitDefinition});
}
/**
* @type {!number}
*/
this.top = top;
/**
* @type {!CircuitDefinition}
*/
this.circuitDefinition = circuitDefinition;
/**
* @type {undefined|!int}
* @private
*/
this._compressedColumnIndex = compressedColumnIndex;
/**
* @type {undefined|!{col: !int, row: undefined|!int, resizeStyle: !boolean}}
* @private
*/
this._highlightedSlot = highlightedSlot;
/**
* @type {undefined|!int}
* @private
*/
this._extraWireStartIndex = extraWireStartIndex;
}
/**
* @param {!number} top
* @returns {!DisplayedCircuit}
*/
static empty(top) {
return new DisplayedCircuit(
top,
new CircuitDefinition(Config.MIN_WIRE_COUNT, []),
undefined,
undefined,
undefined);
}
/**
* @returns {!boolean}
*/
isBeingEdited() {
return this._extraWireStartIndex !== undefined;
}
/**
* The number of wires that were in the circuit before picking up a gate, or the number that will be in the circuit
* after dropping a gate; whichever is larger.
* @returns {!int}
* @private
*/
_groundedWireCount() {
let pseudoCount =
this._extraWireStartIndex !== undefined && this._extraWireStartIndex !== Config.MAX_WIRE_COUNT ? 1 : 0;
let n = Math.max(Config.MIN_WIRE_COUNT, this.circuitDefinition.numWires) - pseudoCount;
return Math.max(n, this.circuitDefinition.minimumRequiredWireCount());
}
/**
* @param {!boolean=true} forTooltip
* @returns {!number}
*/
desiredHeight(forTooltip=false) {
if (forTooltip) {
return this.circuitDefinition.numWires * Config.WIRE_SPACING;
}
return this._groundedWireCount() * Config.WIRE_SPACING + 55;
}
/**
* @param {!boolean=true} forTooltip
* @returns {!number}
*/
desiredWidth(forTooltip=false) {
if (forTooltip) {
return this.opRect(this.circuitDefinition.columns.length - 1).right() + CIRCUIT_OP_LEFT_SPACING;
}
return this._rectForSuperpositionDisplay().right() + 101;
}
/**
* @param {!int} wireIndex
* @returns {!Rect}
*/
wireRect(wireIndex) {
if (wireIndex < 0) {
throw new DetailedError("Bad wireIndex", {wireIndex});
}
return new Rect(0, this.top + Config.WIRE_SPACING * wireIndex, Infinity, Config.WIRE_SPACING);
}
/**
* @param {!number} y
* @returns {!int}
*/
wireIndexAt(y) {
return Math.floor((y - this.top) / Config.WIRE_SPACING);
}
//noinspection JSMethodCanBeStatic
/**
* @param {!number} x
* @returns {!number} The continuous column-space coordinate corresponding to the given display-space coordinate.
* @private
*/
toColumnSpaceCoordinate(x) {
let spacing = (CIRCUIT_OP_HORIZONTAL_SPACING + Config.GATE_RADIUS * 2);
let left = CIRCUIT_OP_LEFT_SPACING - CIRCUIT_OP_HORIZONTAL_SPACING / 2;
return (x - left) / spacing - 0.5;
}
/**
* @param {!number} y
* @returns {undefined|!int}
*/
indexOfDisplayedRowAt(y) {
let i = Math.floor((y - this.top) / Config.WIRE_SPACING);
if (i < 0 || i >= this.circuitDefinition.numWires) {
return undefined;
}
return i;
}
/**
* @param {!number} x
* @returns {undefined|!int}
*/
indexOfDisplayedColumnAt(x) {
let col = this.toColumnSpaceCoordinate(x);
let i;
if (this._compressedColumnIndex === undefined || col < this._compressedColumnIndex - 0.75) {
i = Math.round(col);
} else if (col < this._compressedColumnIndex - 0.25) {
i = this._compressedColumnIndex;
} else {
i = Math.round(col) - 1;
}
if (i < 0 || i >= this.circuitDefinition.columns.length) {
return undefined;
}
return i;
}
/**
* @param {!Point} p
* @returns {undefined|!number}
*/
findOpHalfColumnAt(p) {
if (p.x < 0 || p.y < this.top || p.y > this.top + this.desiredHeight()) {
return undefined;
}
return Math.max(-0.5, Math.round(this.toColumnSpaceCoordinate(p.x) * 2) / 2);
}
/**
* @param {!Hand} hand
* @returns {undefined|!{col: !int, row: !int, halfColIndex: !number}}
* @private
*/
_findModificationIndex_helperColRow(hand) {
if (hand.pos === undefined || hand.heldGate === undefined) {
return undefined;
}
let pos = hand.pos.minus(hand.holdOffset).plus(new Point(Config.GATE_RADIUS, Config.GATE_RADIUS));
let halfColIndex = this.findOpHalfColumnAt(pos);
let row = this.indexOfDisplayedRowAt(pos.y);
if (halfColIndex === undefined || row === undefined) {
return undefined;
}
let col = Math.ceil(halfColIndex);
return {col, row, halfColIndex};
}
/**
* @param {!Hand} hand
* @returns {?{ col : !number, row : !number, isInsert : !boolean }}
*/
findModificationIndex(hand) {
let loc = this._findModificationIndex_helperColRow(hand);
if (loc === undefined) {
return undefined;
}
let {col, row, halfColIndex} = loc;
let isInsert = Math.abs(halfColIndex % 1) === 0.5;
if (col >= this.circuitDefinition.columns.length) {
return {col: col, row: row, isInsert: isInsert};
}
if (!isInsert) {
let mustInsert = this.circuitDefinition.isSlotRectCoveredByGateInSameColumn(
col, row, hand.heldGate.height);
if (mustInsert) {
let isAfter = hand.pos.x > this.opRect(col).center().x;
isInsert = true;
if (isAfter) {
col += 1;
}
}
}
return {col: col, row: row, isInsert: isInsert};
}
/**
* @param {!int} operationIndex
* @returns {Rect!}
*/
opRect(operationIndex) {
let opWidth = Config.GATE_RADIUS * 2;
let opSeparation = opWidth + CIRCUIT_OP_HORIZONTAL_SPACING;
let tweak = 0;
if (this._compressedColumnIndex !== undefined && operationIndex === this._compressedColumnIndex) {
tweak = opSeparation / 2;
}
if (this._compressedColumnIndex !== undefined && operationIndex > this._compressedColumnIndex) {
tweak = opSeparation;
}
let dx = opSeparation * operationIndex - tweak + CIRCUIT_OP_LEFT_SPACING;
return new Rect(dx, this.top, opWidth, this.desiredHeight());
}
/**
* @param {!int} wireIndex
* @param {!int} operationIndex
* @param {!int=} width
* @param {!int=} height
*/
gateRect(wireIndex, operationIndex, width=1, height=1) {
let op = this.opRect(operationIndex);
let wire = this.wireRect(wireIndex);
let r = new Rect(
op.center().x - Config.GATE_RADIUS,
wire.center().y - Config.GATE_RADIUS,
2*Config.GATE_RADIUS + (width-1)*Config.WIRE_SPACING,
2*Config.GATE_RADIUS + (height-1)*Config.WIRE_SPACING);
return new Rect(Math.round(r.x - 0.5) + 0.5, Math.round(r.y - 0.5) + 0.5, Math.round(r.w), Math.round(r.h));
}
/**
* @returns {!DisplayedCircuit}
*/
afterTidyingUp() {
return this.
withCircuit(this.circuitDefinition.
withUncoveredColumnsRemoved().
withHeightOverlapsFixed().
withWidthOverlapsFixed().
withUncoveredColumnsRemoved().
withTrailingSpacersIncluded()).
_withCompressedColumnIndex(undefined).
_withExtraWireStartIndex(undefined).
_withHighlightedSlot(undefined);
}
/**
* @param {!DisplayedCircuit|*} other
* @returns {!boolean}
*/
isEqualTo(other) {
if (this === other) {
return true;
}
return other instanceof DisplayedCircuit &&
this.top === other.top &&
this.circuitDefinition.isEqualTo(other.circuitDefinition) &&
this._compressedColumnIndex === other._compressedColumnIndex &&
this._extraWireStartIndex === other._extraWireStartIndex &&
equate(this._highlightedSlot, other._highlightedSlot);
}
/**
* @param {!Painter} painter
* @param {!Hand} hand
* @param {!CircuitStats} stats
* @param {!boolean=false} forTooltip
* @param {!boolean} showWires
*/
paint(painter, hand, stats, forTooltip=false, showWires=true) {
if (showWires) {
this._drawWires(painter, !forTooltip, hand);
}
for (let col = 0; col < this.circuitDefinition.columns.length; col++) {
this._drawColumn(painter, this.circuitDefinition.columns[col], col, hand, stats);
}
if (!forTooltip) {
this._drawOutputDisplays(painter, stats, hand);
this._drawHintLabels(painter, stats);
}
this._drawRowDragHighlight(painter);
}
/**
* @param {!Painter} painter
* @param {!boolean} showLabels
* @param {!Hand} hand
* @private
*/
_drawWires(painter, showLabels, hand) {
let drawnWireCount = Math.min(this.circuitDefinition.numWires, (this._extraWireStartIndex || Infinity) + 1);
// Initial value labels
if (showLabels) {
for (let row = 0; row < drawnWireCount; row++) {
let wireRect = this.wireRect(row);
let y = wireRect.center().y;
let v = this.circuitDefinition.customInitialValues.get(row);
if (v === undefined) {
v = '0';
}
let rect = this._wireInitialStateClickableRect(row);
painter.noteTouchBlocker({rect, cursor: 'pointer'});
if (this._highlightedSlot === undefined && hand.pos !== undefined && rect.containsPoint(hand.pos)) {
painter.fillRect(rect, Config.HIGHLIGHTED_GATE_FILL_COLOR);
}
painter.print(`|${v}⟩`, 20, y, 'right', 'middle', 'black', '14px sans-serif', 20, Config.WIRE_SPACING);
}
}
// Wires (doubled-up for measured sections).
painter.ctx.save();
for (let row = 0; row < drawnWireCount; row++) {
if (row === this._extraWireStartIndex) {
painter.ctx.globalAlpha *= 0.5;
}
painter.trace(trace => {
let wireRect = this.wireRect(row);
let y = Math.round(wireRect.center().y - 0.5) + 0.5;
let lastX = showLabels ? 25 : 5;
//noinspection ForLoopThatDoesntUseLoopVariableJS
for (let col = 0;
showLabels ? lastX < painter.canvas.width : col <= this.circuitDefinition.columns.length;
col++) {
let x = this.opRect(col).center().x;
if (this.circuitDefinition.locIsMeasured(new Point(col, row))) {
// Measured wire.
trace.line(lastX, y-1, x, y-1);
trace.line(lastX, y+1, x, y+1);
} else {
// Unmeasured wire.
trace.line(lastX, y, x, y);
}
lastX = x;
}
}).thenStroke('black');
}
painter.ctx.restore();
if (this._extraWireStartIndex !== undefined && this.circuitDefinition.numWires === Config.MAX_WIRE_COUNT) {
painter.print(
`(Max wires. Qubit limit is ${Config.MAX_WIRE_COUNT}.)`,
5,
this.wireRect(Config.MAX_WIRE_COUNT).y,
'left',
'top',
'red',
'16px bold monospace',
400,
Config.WIRE_SPACING);
}
}
/**
* @param {!int} col
* @param {!int} row
* @param {!Array.<!Point>} focusPosPts
* @returns {!{isHighlighted: !boolean, isResizeShowing: !boolean, isResizeHighlighted: !boolean}}
* @private
*/
_highlightStatusAt(col, row, focusPosPts) {
if (this._highlightedSlot !== undefined) {
if (this._highlightedSlot.col === col && this._highlightedSlot.row === row) {
return {
isResizeShowing: true,
isResizeHighlighted: this._highlightedSlot.resizeStyle,
isHighlighted: !this._highlightedSlot.resizeStyle
};
}
}
let gate = this.circuitDefinition.gateInSlot(col, row);
if (gate === undefined || this._highlightedSlot !== undefined) {
return {
isResizeShowing: false,
isResizeHighlighted: false,
isHighlighted: false
};
}
let gateRect = this.gateRect(row, col, gate.width, gate.height);
let resizeTabRect = GatePainting.rectForResizeTab(gateRect);
let isOverGate = pos => {
let overGate = this.findGateOverlappingPos(pos);
return overGate !== undefined && overGate.col === col && overGate.row === row;
};
let isNotCoveredAt = pos => {
let g = this.findGateOverlappingPos(pos);
return g === undefined || (g.col === col && g.row === row);
};
let isOverGateResizeTab = pos => isNotCoveredAt(pos) && resizeTabRect.containsPoint(pos);
let isResizeHighlighted = gate.canChangeInSize() && seq(focusPosPts).any(isOverGateResizeTab);
let isHighlighted = !isResizeHighlighted && seq(focusPosPts).any(isOverGate);
let isResizeShowing = gate.canChangeInSize() && (isResizeHighlighted || isHighlighted);
return {isHighlighted, isResizeShowing, isResizeHighlighted};
}
/**
* @param {!Painter} painter
* @param {!int} col
* @param {!int} row
* @param {!Rect} gateRect
* @param {!boolean} isHighlighted
* @private
*/
_drawGate_disabledReason(painter, col, row, gateRect, isHighlighted) {
let isDisabledReason = this.circuitDefinition.gateAtLocIsDisabledReason(col, row);
if (isDisabledReason === undefined) {
return;
}
painter.ctx.save();
if (isHighlighted) {
painter.ctx.globalAlpha *= 0.3;
}
painter.ctx.globalAlpha *= 0.5;
painter.fillRect(gateRect.paddedBy(5), 'yellow');
painter.ctx.globalAlpha *= 2;
painter.strokeLine(gateRect.topLeft(), gateRect.bottomRight(), 'orange', 3);
let r = painter.printParagraph(isDisabledReason, gateRect.paddedBy(5), new Point(0.5, 0.5), 'red');
painter.ctx.globalAlpha *= 0.5;
painter.fillRect(r.paddedBy(2), 'yellow');
painter.ctx.globalAlpha *= 2;
painter.printParagraph(isDisabledReason, gateRect.paddedBy(5), new Point(0.5, 0.5), 'red');
painter.ctx.restore()
}
/**
* @param {!Painter} painter
* @param {!GateColumn} gateColumn
* @param {!int} col
* @param {!Hand} hand
* @param {!CircuitStats} stats
* @private
*/
_drawColumn(painter, gateColumn, col, hand, stats) {
this._drawColumnControlWires(painter, col);
this._drawColumnDragHighlight(painter, col);
for (let row = 0; row < this.circuitDefinition.numWires; row++) {
if (gateColumn.gates[row] === undefined) {
continue;
}
let gate = gateColumn.gates[row];
let gateRect = this.gateRect(row, col, gate.width, gate.height);
let {isHighlighted, isResizeShowing, isResizeHighlighted} =
this._highlightStatusAt(col, row, hand.hoverPoints());
let drawer = gate.customDrawer || GatePainting.DEFAULT_DRAWER;
painter.noteTouchBlocker({rect: gateRect, cursor: 'pointer'});
if (gate.canChangeInSize()) {
painter.noteTouchBlocker({rect: GatePainting.rectForResizeTab(gateRect), cursor: 'ns-resize'});
}
drawer(new GateDrawParams(
painter,
hand,
false,
isHighlighted && !isResizeHighlighted,
isResizeShowing,
isResizeHighlighted,
gateRect,
gate,
stats,
{row, col},
this._highlightedSlot === undefined ? hand.hoverPoints() : [],
stats.customStatsForSlot(col, row)));
this._drawGate_disabledReason(painter, col, row, gateRect, isHighlighted);
}
this._drawColumnSurvivalRate(painter, gateColumn, col, stats);
}
/**
* @param {!Painter} painter
* @param {!GateColumn} gateColumn
* @param {!int} col
* @param {!CircuitStats} stats
* @private
*/
_drawColumnSurvivalRate(painter, gateColumn, col, stats) {
if (gateColumn.indexOfNonUnitaryGate() === undefined) {
return;
}
let preRate = stats.survivalRate(col - 1);
let postRate = stats.survivalRate(col);
let marginalRate = (postRate - preRate) / preRate;
if (isNaN(marginalRate) || Math.abs(marginalRate) <= 0.005) {
return;
}
let descAmount;
let descCategory;
if (marginalRate < 0) {
let rate = Math.round(-marginalRate * 100);
let rateDesc = marginalRate === -1 ? "100" : rate < 100 ? rate : ">99";
descAmount = `${rateDesc}%`;
descCategory = 'omits';
} else {
let factor = Math.round(marginalRate * 100 + 100);
descAmount = `${factor}%`;
descCategory = 'gains';
}
let pt = this.opRect(col).bottomCenter();
painter.print(
descCategory,
pt.x,
pt.y - 28,
'center',
'bottom',
'red',
'14px sans-serif',
800,
50);
painter.print(
descAmount,
pt.x,
pt.y - 13,
'center',
'bottom',
'red',
'14px sans-serif',
800,
50);
}
_drawColumnDragHighlight(painter, col) {
if (this._highlightedSlot !== undefined &&
this._highlightedSlot.col === col &&
this._highlightedSlot.row === undefined) {
let rect = this.gateRect(0, col, 1, this._groundedWireCount()).paddedBy(3);
painter.fillRect(rect, 'rgba(255, 196, 112, 0.7)');
painter.strokeRect(rect, 'black');
}
}
/**
* @param {!Painter} painter
* @private
*/
_drawRowDragHighlight(painter) {
if (this._highlightedSlot !== undefined &&
this._highlightedSlot.col === undefined &&
this._highlightedSlot.row !== undefined) {
let row = this._highlightedSlot.row;
let w = this.gateRect(row, this.clampedCircuitColCount() + 1).x;
let rect = this.wireRect(row).takeLeft(w);
painter.fillRect(rect, 'rgba(255, 196, 112, 0.7)');
painter.strokeRect(rect, 'black');
}
}
/**
* @param {!Painter} painter
* @param {!int} columnIndex
* @private
*/
_drawColumnControlWires(painter, columnIndex) {
let x = Math.round(this.opRect(columnIndex).center().x - 0.5) + 0.5;
// Dashed line indicates effects from non-unitary gates may affect, or appear to affect, other wires.
if (this.circuitDefinition.columns[columnIndex].hasGatesWithGlobalEffects()) {
painter.ctx.save();
painter.ctx.setLineDash([1, 4]);
painter.strokeLine(
new Point(x, this.gateRect(0, 0).y),
new Point(x, this.opRect(0).bottom() - 40));
painter.ctx.restore();
}
for (let {first, last, measured} of this.circuitDefinition.controlLinesRanges(columnIndex)) {
let y1 = this.wireRect(first).center().y;
let y2 = this.wireRect(last).center().y;
if (measured) {
painter.strokeLine(new Point(x+1, y1), new Point(x+1, y2));
painter.strokeLine(new Point(x-1, y1), new Point(x-1, y2));
} else {
painter.strokeLine(new Point(x, y1), new Point(x, y2));
}
}
}
/**
* @param {!Hand} hand
* @returns {!DisplayedCircuit}
*/
previewDrop(hand) {
return hand.heldRow !== undefined ? this._previewDropMovedRow(hand) :
hand.heldColumn !== undefined ? this._previewDropMovedGateColumn(hand) :
hand.heldGate !== undefined ? this._previewDropMovedGate(hand) :
this._previewResizedGate(hand);
}
/**
* @param {!Hand} hand
* @returns {!DisplayedCircuit}
* @private
*/
_previewDropMovedRow(hand) {
if (hand.pos === undefined) {
return this;
}
let handWire = this.wireIndexAt(hand.pos.y);
if (handWire < 0 || handWire >= this.circuitDefinition.numWires) {
// Dragged the row out of the circuit.
return this;
}
let heldRowHeight = seq(hand.heldRow.gates).map(g => g === undefined ? 1 : g.height).max(1);
handWire = Math.min(handWire, this.circuitDefinition.numWires - heldRowHeight);
let newCols = [];
for (let c = 0; c < this.circuitDefinition.columns.length; c++) {
let gates = [...this.circuitDefinition.columns[c].gates];
gates.splice(handWire, 0, hand.heldRow.gates[c]);
gates.pop();
newCols.push(new GateColumn(gates));
}
let newInitialStates = seq(this.circuitDefinition.customInitialValues.entries()).
map(([k, v]) => [k + (k >= handWire ? 1 : 0), v]).
toMap(([k, _]) => k, ([_, v]) => v);
if (hand.heldRow.initialState !== undefined) {
newInitialStates.set(handWire, hand.heldRow.initialState);
}
let newCircuitDef = this.circuitDefinition.withColumns(newCols).withInitialStates(newInitialStates);
return this.withCircuit(newCircuitDef).
_withHighlightedSlot({row: handWire, col: undefined, resizeStyle: false});
}
/**
* @param {!Hand} hand
* @returns {!DisplayedCircuit}
* @private
*/
_previewDropMovedGateColumn(hand) {
if (hand.pos === undefined) {
return this;
}
let handWire = this.wireIndexAt(hand.pos.y);
if (handWire < 0 || handWire >= Config.MAX_WIRE_COUNT || hand.pos.x <= 1) {
// Dragged the gate column out of the circuit.
return this;
}
let halfCol = this.findOpHalfColumnAt(new Point(hand.pos.x, this.top));
let mustInsert = halfCol % 1 === 0 &&
this.circuitDefinition.columns[halfCol] !== undefined &&
!this.circuitDefinition.columns[halfCol].isEmpty();
if (mustInsert) {
let isAfter = hand.pos.x > this.opRect(halfCol).center().x;
halfCol += isAfter ? 0.5 : -0.5;
}
let col = Math.ceil(halfCol);
let isInsert = halfCol % 1 !== 0;
let rowShift = Math.round((hand.pos.y - hand.holdOffset.y - this.top) / Config.WIRE_SPACING);
let newCircuitDef = this._shiftAndSpliceColumn(rowShift, [...hand.heldColumn.gates], col, isInsert);
return this.withCircuit(newCircuitDef).
_withHighlightedSlot({row: undefined, col, resizeStyle: false}).
_withCompressedColumnIndex(isInsert ? col : undefined);
}
_shiftAndSpliceColumn(rowShift, gatesOfCol, insertCol, isInsert) {
// Move gates upward.
while (rowShift < 0 && gatesOfCol[0] === undefined) {
gatesOfCol.shift();
gatesOfCol.push(undefined);
rowShift += 1;
}
// Shift gates downward.
while (rowShift > 0 && new GateColumn(gatesOfCol).minimumRequiredWireCount() < Config.MAX_WIRE_COUNT) {
gatesOfCol.unshift(undefined);
if (new GateColumn(gatesOfCol).minimumRequiredWireCount() < gatesOfCol.length) {
gatesOfCol.pop();
}
rowShift -= 1;
}
let expandedCircuit = this.circuitDefinition.withWireCount(gatesOfCol.length);
let newCols = [...expandedCircuit.columns];
// Move displays rightward.
while (newCols.length < insertCol) {
newCols.push(GateColumn.empty(expandedCircuit.numWires));
}
newCols.splice(insertCol, isInsert ? 0 : 1, new GateColumn(gatesOfCol));
return expandedCircuit.withColumns(newCols).withTrailingSpacersIncluded();
}
/**
* @param {!Hand} hand
* @returns {!DisplayedCircuit}
* @private
*/
_previewDropMovedGate(hand) {
let modificationPoint = this.findModificationIndex(hand);
if (modificationPoint === undefined) {
return this;
}
// Use the grab offset instead of the gate height so that tall gates are 'sticky' when dragging downward: they
// aren't removed until the hand actually leaves the circuit area.
let handRowOffset = Math.floor(hand.holdOffset.y/Config.WIRE_SPACING);
if (modificationPoint.row + handRowOffset >= this.circuitDefinition.numWires) {
return this;
}
let addedGate = hand.heldGate;
let emptyCol = GateColumn.empty(this.circuitDefinition.numWires);
let i = modificationPoint.col;
let isInserting = modificationPoint.isInsert;
let row = Math.min(modificationPoint.row, Math.max(0, Config.MAX_WIRE_COUNT - addedGate.height));
let newCols = seq(this.circuitDefinition.columns).
padded(i, emptyCol).
ifThen(isInserting, s => s.withInsertedItem(i, emptyCol)).
padded(i + addedGate.width, emptyCol).
withTransformedItem(i, c => c.withGatesAdded(row, new GateColumn([addedGate]))).
toArray();
let newWireCount = Math.max(
this._extraWireStartIndex || 0,
Math.max(
this.circuitDefinition.numWires,
addedGate.height + row));
if (newWireCount > Config.MAX_WIRE_COUNT) {
return this;
}
let newCircuitDef = this.circuitDefinition.
withColumns(newCols).
withWireCount(newWireCount);
return this.withCircuit(newCircuitDef).
_withHighlightedSlot({row, col: modificationPoint.col, resizeStyle: false}).
_withCompressedColumnIndex(isInserting ? i : undefined).
_withFallbackExtraWireStartIndex(this.circuitDefinition.numWires);
}
/**
* @param {!Hand} hand
* @returns {!DisplayedCircuit}
* @private
*/
_previewResizedGate(hand) {
if (hand.resizingGateSlot === undefined || hand.pos === undefined) {
return this;
}
let gate = this.circuitDefinition.gateInSlot(hand.resizingGateSlot.x, hand.resizingGateSlot.y);
if (gate === undefined) {
return this;
}
let row = Math.min(
this.wireIndexAt(hand.pos.y - hand.holdOffset.y),
Config.MAX_WIRE_COUNT - 1);
let newGate = seq(gate.gateFamily).minBy(g => Math.abs(g.height - (row - hand.resizingGateSlot.y + 1)));
let newWireCount = Math.min(Config.MAX_WIRE_COUNT,
Math.max(this.circuitDefinition.numWires, newGate.height + hand.resizingGateSlot.y));
let newCols = seq(this.circuitDefinition.columns).
withTransformedItem(hand.resizingGateSlot.x,
colObj => new GateColumn(seq(colObj.gates).
withOverlayedItem(hand.resizingGateSlot.y, newGate).
toArray())).
toArray();
let newCircuitWithoutOverlapFix = this.circuitDefinition.withColumns(newCols).withWireCount(newWireCount);
let newCircuitWithOverlapFix = newCircuitWithoutOverlapFix.withHeightOverlapsFixed();
let newCircuit = newCircuitWithOverlapFix.withTrailingSpacersIncluded();
return this.withCircuit(newCircuit).
_withHighlightedSlot(this._highlightedSlot).
_withCompressedColumnIndex(newCircuitWithoutOverlapFix.isEqualTo(newCircuitWithOverlapFix) ?
undefined :
hand.resizingGateSlot.x + 1).
_withFallbackExtraWireStartIndex(this.circuitDefinition.numWires);
}
/**
* @param {!Hand} hand
* @returns {!DisplayedCircuit}
*/
afterDropping(hand) {
return this.previewDrop(hand)._withCompressedColumnIndex(undefined);
}
/**
* @param {!CircuitDefinition} circuitDefinition
* @returns {!DisplayedCircuit}
*/
withCircuit(circuitDefinition) {
return new DisplayedCircuit(
this.top,
circuitDefinition,
this._compressedColumnIndex,
this._highlightedSlot,
this._extraWireStartIndex);
}
/**
* @param {undefined|!int} compressedColumnIndex
* @returns {!DisplayedCircuit}
* @private
*/
_withCompressedColumnIndex(compressedColumnIndex) {
return new DisplayedCircuit(
this.top,
this.circuitDefinition,
compressedColumnIndex,
this._highlightedSlot,
this._extraWireStartIndex);
}
/**
* @param {undefined|!{col: undefined|!int, row: undefined|!int, resizeStyle: !boolean}} slot
* @returns {!DisplayedCircuit}
* @private
*/
_withHighlightedSlot(slot) {
return new DisplayedCircuit(
this.top,
this.circuitDefinition,
this._compressedColumnIndex,
slot,
this._extraWireStartIndex);
}
/**
* @param {undefined|!int} extraWireStartIndex
* @returns {!DisplayedCircuit}
* @private
*/
_withExtraWireStartIndex(extraWireStartIndex) {
return new DisplayedCircuit(
this.top,
this.circuitDefinition,
this._compressedColumnIndex,
this._highlightedSlot,
extraWireStartIndex);
}
/**
* @param {undefined|!int} fallbackExtraWireStartIndex
* @returns {!DisplayedCircuit}
* @private
*/
_withFallbackExtraWireStartIndex(fallbackExtraWireStartIndex) {
return this._withExtraWireStartIndex(this._extraWireStartIndex || fallbackExtraWireStartIndex);
}
/**
* @param {!Hand} hand
* @param {!int} extraWireCount
* @returns {!DisplayedCircuit}
*/
withJustEnoughWires(hand, extraWireCount) {
let neededWireCountForPlacement = hand.heldGate !== undefined ? hand.heldGate.height : 0;
let desiredWireCount = this.circuitDefinition.minimumRequiredWireCount();
let clampedWireCount = Math.min(
Config.MAX_WIRE_COUNT,
Math.max(
Math.min(1, neededWireCountForPlacement),
Math.max(Config.MIN_WIRE_COUNT, desiredWireCount) + extraWireCount));
return this.withCircuit(this.circuitDefinition.withWireCount(clampedWireCount)).
_withExtraWireStartIndex(extraWireCount === 0 ? undefined : this.circuitDefinition.numWires);
}
/**
* @param {!Point} pos
* @returns {undefined|!{col: !int, row: !int, offset: !Point}}
*/
findGateOverlappingPos(pos) {
let col = this.indexOfDisplayedColumnAt(pos.x);
let row = this.indexOfDisplayedRowAt(pos.y);
if (col === undefined || row === undefined) {
return undefined;
}
let target = this.circuitDefinition.findGateCoveringSlot(col, row);
if (target === undefined) {
return undefined;
}
let gateRect = this.gateRect(target.row, target.col, target.gate.width, target.gate.height);
if (!gateRect.containsPoint(pos)) {
return undefined;
}
return {col: target.col, row: target.row, offset: pos.minus(gateRect.topLeft())};
}
/**
* @param {!Point} pos
* @returns {undefined|!{col: !int, row: !int, gate: !Gate}}
*/
findGateWithButtonContaining(pos) {
let foundPt = this.findGateOverlappingPos(pos);
if (foundPt === undefined) {
return undefined;
}
let gate = this.circuitDefinition.gateInSlot(foundPt.col, foundPt.row);
if (gate.onClickGateFunc === undefined) {
return undefined;
}
let buttonRect = GatePainting.gateButtonRect(this.gateRect(foundPt.row, foundPt.col, gate.width, gate.height));
if (!buttonRect.containsPoint(pos)) {
return undefined;
}
return {col: foundPt.col, row: foundPt.row, gate};
}
/**
* @param {!int} wire
* @returns {!Rect}
* @private
*/
_wireInitialStateClickableRect(wire) {
let r = this.wireRect(wire);
r.x = 0;
r.y += 5;
r.w = 30;
r.h -= 10;
return r;
}
/**
* @param {!Point} pt
* @returns {undefined|!int}
*/
findWireWithInitialStateAreaContaining(pt) {
// Is it in the right vertical band; the one at the start of the circuit?
if (pt.x < 0 || pt.x > 30) {
return undefined;
}
// Which wire is it? Is it one that's actually in the circuit?
let wire = this.wireIndexAt(pt.y);
if (wire < 0 || wire >= this.circuitDefinition.numWires) {
return undefined;
}
// Is it inside the intended click area, instead of just off to the side?
let r = this._wireInitialStateClickableRect(wire);
if (!r.containsPoint(pt)) {
return undefined;
}
// Good to go.
return wire;
}
/**
* @param {!Hand} hand
* @returns {undefined|!DisplayedCircuit}
*/
tryClick(hand) {
if (hand.pos === undefined || hand.heldGate !== undefined) {
return undefined;
}
let clickedInitialStateWire = this.findWireWithInitialStateAreaContaining(hand.pos);
if (clickedInitialStateWire !== undefined) {
return this.withCircuit(this.circuitDefinition.withSwitchedInitialStateOn(clickedInitialStateWire))
}
let found = this.findGateWithButtonContaining(hand.pos);
if (found === undefined) {
return undefined;
}
let newGate = found.gate.onClickGateFunc(found.gate);
let cols = [...this.circuitDefinition.columns];
let col = cols[found.col];
let gates = [...col.gates];
gates.splice(found.row, 1, newGate);
cols.splice(found.col, 1, new GateColumn(gates));
return this.withCircuit(this.circuitDefinition.withColumns(cols));
}
/**
* @param {!Hand} hand
* @param {!boolean=false} duplicate
* @param {!boolean=false} wholeColumn
* @param {!boolean=false} ignoreResizeTabs
* @param {!boolean=false} alt Whether or not to replace grabbed gates with their alternates.
* @returns {!{newCircuit: !DisplayedCircuit, newHand: !Hand}}
*/
tryGrab(hand, duplicate=false, wholeColumn=false, ignoreResizeTabs=false, alt=false) {
if (wholeColumn) {
let grabRowResult = this._tryGrabRow(hand, alt);
if (grabRowResult !== undefined) {
return grabRowResult;
}
return this._tryGrabWholeColumn(hand, duplicate, alt) || {newCircuit: this, newHand: hand};
}
let newHand = hand;
let newCircuit = this;
if (!ignoreResizeTabs) {
let resizing = this._tryGrabResizeTab(hand);
if (resizing !== undefined) {
newHand = resizing.newHand;
newCircuit = resizing.newCircuit;
}
}
return newCircuit._tryGrabGate(newHand, duplicate, alt) || {newCircuit, newHand};
}
/**
* @param {!Hand} hand
* @param {!boolean} alt
* @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}}
*/
_tryGrabRow(hand, alt) {
if (hand.pos === undefined) {
return undefined;
}
// Which wire is it? Is it one that's actually in the circuit?
let wire = this.wireIndexAt(hand.pos.y);
if (wire < 0 || wire >= this.circuitDefinition.numWires) {
return undefined;
}
// Is it inside the intended click area, instead of just off to the side?
let r = this._wireInitialStateClickableRect(wire);
if (!r.containsPoint(hand.pos)) {
return undefined;
}
let {newCircuit, initialState, rowGates} = this._cutRow(wire);
let holdOffset = new Point(0, hand.pos.y - r.y);
if (alt) {
rowGates = rowGates.map(e => e === undefined ? e : e.alternate);
}
return {
newCircuit: this.withCircuit(newCircuit),
newHand: hand.withHeldRow({initialState, gates: rowGates}, holdOffset)
};
}
/**
* @param {!int} row
* @returns {!{newCircuit: !CircuitDefinition, rowGates: !Array.<undefined|!Gate>, initialState: *}}
* @private
*/
_cutRow(row) {
let row_gates = [];
let cols = [];
for (let i = 0; i < this.circuitDefinition.columns.length; i++) {
let col_gates = [...this.circuitDefinition.columns[i].gates];
row_gates.push(col_gates[row]);
col_gates.splice(row, 1);
col_gates.push(undefined);
cols.push(new GateColumn(col_gates));
}
let newInitialStates = seq(this.circuitDefinition.customInitialValues.entries()).
filter(([k, _]) => k !== row).
map(([k, v]) => [k - (k > row ? 1 : 0), v]).
toMap(([k, _]) => k, ([_, v]) => v);
return {
newCircuit: this.circuitDefinition.withColumns(cols).withInitialStates(newInitialStates),
rowGates: row_gates,
initialState: this.circuitDefinition.customInitialValues.get(row)
};
}
/**
* @param {!Hand} hand
* @param {!boolean} duplicate
* @param {!boolean} alt
* @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}}
*/
_tryGrabGate(hand, duplicate, alt) {
if (hand.isBusy() || hand.pos === undefined) {
return undefined;
}
let foundPt = this.findGateOverlappingPos(hand.pos);
if (foundPt === undefined) {
return undefined;
}
let {col, row, offset} = foundPt;
let gate = this.circuitDefinition.columns[col].gates[row];
if (alt) {
gate = gate.alternate;
}
let remainingGates = seq(this.circuitDefinition.columns[col].gates).toArray();
if (!duplicate) {
remainingGates[row] = undefined;
}
let newCols = seq(this.circuitDefinition.columns).
withOverlayedItem(col, new GateColumn(remainingGates)).
toArray();
return {
newCircuit: new DisplayedCircuit(
this.top,
this.circuitDefinition.withColumns(newCols),
undefined,
undefined,
this._extraWireStartIndex),
newHand: hand.withHeldGate(gate, offset)
};
}
/**
* @param {!Hand} hand
* @returns {!{newCircuit: !DisplayedCircuit, newHand: !Hand}}
*/
_tryGrabResizeTab(hand) {
if (hand.isBusy() || hand.pos === undefined) {
return undefined;
}
for (let col = 0; col < this.circuitDefinition.columns.length; col++) {
for (let row = 0; row < this.circuitDefinition.numWires; row++) {
let gate = this.circuitDefinition.columns[col].gates[row];
if (gate === undefined) {
continue;
}
let {isResizeHighlighted} =
this._highlightStatusAt(col, row, hand.hoverPoints());
if (isResizeHighlighted) {
let offset = hand.pos.minus(this.gateRect(row + gate.height - 1, col, 1, 1).center());
return {
newCircuit: this._withHighlightedSlot({col, row, resizeStyle: true}),
newHand: hand.withResizeSlot(new Point(col, row), offset)
};
}
}
}
return undefined;
}
/**
* @param {!Hand} hand
* @param {!boolean} duplicate
* @param {!boolean} alt Whether or not to replace grabbed gates with their alternates.
* @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}}
* @private
*/
_tryGrabWholeColumn(hand, duplicate, alt) {
if (hand.isBusy() || hand.pos === undefined) {
return undefined;
}
let col = Math.round(this.toColumnSpaceCoordinate(hand.pos.x));
if (col < 0 || col >= this.circuitDefinition.columns.length || this.circuitDefinition.columns[col].isEmpty()) {
return undefined;
}
let newCols = [...this.circuitDefinition.columns];
if (!duplicate) {
newCols.splice(col, 1, GateColumn.empty(this.circuitDefinition.numWires));
}
let holdOffset = new Point(0, this.wireIndexAt(hand.pos.y) * Config.WIRE_SPACING + Config.WIRE_SPACING/2);
let grabbedGates = this.circuitDefinition.columns[col];
if (alt) {
grabbedGates = new GateColumn(grabbedGates.gates.map(e => e === undefined ? e : e.alternate));
}
return {
newCircuit: this.withCircuit(this.circuitDefinition.withColumns(newCols)),
newHand: hand.withHeldGateColumn(grabbedGates, holdOffset)
};
}
/**
* @returns {Infinity|!number}
*/
stableDuration() {
return this.circuitDefinition.stableDuration();
}
/**
* @returns {!int}
*/
importantWireCount() {
return Math.max(
this.circuitDefinition.numWires - (this._extraWireStartIndex === Config.MAX_WIRE_COUNT ? 0 : 1),
Config.MIN_WIRE_COUNT,
this.circuitDefinition.minimumRequiredWireCount());
}
/**
* Draws a peek gate on each wire at the right-hand side of the circuit.
*
* @param {!Painter} painter
* @param {!CircuitStats} stats
* @param {!Hand} hand
* @private
*/
_drawOutputDisplays(painter, stats, hand) {
let chanceCol = this.clampedCircuitColCount() + 1;
let blochCol = chanceCol + 1;
let numWire = this.importantWireCount();
for (let i = 0; i < numWire; i++) {
let p = stats.controlledWireProbabilityJustAfter(i, Infinity);
MathPainter.paintProbabilityBox(painter, p, this.gateRect(i, chanceCol), hand.hoverPoints());
let m = stats.qubitDensityMatrix(Infinity, i);
if (m !== undefined) {
paintBlochSphereDisplay(painter, m, this.gateRect(i, blochCol), hand.hoverPoints());
}
}
let bottom = this.wireRect(numWire-1).bottom();
let x = this.opRect(chanceCol - 1).x;
painter.printParagraph(
"Local wire states\n(Chance/Bloch)",
new Rect(x, bottom+4, 190, 40),
new Point(0.5, 0),
'gray');
this._drawOutputSuperpositionDisplay(painter, stats, hand);
}
/**
* @returns {!number} The number of columns used for drawing the circuit, before the output display.
*/
clampedCircuitColCount() {
return Math.max(
this.circuitDefinition.columns.length,
Config.MIN_COL_COUNT + (this._compressedColumnIndex !== undefined ? 1 : 0));
}
/**
* Draws a peek gate on each wire at the right-hand side of the circuit.
*
* @param {!Painter} painter
* @param {!CircuitStats} stats
* @param {!Hand} hand
* @private
*/
_drawOutputSuperpositionDisplay(painter, stats, hand) {
let amplitudeGrid = this._outputStateAsMatrix(stats);
let gridRect = this._rectForSuperpositionDisplay();
let numWire = this.importantWireCount();
MathPainter.paintMatrix(
painter,
amplitudeGrid,
gridRect,
numWire < Config.SIMPLE_SUPERPOSITION_DRAWING_WIRE_THRESHOLD ? Config.SUPERPOSITION_MID_COLOR : undefined,
'black',
numWire < Config.SIMPLE_SUPERPOSITION_DRAWING_WIRE_THRESHOLD ? Config.SUPERPOSITION_FORE_COLOR : undefined,
Config.SUPERPOSITION_BACK_COLOR);
let forceSign = v => (v >= 0 ? '+' : '') + v.toFixed(2);
MathPainter.paintMatrixTooltip(painter, amplitudeGrid, gridRect, hand.hoverPoints(),
(c, r) => `Amplitude of |${Util.bin(r*amplitudeGrid.width() + c, numWire)}⟩ (decimal ${r*amplitudeGrid.width() + c})`,
(c, r, v) => 'val:' + v.toString(new Format(false, 0, 5, ", ")),
(c, r, v) => `mag²:${(v.norm2()*100).toFixed(4)}%, phase:${forceSign(v.phase() * 180 / Math.PI)}°`);
this._drawOutputSuperpositionDisplay_labels(painter);
}
/**
* @param {!Painter} painter
* @private
*/
_drawOutputSuperpositionDisplay_labels(painter) {
let gridRect = this._rectForSuperpositionDisplay();
let numWire = this.importantWireCount();
_cachedRowLabelDrawer.paint(gridRect.right(), gridRect.y, painter, numWire);
_cachedColLabelDrawer.paint(gridRect.x, gridRect.bottom(), painter, numWire);
}
/**
* @param {!CircuitStats} stats
* @returns {!Matrix}
* @private
*/
_outputStateAsMatrix(stats) {
let numWire = this.importantWireCount();
let buf = stats.finalState.rawBuffer();
if (stats.circuitDefinition.numWires !== numWire) {
let r = new Float32Array(2 << numWire);
r.set(buf.slice(0, r.length));
buf = r;
}
let [colWires, rowWires] = [Math.floor(numWire/2), Math.ceil(numWire/2)];
let [colCount, rowCount] = [1 << colWires, 1 << rowWires];
//noinspection JSCheckFunctionSignatures
return new Matrix(colCount, rowCount, buf);
}
/**
* @returns {!Rect}
* @private
*/
_rectForSuperpositionDisplay() {
let col = this.clampedCircuitColCount() + EXTRA_COLS_FOR_SINGLE_QUBIT_DISPLAYS + 1;
let numWire = this.importantWireCount();
let [colWires, rowWires] = [Math.floor(numWire/2), Math.ceil(numWire/2)];
let [colCount, rowCount] = [1 << colWires, 1 << rowWires];
let topRect = this.gateRect(0, col);
let bottomRect = this.gateRect(numWire-1, col);
let gridRect = new Rect(topRect.x, topRect.y, 0, bottomRect.bottom() - topRect.y);
return gridRect.withW(gridRect.h * (colCount/rowCount));
}
/**
* Draws a peek gate on each wire at the right-hand side of the circuit.
*
* @param {!Painter} painter
* @param {!CircuitStats} stats
* @private
*/
_drawHintLabels(painter, stats) {
let gridRect = this._rectForSuperpositionDisplay();
// Amplitude hint.
painter.print(
'Final amplitudes',
gridRect.right() + 3,
gridRect.bottom() + 3,
'left',
'top',
'gray',
'12px sans-serif',
100,
20);
// Deferred measurement warning.
if (this.circuitDefinition.colIsMeasuredMask(Infinity) !== 0) {
painter.printParagraph(
"(assuming measurement deferred)",
new Rect(
gridRect.right() + 3,
gridRect.bottom() + 20,
100,
75),
new Point(0.5, 0),
'red');
}
// Discard rate warning.
let survivalRate = stats.survivalRate(Infinity);
if (Math.abs(survivalRate - 1) > 0.01) {
let desc;
if (survivalRate < 1) {
let rate = Math.round(survivalRate * 100);
let rateDesc = survivalRate === 0 ? "0" :
rate > 0 ? rate :
"<1";
desc = `kept: ${rateDesc}%`;
} else {
let factor = Math.round(survivalRate * 100);
desc = `over-unity: ${factor}%`;
}
painter.print(
desc,
this._rectForSuperpositionDisplay().x - 5,
gridRect.bottom() + SUPERPOSITION_GRID_LABEL_SPAN,
'right',
'bottom',
'red',
'14px sans-serif',
800,
50);
}
}
/**
* Parses a text diagram of a circuit, with positions marked by numbers, into a displayed circuit and a list of the
* positions of the marked points.
*
* Note: All lines should start with a pipe (|).
* Note: Follow a number with a carat (^) to indicate a position above the carat.
* Note: Separate wires with blank lines. Also start and end with blank lines.
* Note: Hyphens (-) mark wires, but can't be used at gate locations. Use a char mapped to undefined for that.
*
* Example diagram, which could be used to seed a drag of the X gate from after the control to under the control:
* |
* |-H-C-X-
* | 0^
* |-+-1-+-
* |
*
* @param {!string} diagramText
* @param {!Map<!string, undefined|!Gate>} gateMap
* @returns {!{circuit: !DisplayedCircuit, pts: !Array.<!Point>}}
*/
static fromTextDiagram(gateMap, diagramText) {
let lines = diagramText.split('\n').map(e => {
let p = e.split('|');
if (p.length !== 2) {
throw new DetailedError('Bad diagram', {diagramText, gateMap});
}
return p[1];
});
let circuitDiagramSubset = seq(lines).
skip(1).
stride(2).
map(line => seq(line).skip(1).stride(2).join("")).
join('\n');
let top = 10;
let circuit = new DisplayedCircuit(
top,
CircuitDefinition.fromTextDiagram(gateMap, circuitDiagramSubset),
undefined,
undefined,
undefined);
let pts = Seq.naturals().
takeWhile(k => diagramText.indexOf(k) !== -1).
map(k => {
let pos = seq(lines).mapWithIndex((line, row) => ({row, col: line.indexOf(k)})).
filter(e => e.col !== -1).
single();
if (lines[pos.row][pos.col + 1] === '^') {
pos.row -= 1;
pos.col += 1;
}
return new Point(
pos.col * Config.WIRE_SPACING / 2 + 35.5,
pos.row * Config.WIRE_SPACING / 2 + 10.5);
}).toArray();
return {circuit, pts};
}
}
/**
* @param {!Painter} painter
* @param {!CircuitDefinition} circuitDefinition
* @param {!Rect} rect
* @param {!boolean} showWires
* @param {!number} time
* @returns {!{maxW: !number, maxH: !number}}
*/
function drawCircuitTooltip(painter, circuitDefinition, rect, showWires, time) {
let displayed = new DisplayedCircuit(
0,
circuitDefinition,
undefined,
undefined,
undefined);
let neededWidth = displayed.desiredWidth(true);
let neededHeight = displayed.desiredHeight(true);
let scaleX = rect.w / neededWidth;
let scaleY = rect.h / neededHeight;
if (showWires) {
let s = Math.min(scaleX, scaleY);
scaleX = s;
scaleY = s;
}
let stats = CircuitStats.withNanDataFromCircuitAtTime(circuitDefinition, time);
try {
painter.ctx.save();
painter.ctx.translate(rect.x, rect.y);
painter.ctx.scale(Math.min(1, scaleX), Math.min(1, scaleY));
painter.ctx.translate(0, 0);
displayed.paint(
painter,
Hand.EMPTY,
stats,
true,
showWires);
} finally {
painter.ctx.restore();
}
return {maxW: neededWidth*scaleX, maxH: neededHeight*scaleY};
}
/**
* @param {!GateDrawParams} args
*/
let GATE_CIRCUIT_DRAWER = args => {
let circuit = args.gate.knownCircuit;
if (circuit === undefined || args.gate.symbol !== '') {
if (args.gate.stableDuration() === Infinity) {
GatePainting.DEFAULT_DRAWER(args);
} else {
GatePainting.makeCycleDrawer()(args);
}
return;
}
let toolboxColor = args.gate.stableDuration() === Infinity ?
Config.GATE_FILL_COLOR :
Config.TIME_DEPENDENT_HIGHLIGHT_COLOR;
GatePainting.paintBackground(args, toolboxColor);
drawCircuitTooltip(args.painter, args.gate.knownCircuitNested, args.rect, false, args.stats.time);
GatePainting.paintOutline(args);
if (args.isHighlighted) {
args.painter.ctx.save();
args.painter.ctx.globalAlpha *= 0.9;
args.painter.fillRect(args.rect, Config.HIGHLIGHTED_GATE_FILL_COLOR);
args.painter.ctx.restore();
}
GatePainting.paintOutline(args);
};
/**
* @param {!Painter} painter
* @param {!number} dy
* @param {!int} n
* @param {!function(!int) : !String} labeller
* @param {!number} boundingWidth
* @private
*/
function _drawLabelsReasonablyFast(painter, dy, n, labeller, boundingWidth) {
let ctx = painter.ctx;
ctx.save();
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
painter.ctx.font = '12px monospace';
let w = Math.max(
painter.ctx.measureText(labeller(0)).width,
painter.ctx.measureText(labeller(n-1)).width);
let h = ctx.measureText("0").width * 2.5;
let scale = Math.min(Math.min((boundingWidth-2) / w, dy / h), 1);
// Row labels.
let step = dy/scale;
let pad = 1/scale;
ctx.scale(scale, scale);
ctx.translate(0, dy*0.5/scale - h*0.5);
ctx.fillStyle = 'lightgray';
if (h < step*0.95) {
for (let i = 0; i < n; i++) {
ctx.fillRect(0, step*i, w + 2*pad, h);
}
} else {
ctx.fillRect(0, 0, w + 2*pad, step*n);
}
ctx.fillStyle = 'black';
for (let i = 0; i < n; i++) {
ctx.fillText(labeller(i), pad, h*0.5 + step*i);
}
ctx.restore();
}
let _cachedRowLabelDrawer = new CachablePainting(
numWire => ({
width: SUPERPOSITION_GRID_LABEL_SPAN,
height: (numWire - 1) * Config.WIRE_SPACING + Config.GATE_RADIUS * 2
}),
(painter, numWire) => {
let [colWires, rowWires] = [Math.floor(numWire/2), Math.ceil(numWire/2)];
let rowCount = 1 << rowWires;
//noinspection JSCheckFunctionSignatures
let suffix = colWires < 4 ? "_".repeat(colWires) : "_..";
//noinspection JSCheckFunctionSignatures
_drawLabelsReasonablyFast(
painter,
painter.canvas.height / rowCount,
rowCount,
i => Util.bin(i, rowWires) + suffix,
SUPERPOSITION_GRID_LABEL_SPAN);
});
let _cachedColLabelDrawer = new CachablePainting(
numWire => {
let [colWires, rowWires] = [Math.floor(numWire/2), Math.ceil(numWire/2)];
let [colCount, rowCount] = [1 << colWires, 1 << rowWires];
let total_height = (numWire - 1) * Config.WIRE_SPACING + Config.GATE_RADIUS * 2;
let cellDiameter = total_height / rowCount;
return {
width: colCount * cellDiameter,
height: SUPERPOSITION_GRID_LABEL_SPAN
}
},
(painter, numWire) => {
let [colWires, rowWires] = [Math.floor(numWire/2), Math.ceil(numWire/2)];
let colCount = 1 << colWires;
let dw = painter.canvas.width / colCount;
painter.ctx.translate(colCount*dw, 0);
painter.ctx.rotate(Math.PI/2);
//noinspection JSCheckFunctionSignatures
let prefix = rowWires < 4 ? "_".repeat(rowWires) : ".._";
//noinspection JSCheckFunctionSignatures
_drawLabelsReasonablyFast(
painter,
dw,
colCount,
i => prefix + Util.bin(colCount-1-i, colWires),
SUPERPOSITION_GRID_LABEL_SPAN);
});
export {DisplayedCircuit, drawCircuitTooltip, GATE_CIRCUIT_DRAWER}