From 1e141466441865490d4208be3ca42aa161802fc7 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 7 Jun 2018 16:28:36 -0300 Subject: [PATCH 01/38] Implement switchable initial states (by clicking on the input ket) --- src/base/Equate.js | 2 +- src/circuit/CircuitComputeUtil.js | 3 + src/circuit/CircuitDefinition.js | 91 ++++++++++++++++++++++++++++--- src/circuit/Serializer.js | 52 +++++++++++++++++- src/ui/DisplayedCircuit.js | 67 ++++++++++++++++++++++- src/ui/DisplayedInspector.js | 8 +++ test/circuit/CircuitStats.test.js | 14 +++++ 7 files changed, 224 insertions(+), 13 deletions(-) diff --git a/src/base/Equate.js b/src/base/Equate.js index 32b874d1..438c5740 100644 --- a/src/base/Equate.js +++ b/src/base/Equate.js @@ -240,4 +240,4 @@ function equate_Objects(subject, other) { return true; } -export {equate} +export {equate, equate_Maps} diff --git a/src/circuit/CircuitComputeUtil.js b/src/circuit/CircuitComputeUtil.js index d56449cc..5ef3c8f5 100644 --- a/src/circuit/CircuitComputeUtil.js +++ b/src/circuit/CircuitComputeUtil.js @@ -14,6 +14,7 @@ import {CircuitEvalContext} from "src/circuit/CircuitEvalContext.js" import {CircuitShaders} from "src/circuit/CircuitShaders.js" +import {DetailedError} from "src/base/DetailedError.js" import {KetTextureUtil} from "src/circuit/KetTextureUtil.js" import {Controls} from "src/circuit/Controls.js" import {GateBuilder} from "src/circuit/Gate.js" @@ -83,6 +84,8 @@ function advanceStateWithCircuit(ctx, circuitDefinition, collectStats) { } }; + circuitDefinition.applyInitialStateOperations(ctx); + // Apply each column in the circuit. for (let col = 0; col < circuitDefinition.columns.length; col++) { _advanceStateWithCircuitDefinitionColumn( diff --git a/src/circuit/CircuitDefinition.js b/src/circuit/CircuitDefinition.js index 463a5632..1b74cfba 100644 --- a/src/circuit/CircuitDefinition.js +++ b/src/circuit/CircuitDefinition.js @@ -17,6 +17,7 @@ import {Config} from "src/Config.js" import {Controls} from "src/circuit/Controls.js" import {CustomGateSet} from "src/circuit/CustomGateSet.js" import {DetailedError} from "src/base/DetailedError.js" +import {equate_Maps} from "src/base/Equate.js"; import {Gate} from "src/circuit/Gate.js" import {GateColumn} from "src/circuit/GateColumn.js" import {GateShaders} from "src/circuit/GateShaders.js" @@ -39,13 +40,15 @@ class CircuitDefinition { * @param {undefined|!Map.=} outerContext * @param {!CustomGateSet} customGateSet * @param {!boolean} isNested + * @param {!Map.} customInitialValues */ constructor(numWires, columns, outerRowOffset=0, outerContext=new Map(), customGateSet=new CustomGateSet(), - isNested=false) { + isNested=false, + customInitialValues=new Map()) { if (numWires < 0) { throw new DetailedError("Bad numWires", {numWires}) } @@ -69,6 +72,17 @@ class CircuitDefinition { this.outerRowOffset = outerRowOffset; this.outerContext = outerContext; this.isNested = isNested; + /** @type {!Map.} */ + this.customInitialValues = new Map(); + for (let [k, v] of customInitialValues.entries()) { + if (!Number.isInteger(k) || k < 0) { + throw new DetailedError('Initial state key out of range.', {customInitialValues, numWires}); + } + if (k >= this.numWires) { + continue; + } + this.customInitialValues.set(k, v); + } /** * @type {!Array.>} @@ -126,6 +140,30 @@ class CircuitDefinition { return !this.columns.every(e => !e.hasControl(-1)); } + /** + * @param {!int} wire + * @returns {!CircuitDefinition} + */ + withSwitchedInitialStateOn(wire) { + let m = new Map([...this.customInitialValues.entries()]); + let v = m.get(wire); + let cycle = [undefined, '1', '+', '-', 'S', 'S†']; + let newVal = cycle[(cycle.indexOf(v) + 1) % cycle.length]; + if (newVal === undefined) { + m.delete(wire); + } else { + m.set(wire, newVal); + } + return new CircuitDefinition( + this.numWires, + this.columns, + this.outerRowOffset, + this.outerContext, + this.customGateSet, + this.isNested, + m); + } + /** * @returns {!boolean} */ @@ -194,7 +232,8 @@ class CircuitDefinition { outerRowOffset, outerContext, this.customGateSet, - true); + true, + this.customInitialValues); } /** @@ -235,7 +274,8 @@ class CircuitDefinition { } return other instanceof CircuitDefinition && this.numWires === other.numWires && - seq(this.columns).isEqualTo(seq(other.columns), Util.CUSTOM_IS_EQUAL_TO_EQUALITY); + seq(this.columns).isEqualTo(seq(other.columns), Util.CUSTOM_IS_EQUAL_TO_EQUALITY) && + equate_Maps(this.customInitialValues, other.customInitialValues); } /** @@ -355,7 +395,9 @@ class CircuitDefinition { cols, this.outerRowOffset, this.outerContext, - this.customGateSet); + this.customGateSet, + false, + this.customInitialValues); } /** @@ -508,7 +550,9 @@ class CircuitDefinition { seq(this.columns).filterWithIndex((e, i) => used.has(i)).toArray(), this.outerRowOffset, this.outerContext, - this.customGateSet); + this.customGateSet, + false, + this.customInitialValues); } /** @@ -527,7 +571,9 @@ class CircuitDefinition { ])), this.outerRowOffset, this.outerContext, - this.customGateSet); + this.customGateSet, + false, + this.customInitialValues); } /** @@ -539,6 +585,9 @@ class CircuitDefinition { for (let c of this.columns) { best = Math.max(best, c.minimumRequiredWireCount()); } + for (let usedWire of this.customInitialValues.keys()) { + best = Math.max(best, usedWire + 1); + } return best; } @@ -845,6 +894,22 @@ class CircuitDefinition { return this._colRowDisabledReason[col][row]; } + /** + * @param {!CircuitEvalContext} ctx + * @return {void} + */ + applyInitialStateOperations(ctx) { + for (let wire = 0; wire < this.numWires; wire++) { + let state = this.customInitialValues.get(wire); + if (!STATE_TO_GATES.has(state)) { + throw new DetailedError('Unrecognized initial state.', {state}); + } + for (let gate of STATE_TO_GATES.get(state)) { + GateShaders.applyMatrixOperation(ctx.withRow(ctx.row + wire), gate.knownMatrixAt(ctx.time)) + } + } + } + /** * @param {!int} colIndex * @param {!CircuitEvalContext} ctx @@ -964,7 +1029,9 @@ class CircuitDefinition { this.columns, this.outerRowOffset, this.outerContext, - this.customGateSet.withGate(gate)); + this.customGateSet.withGate(gate), + false, + this.customInitialValues); } /** @@ -1050,6 +1117,16 @@ function firstLastMatchInRange(rangeLen, predicate){ return [first, last]; } +/** @type {!Map.>} */ +const STATE_TO_GATES = new Map([ + [undefined, []], + ['1', [Gates.HalfTurns.X]], + ['+', [Gates.HalfTurns.H]], + ['-', [Gates.HalfTurns.H, Gates.HalfTurns.Z]], + ['S', [Gates.HalfTurns.H, Gates.QuarterTurns.SqrtZForward]], + ['S†', [Gates.HalfTurns.H, Gates.QuarterTurns.SqrtZBackward]] +]); + CircuitDefinition.EMPTY = new CircuitDefinition(0, []); export {CircuitDefinition} diff --git a/src/circuit/Serializer.js b/src/circuit/Serializer.js index 1f4dddc1..b246e698 100644 --- a/src/circuit/Serializer.js +++ b/src/circuit/Serializer.js @@ -381,6 +381,17 @@ let toJson_CircuitDefinition = (v, context) => { if (context === undefined && v.customGateSet.gates.length > 0) { result.gates = toJson_CustomGateSet(v.customGateSet); } + if (v.customInitialValues.size > 0) { + result.init = []; + let maxInit = seq(v.customInitialValues.keys()).max(); + for (let i = 0; i <= maxInit; i++) { + let s = v.customInitialValues.get(i); + result.init.push( + s === undefined ? 0 : + s === '1' ? 1 : + s); + } + } return result; }; @@ -395,6 +406,38 @@ function fromJsonText_CircuitDefinition(jsonText) { return _cachedCircuit; } +/** + * @param {object} json + * @returns {!Map.} + * @throws + */ +function _fromJson_InitialState(json) { + let {init} = json; + if (init === undefined) { + return new Map(); + } + + if (!Array.isArray(init)) { + throw new DetailedError('Initial states must be an array.', {json}); + } + + let result = new Map(); + for (let i = 0; i < init.length; i++) { + let v = init[i]; + if (v === 0) { + // 0 is the default. Don't need to do anything. + } else if (v === 1) { + result.set(i, '1'); + } else if (['+', '-', 'S', 'S†'].indexOf(v) !== -1) { + result.set(i, v); + } else { + throw new DetailedError('Unrecognized initial state key.', {v, json}); + } + } + + return result; +} + /** * @param {object} json * @param {undefined|!CustomGateSet} context @@ -411,11 +454,16 @@ function fromJson_CircuitDefinition(json, context=undefined) { } let gateCols = cols.map(e => fromJson_GateColumn(e, customGateSet)); + let initialValues = _fromJson_InitialState(json); + let numWires = 0; for (let col of gateCols) { numWires = Math.max(numWires, col.minimumRequiredWireCount()); } - numWires = Math.max(Config.MIN_WIRE_COUNT, Math.min(numWires, Config.MAX_WIRE_COUNT)); + numWires = Math.max( + Config.MIN_WIRE_COUNT, + Math.min(numWires, Config.MAX_WIRE_COUNT), + ...[...initialValues.keys()].map(e => e + 1)); gateCols = gateCols.map(col => new GateColumn([ ...col.gates, @@ -424,7 +472,7 @@ function fromJson_CircuitDefinition(json, context=undefined) { // Silently discard gates off the edge of the circuit. ].slice(0, numWires))); - return new CircuitDefinition(numWires, gateCols, undefined, undefined, customGateSet). + return new CircuitDefinition(numWires, gateCols, undefined, undefined, customGateSet, false, initialValues). withTrailingSpacersIncluded(); } diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index 33a623e0..b4894b08 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -341,7 +341,7 @@ class DisplayedCircuit { */ paint(painter, hand, stats, forTooltip=false, showWires=true) { if (showWires) { - this._drawWires(painter, !forTooltip); + this._drawWires(painter, !forTooltip, hand); } for (let col = 0; col < this.circuitDefinition.columns.length; col++) { @@ -357,9 +357,10 @@ class DisplayedCircuit { /** * @param {!Painter} painter * @param {!boolean} showLabels + * @param {!Hand} hand * @private */ - _drawWires(painter, showLabels) { + _drawWires(painter, showLabels, hand) { let drawnWireCount = Math.min(this.circuitDefinition.numWires, (this._extraWireStartIndex || Infinity) + 1); // Initial value labels @@ -367,7 +368,16 @@ class DisplayedCircuit { for (let row = 0; row < drawnWireCount; row++) { let wireRect = this.wireRect(row); let y = wireRect.center().y; - painter.print('|0⟩', 20, y, 'right', 'middle', 'black', '14px sans-serif', 20, Config.WIRE_SPACING); + let v = this.circuitDefinition.customInitialValues.get(row); + if (v === undefined) { + v = '0'; + } + let rect = this._wireInitialStateClickableRect(row); + painter.noteTouchBlocker({rect, cursor: 'pointer'}); + if (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); } } @@ -921,6 +931,47 @@ class DisplayedCircuit { 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} + * @private + */ + _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} @@ -930,6 +981,11 @@ class DisplayedCircuit { 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; @@ -1179,6 +1235,7 @@ class DisplayedCircuit { 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); } @@ -1436,7 +1493,9 @@ let _cachedRowLabelDrawer = new CachablePainting( (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, @@ -1463,7 +1522,9 @@ let _cachedColLabelDrawer = new CachablePainting( 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, diff --git a/src/ui/DisplayedInspector.js b/src/ui/DisplayedInspector.js index 296166d8..9c7b2e5c 100644 --- a/src/ui/DisplayedInspector.js +++ b/src/ui/DisplayedInspector.js @@ -306,6 +306,10 @@ class DisplayedInspector { * @private */ _watchOutputsChangeVisibility() { + if (this.displayedCircuit.circuitDefinition.customInitialValues.size > 0) { + return 0; + } + let gatesInCircuit = this.displayedCircuit.circuitDefinition.countGatesUpTo(2); let gatesInPlay = gatesInCircuit + (this.hand.isBusy() ? 1 : 0); if (gatesInCircuit >= 2 || gatesInPlay === 0) { @@ -413,6 +417,10 @@ class DisplayedInspector { let gatesInCircuit = circ.countGatesUpTo(2); let gatesInPlay = gatesInCircuit + (this.hand.heldGate !== undefined ? 1 : 0); + if (circ.customInitialValues.size > 0) { + return 0; + } + let gate = circ.gateInSlot(0, 0); if (circ.hasControls() || !circ.hasNonControlGates() || (gate !== undefined && gate.height > 1)) { return 0; diff --git a/test/circuit/CircuitStats.test.js b/test/circuit/CircuitStats.test.js index 4d46da26..2cc2fd13 100644 --- a/test/circuit/CircuitStats.test.js +++ b/test/circuit/CircuitStats.test.js @@ -353,3 +353,17 @@ suite.testUsingWebGL('classical-bit-rotate-with-classical-control-does-fire', () assertThat(stats.qubitDensityMatrix(Infinity, 1)).isEqualTo(Matrix.square(0, 0, 0, 1)); assertThat(stats.qubitDensityMatrix(Infinity, 2)).isEqualTo(Matrix.square(0, 0, 0, 1)); }); + +suite.testUsingWebGL("initial_states", () => { + let circuit = Serializer.fromJson(CircuitDefinition, { + init: [0, 1, '+', '-', 'S', 'S†'], + cols: [], + }); + let stats = CircuitStats.fromCircuitAtTime(circuit, 0); + assertThat(stats.qubitDensityMatrix(9, 0).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([0, 0, -1]); + assertThat(stats.qubitDensityMatrix(9, 1).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([0, 0, +1]); + assertThat(stats.qubitDensityMatrix(9, 2).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([-1, 0, 0]); + assertThat(stats.qubitDensityMatrix(9, 3).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([+1, 0, 0]); + assertThat(stats.qubitDensityMatrix(9, 4).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([0, +1, 0]); + assertThat(stats.qubitDensityMatrix(9, 5).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([0, -1, 0]); +}); From 48c57df8d3ee55c67675ef8668c900ce43209040 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 7 Jun 2018 16:37:04 -0300 Subject: [PATCH 02/38] Draw T and S gates as "T" and "S" instead of "Z^1/2" etc --- src/circuit/Gate.js | 5 +++-- src/gates/QuarterTurnGates.js | 10 ++++++---- src/gates/VariousZGates.js | 10 ++++++---- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index ad751710..714784e0 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -215,15 +215,16 @@ class Gate { * @param {!Matrix} matrix * @param {!string} name * @param {!string} blurb + * @param {undefined|!string} serializedId * @returns {!Gate} */ - static fromKnownMatrix(symbol, matrix, name='', blurb='') { + static fromKnownMatrix(symbol, matrix, name='', blurb='', serializedId=undefined) { if (!(matrix instanceof Matrix)) { throw new DetailedError("Bad matrix.", {symbol, matrix, name, blurb}); } let g = new Gate(); g.symbol = symbol; - g.serializedId = symbol; + g.serializedId = serializedId === undefined ? symbol : serializedId; g.name = name; g.blurb = blurb; g._isDefinitelyUnitary = matrix.isUnitary(0.01); diff --git a/src/gates/QuarterTurnGates.js b/src/gates/QuarterTurnGates.js index cf5b9119..b6aff93e 100644 --- a/src/gates/QuarterTurnGates.js +++ b/src/gates/QuarterTurnGates.js @@ -47,17 +47,19 @@ QuarterTurnGates.SqrtYBackward = Gate.fromKnownMatrix( /** @type {!Gate} */ QuarterTurnGates.SqrtZForward = Gate.fromKnownMatrix( - "Z^½", + "S", Matrix.fromPauliRotation(0, 0, 0.25), "√Z Gate", - "Principle square root of Z.\nAlso known as the 'S' gate."); + "Principle square root of Z.\nAlso known as the 'S' gate.", + "Z^½"); /** @type {!Gate} */ QuarterTurnGates.SqrtZBackward = Gate.fromKnownMatrix( - "Z^-½", + "S^-1", Matrix.fromPauliRotation(0, 0, 0.75), "Z^-½ Gate", - "Adjoint square root of Z."); + "Adjoint square root of Z.", + "Z^-½"); QuarterTurnGates.all = [ QuarterTurnGates.SqrtXForward, diff --git a/src/gates/VariousZGates.js b/src/gates/VariousZGates.js index 6ff37702..9163255c 100644 --- a/src/gates/VariousZGates.js +++ b/src/gates/VariousZGates.js @@ -28,15 +28,17 @@ VariousZGates.Z3i = Gate.fromKnownMatrix( "Z^-⅓ Gate", "Adjoint third root of Z."); VariousZGates.Z4 = Gate.fromKnownMatrix( - "Z^¼", + "T", Matrix.fromPauliRotation(0, 0, 1 / 8), "Z^¼ Gate", - "Principle fourth root of Z.\nAlso known as the 'T' gate."); + "Principle fourth root of Z.", + "Z^¼"); VariousZGates.Z4i = Gate.fromKnownMatrix( - "Z^-¼", + "T^-1", Matrix.fromPauliRotation(0, 0, -1 / 8), "Z^-¼ Gate", - "Adjoint fourth root of Z."); + "Adjoint fourth root of Z.", + "Z^-¼"); VariousZGates.Z8 = Gate.fromKnownMatrix( "Z^⅛", Matrix.fromPauliRotation(0, 0, 1 / 16), From 652c188f54927aebde9d7aef126f5459bb7486e3 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 01:07:57 -0300 Subject: [PATCH 03/38] Implement X/Y/Z detectors and detect-control-reset gates --- src/circuit/Gate.js | 1 - src/circuit/GateColumn.js | 27 ++- src/gates/AllGates.js | 16 +- src/gates/Controls.js | 6 + src/gates/Detector.js | 309 ++++++++++++++++++++++--- test/circuit/CircuitDefinition.test.js | 18 +- test/circuit/Serializer.test.js | 4 +- test/gates/AllGates.test.js | 18 +- test/gates/Detector.test.js | 2 +- 9 files changed, 355 insertions(+), 46 deletions(-) diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 714784e0..bafe9020 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -800,7 +800,6 @@ class GateBuilder { markAsControlExpecting(bit) { this.gate._controlBit = bit; this.gate.isControlWireSource = true; - this.gate._isDefinitelyUnitary = true; this.gate.interestedInControls = false; return this; } diff --git a/src/circuit/GateColumn.js b/src/circuit/GateColumn.js index 8d2d8a21..fbaca1db 100644 --- a/src/circuit/GateColumn.js +++ b/src/circuit/GateColumn.js @@ -69,11 +69,21 @@ class GateColumn { return this.gates.every(e => e === undefined); } - hasControl(inputMeasureMask) { - return this.hasCoherentControl(inputMeasureMask) || this.hasMeasuredControl(inputMeasureMask); + /** + * @param {!int} inputMeasureMask + * @param {!int} ignoreMask + * @returns {!boolean} + */ + hasControl(inputMeasureMask=0, ignoreMask=0) { + return this.hasCoherentControl(inputMeasureMask | ignoreMask) || + this.hasMeasuredControl(inputMeasureMask & ~ignoreMask); } - hasCoherentControl(inputMeasureMask) { + /** + * @param {!int} inputMeasureMask + * @returns {!boolean} + */ + hasCoherentControl(inputMeasureMask=0) { for (let i = 0; i < this.gates.length; i++) { if ((inputMeasureMask & (1 << i)) === 0 && this.gates[i] !== undefined && @@ -84,7 +94,11 @@ class GateColumn { return false; } - hasMeasuredControl(inputMeasureMask) { + /** + * @param {!int} inputMeasureMask + * @returns {!boolean} + */ + hasMeasuredControl(inputMeasureMask=0) { for (let i = 0; i < this.gates.length; i++) { if ((inputMeasureMask & (1 << i)) !== 0 && this.gates[i] !== undefined && @@ -380,8 +394,9 @@ class GateColumn { // without getting the wrong answer, at least). let hasSingleResult = gate === Gates.PostSelectionGates.PostSelectOn || gate === Gates.PostSelectionGates.PostSelectOff - || gate === Gates.Detector; - if (!this.hasControl() && hasSingleResult) { + || gate === Gates.Detectors.ZDetector + || gate === Gates.Detectors.ZDetectControlClear; + if (!this.hasControl(0, 1 << row) && hasSingleResult) { state.measureMask &= ~(1< { if (args.isInToolbox || args.isHighlighted) { GatePainting.paintBackground(args); @@ -41,6 +42,7 @@ Controls.AntiControl = new GateBuilder(). setBlurb("Conditions on a qubit being OFF.\nGates in the same column only apply to states meeting the condition."). promiseHasNoNetEffectOnStateVector(). markAsControlExpecting(false). + promiseEffectIsUnitary(). setDrawer(args => { if (args.isInToolbox || args.isHighlighted) { GatePainting.paintBackground(args); @@ -64,6 +66,7 @@ Controls.XAntiControl = new GateBuilder(). HalfTurnGates.H.customOperation). setActualEffectToUpdateFunc(() => {}). promiseEffectIsStable(). + promiseEffectIsUnitary(). setDrawer(args => { if (args.isInToolbox || args.isHighlighted) { GatePainting.paintBackground(args); @@ -88,6 +91,7 @@ Controls.XControl = new GateBuilder(). HalfTurnGates.H.customOperation). setActualEffectToUpdateFunc(() => {}). promiseEffectIsStable(). + promiseEffectIsUnitary(). setDrawer(args => { if (args.isInToolbox || args.isHighlighted) { GatePainting.paintBackground(args); @@ -113,6 +117,7 @@ Controls.YAntiControl = new GateBuilder(). ctx => GateShaders.applyMatrixOperation(ctx, QuarterTurnGates.SqrtXBackward._knownMatrix)). setActualEffectToUpdateFunc(() => {}). promiseEffectIsStable(). + promiseEffectIsUnitary(). setDrawer(args => { if (args.isInToolbox || args.isHighlighted) { GatePainting.paintBackground(args); @@ -141,6 +146,7 @@ Controls.YControl = new GateBuilder(). ctx => GateShaders.applyMatrixOperation(ctx, QuarterTurnGates.SqrtXBackward._knownMatrix)). setActualEffectToUpdateFunc(() => {}). promiseEffectIsStable(). + promiseEffectIsUnitary(). setDrawer(ctx => { if (ctx.isInToolbox || ctx.isHighlighted) { GatePainting.paintBackground(ctx); diff --git a/src/gates/Detector.js b/src/gates/Detector.js index cb4b5991..3d9a91fb 100644 --- a/src/gates/Detector.js +++ b/src/gates/Detector.js @@ -24,6 +24,12 @@ import {WglArg} from 'src/webgl/WglArg.js'; import {Config} from 'src/Config.js' import {GatePainting} from 'src/draw/GatePainting.js' import {makePseudoShaderWithInputsAndOutputAndCode, Outputs} from 'src/webgl/ShaderCoders.js'; +import {Matrix} from "src/math/Matrix.js"; +import {GateShaders} from "src/circuit/GateShaders.js"; +import {Point} from "src/math/Point.js"; +import {DetailedError} from "src/base/DetailedError.js"; +import {QuarterTurnGates} from "src/gates/QuarterTurnGates.js"; +import {HalfTurnGates} from "src/gates/HalfTurnGates.js"; /** * @param {!CircuitEvalContext} ctx @@ -98,6 +104,30 @@ let detectorShader = makePseudoShaderWithInputsAndOutputAndCode( } `); +/** + * @param {!CircuitEvalContext} ctx + * @param {!string} axis + * @param {!boolean} inverse + */ +function switchToBasis(ctx, axis, inverse) { + switch (axis) { + case 'X': + GateShaders.applyMatrixOperation(ctx, HalfTurnGates.H.knownMatrixAt(0)); + break; + case 'Y': + if (inverse) { + GateShaders.applyMatrixOperation(ctx, QuarterTurnGates.SqrtXBackward.knownMatrixAt(0)); + } else { + GateShaders.applyMatrixOperation(ctx, QuarterTurnGates.SqrtXForward.knownMatrixAt(0)); + } + break; + case 'Z': + break; // Already in the right basis. + default: + throw new DetailedError('Unrecognized axis.', {axis}); + } + +} /** * Applies a sample measurement operation to the state. * @param {!CircuitEvalContext} ctx @@ -123,19 +153,35 @@ function sampleMeasure(ctx) { /** * @param {!GateDrawParams} args + * @param {!string} axis */ -function drawDetector(args) { - // Draw framing. +function drawDetector(args, axis) { + drawHighlight(args); + drawWedge(args, axis); + drawClick(args, axis); +} + +/** + * @param {!GateDrawParams} args + */ +function drawHighlight(args) { + // Can't use the typical highlight function because the detector has no box outline. if (args.isHighlighted || args.isInToolbox) { args.painter.fillRect( args.rect, - args.isHighlighted ? Config.HIGHLIGHTED_GATE_FILL_COLOR : Config.TIME_DEPENDENT_HIGHLIGHT_COLOR); + args.isHighlighted ? Config.HIGHLIGHTED_GATE_FILL_COLOR : Config.GATE_FILL_COLOR); GatePainting.paintOutline(args); } +} +/** + * @param {!GateDrawParams} args + * @param {!string} axis + */ +function drawWedge(args, axis) { // Draw semi-circle wedge. const τ = Math.PI * 2; - let r = args.rect.w*0.4; + let r = Math.min(args.rect.h / 2, args.rect.w) - 1; let {x, y} = args.rect.center(); x -= r*0.5; x += 0.5; @@ -144,42 +190,247 @@ function drawDetector(args) { trace.ctx.arc(x, y, r, τ*3/4, τ/4); trace.ctx.lineTo(x, y - r - 1); }).thenStroke('black', 2).thenFill(Config.TIME_DEPENDENT_HIGHLIGHT_COLOR); + args.painter.printLine(axis, args.rect, 0.5, undefined, undefined, undefined, 0.5); +} +/** + * @param {!GateDrawParams} args + * @param {undefined|!string} axis + */ +function drawClick(args, axis) { // Draw tilted "*click*" text. let clicked = args.customStats; - if (clicked) { - args.painter.ctx.save(); - args.painter.ctx.translate(args.rect.center().x, args.rect.center().y); - args.painter.ctx.rotate(τ/8); - args.painter.ctx.strokeStyle = 'white'; - args.painter.ctx.lineWidth = 2; + if (!clicked) { + return; +} + let r = Math.min(args.rect.h / 2, args.rect.w); + args.painter.ctx.save(); + args.painter.ctx.translate(args.rect.center().x, args.rect.center().y); + args.painter.ctx.rotate(axis === undefined ? Math.PI/3 : Math.PI/4); + args.painter.ctx.strokeStyle = 'white'; + args.painter.ctx.lineWidth = 3; + args.painter.print( + '*click*', + 0, + axis === undefined ? 0 : -5, + 'center', + 'middle', + 'black', + 'bold 16px sans-serif', + r*2.8, + r*2.8, + undefined, + true); + + if (axis !== undefined) { args.painter.print( - '*click*', - 0, + axis, 0, + 10, 'center', 'middle', 'black', 'bold 16px sans-serif', - args.rect.w*1.4, - args.rect.h*1.4, + r * 2.8, + r * 2.8, undefined, true); - args.painter.ctx.restore(); } + args.painter.ctx.restore(); +} + +/** + * @param {!GateDrawParams} args + * @param {!string} axis + */ +function drawControlBulb(args, axis) { + redrawControlWires(args); + let p = args.rect.center(); + switch (axis) { + case 'X': + args.painter.fillCircle(p, 5); + args.painter.strokeCircle(p, 5); + args.painter.strokeLine(p.offsetBy(0, -5), p.offsetBy(0, +5)); + args.painter.strokeLine(p.offsetBy(-5, 0), p.offsetBy(+5, 0)); + break; + case 'Y': + args.painter.fillCircle(p, 5); + args.painter.strokeCircle(p, 5); + let r = 5*Math.sqrt(0.5)*1.1; + args.painter.strokeLine(p.offsetBy(+r, -r), p.offsetBy(-r, +r)); + args.painter.strokeLine(p.offsetBy(-r, +r), p.offsetBy(+r, -r)); + break; + case 'Z': + args.painter.fillCircle(p, 5, "black"); + break; + default: + throw new DetailedError('Unrecognized axis.', {axis}); + } +} + +/** + * @param {!GateDrawParams} args + * @param {!string} axis + */ +function drawDetectClearReset(args, axis) { + let fullRect = args.rect; + let detectorRect = fullRect.leftHalf(); + let resetRect = fullRect.rightHalf(); + + // Draw background. + let clearWireRect = fullRect.rightHalf(); + clearWireRect.y += clearWireRect.h / 2 - 2; + clearWireRect.h = 5; + args.painter.fillRect(clearWireRect, 'white'); + drawHighlight(args); + + // Draw text elements. + args.painter.printLine('|0⟩', resetRect, 1, undefined, undefined, undefined, 0.5); + + // Draw detector. + args.rect = detectorRect; + drawWedge(args, axis); + + args.rect = fullRect; + drawControlBulb(args, axis); + args.rect = detectorRect; + drawClick(args, undefined); + + args.rect = fullRect; +} + +/** + * @param {!GateDrawParams} args + */ +function redrawControlWires(args) { + if (args.positionInCircuit === undefined || args.isHighlighted) { + return; + } + let painter = args.painter; + let columnIndex = args.positionInCircuit.col; + let x = Math.round(args.rect.center().x - 0.5) + 0.5; + + // Dashed line indicates effects from non-unitary gates may affect, or appear to affect, other wires. + let circuit = args.stats.circuitDefinition; + if (circuit.columns[columnIndex].hasGatesWithGlobalEffects()) { + painter.ctx.save(); + painter.ctx.setLineDash([1, 4]); + painter.strokeLine(new Point(x, args.rect.y), new Point(x, args.rect.bottom())); + painter.ctx.restore(); + } + + let row = args.positionInCircuit.row; + for (let {first, last, measured} of circuit.controlLinesRanges(columnIndex)) { + if (first <= row && row <= last) { + let y1 = first === row ? args.rect.center().y : args.rect.y; + let y2 = last === row ? args.rect.center().y : args.rect.bottom(); + 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 {!function(!CircuitEvalContext) : T} func + * @returns {!function(!CircuitEvalContext) : T} + * @template T + */ +function withClearedControls(func) { + return ctx => { + let controls = ctx.controls; + let texture = ctx.controlsTexture; + try { + ctx.controls = Controls.NONE; + ctx.controlsTexture = controlMaskTex(ctx, ctx.controls); + return func(ctx); + } finally { + ctx.controlsTexture.deallocByDepositingInPool('withClearedControls'); + ctx.controlsTexture = texture; + ctx.controls = controls; + } + }; +} + +/** + * @param {!string} axis + * @returns {!Gate} + */ +function makeDetectControlClearGate(axis) { + let builder = new GateBuilder(). + setSerializedIdAndSymbol(`${axis}DetectControlReset`). + setTitle(`${axis} Detect-Control-Reset`). + setBlurb(`Does a collapsing ${axis}-axis measurement.\nControls operations with the result.\nResets the target to |0⟩.`). + setDrawer(args => drawDetectClearReset(args, axis)). + markAsControlExpecting(true). + markAsReachingOtherWires(). + setActualEffectToUpdateFunc(() => {}). + setStatTexturesMaker(withClearedControls(detectorStatTexture)). + setSetupCleanupEffectToUpdateFunc( + withClearedControls(ctx => { + switchToBasis(ctx, axis, false); + sampleMeasure(ctx); + }), + withClearedControls(ctx => { + GateShaders.applyMatrixOperation(ctx, Matrix.square(1, 1, 0, 0)); + })). + setStatPixelDataPostProcessor((pixels, circuit, row, col) => pixels[0] > 0); + if (axis === 'Z') { + builder.promiseEffectIsDiagonal(); + } + return builder.gate; } -let Detector = new GateBuilder(). - setSerializedIdAndSymbol('Detector'). - setTitle('Detector'). - setBlurb('Makes a *click* when the target qubit is ON and all controls are satisfied.\n' + - 'Measures whether to click, samples the result, then post-selects on it.'). - setDrawer(drawDetector). - promiseEffectIsDiagonal(). - markAsReachingOtherWires(). - setActualEffectToUpdateFunc(sampleMeasure). - setStatTexturesMaker(detectorStatTexture). - setStatPixelDataPostProcessor((pixels, circuit, row, col) => pixels[0] > 0). - gate; - -export {Detector} +/** + * @param {!string} axis + * @returns {!Gate} + */ +function makeDetector(axis) { + let state = new Map([ + ['X', '|0⟩-|1⟩'], + ['Y', '|0⟩+i|1⟩'], + ['Z', '|0⟩'], + ]).get(axis); + let builder = new GateBuilder(). + setSerializedIdAndSymbol(`${axis}Detector`). + setTitle(`${axis} Axis Detector`). + setBlurb( + `Collapsing ${axis}-axis measurement.\n` + + `Shows *click* when the target qubit is ${state} and controls are satisfied.`). + setDrawer(args => drawDetector(args, axis)). + markAsReachingOtherWires(). + setSetupCleanupEffectToUpdateFunc( + ctx => switchToBasis(ctx, axis, false), + ctx => switchToBasis(ctx, axis, true)). + setActualEffectToUpdateFunc(sampleMeasure). + setStatTexturesMaker(detectorStatTexture). + setStatPixelDataPostProcessor((pixels, circuit, row, col) => pixels[0] > 0); + if (axis === 'Z') { + builder.promiseEffectIsDiagonal(); + } + return builder.gate; +} + +let Detectors = {}; + +Detectors.XDetector = makeDetector('X'); +Detectors.YDetector = makeDetector('Y'); +Detectors.ZDetector = makeDetector('Z'); + +Detectors.XDetectControlClear = makeDetectControlClearGate('X'); +Detectors.YDetectControlClear = makeDetectControlClearGate('Y'); +Detectors.ZDetectControlClear = makeDetectControlClearGate('Z'); + +Detectors.all = [ + Detectors.XDetector, + Detectors.YDetector, + Detectors.ZDetector, + Detectors.XDetectControlClear, + Detectors.YDetectControlClear, + Detectors.ZDetectControlClear, +]; + +export {Detectors} diff --git a/test/circuit/CircuitDefinition.test.js b/test/circuit/CircuitDefinition.test.js index 7a615c89..66970dcf 100644 --- a/test/circuit/CircuitDefinition.test.js +++ b/test/circuit/CircuitDefinition.test.js @@ -401,7 +401,11 @@ suite.test("minimumRequiredColCount", () => { suite.test("colIsMeasuredMask", () => { let assertAbout = (diagram, ...extraGates) => { - let c = circuit(diagram, ['D', Gates.Detector], ...extraGates); + let c = circuit( + diagram, + ['D', Gates.Detectors.ZDetector], + ['R', Gates.Detectors.ZDetectControlClear], + ...extraGates); return assertThat(Seq.range(c.columns.length + 3).map(i => c.colIsMeasuredMask(i-1)).toArray()); }; @@ -421,6 +425,10 @@ suite.test("colIsMeasuredMask", () => { assertAbout(`M!`).isEqualTo([0, 0, 1, 0, 0]); // Detector clears. assertAbout(`MD`).isEqualTo([0, 0, 1, 0, 0]); + // Detect-Control-Reset clears. + assertAbout(`MR`).isEqualTo([0, 0, 1, 0, 0]); + assertAbout(`MR + -X`).isEqualTo([0, 0, 1, 0, 0]); // Controlled detector doesn't clear. assertAbout(`MD -●`).isEqualTo([0, 0, 1, 1, 1]); @@ -885,6 +893,14 @@ suite.test("gateAtLocIsDisabledReason", () => { bad(1, 1, `-●- M<- M/-`, ['<', Gates.CycleBitsGates.CycleBitsFamily]); + + // Detectors. + bad(1, 0, `MD`, ['D', Gates.Detectors.XDetectControlClear]); + bad(1, 0, `MD`, ['D', Gates.Detectors.YDetectControlClear]); + good(1, 0, `MD`, ['D', Gates.Detectors.ZDetectControlClear]); + bad(1, 0, `MR`, ['R', Gates.Detectors.XDetectControlClear]); + bad(1, 0, `MR`, ['R', Gates.Detectors.YDetectControlClear]); + good(1, 0, `MR`, ['R', Gates.Detectors.ZDetectControlClear]); }); suite.test("gateAtLocIsDisabledReason_controls", () => { diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index 56a9a7e6..8a9786fc 100644 --- a/test/circuit/Serializer.test.js +++ b/test/circuit/Serializer.test.js @@ -179,7 +179,9 @@ suite.test("roundTrip_circuitDefinition", () => { const IDS_THAT_SHOULD_BE_KNOWN = [ "•", "◦", "⊕", "⊖", "⊗", "(/)", "|0⟩⟨0|", "|1⟩⟨1|", "|+⟩⟨+|", "|-⟩⟨-|", "|X⟩⟨X|", "|/⟩⟨/|", - "Measure", "Detector", + "Measure", + "XDetector", "YDetector", "ZDetector", + "XDetectControlReset", "YDetectControlReset", "ZDetectControlReset", "Swap", "…", "inputA1", "inputA2", "inputA3", "inputA4", "inputA5", "inputA6", "inputA7", "inputA8", "inputA9", "inputA10", "inputA11", "inputA12", "inputA13", "inputA14", "inputA15", "inputA16", diff --git a/test/gates/AllGates.test.js b/test/gates/AllGates.test.js index b6a752ff..a59c45df 100644 --- a/test/gates/AllGates.test.js +++ b/test/gates/AllGates.test.js @@ -148,7 +148,7 @@ suite.testUsingWebGL("gatesActLikeTheirKnownPhasingFunction", () => { } }); -suite.testUsingWebGL("knownNonUnitaryGates", () => { +suite.test("knownNonUnitaryGates", () => { let nonUnitaryGates = new Set(Gates.KnownToSerializer. filter(g => !g.isDefinitelyUnitary()). map(g => g.serializedId)); @@ -163,7 +163,14 @@ suite.testUsingWebGL("knownNonUnitaryGates", () => { '|-⟩⟨-|', '|X⟩⟨X|', '|/⟩⟨/|', - 'Detector', + // Collapsing measurement isn't unitary. + 'XDetector', + 'YDetector', + 'ZDetector', + // Especially if you reset the qubit afterwards. + 'XDetectControlReset', + 'YDetectControlReset', + 'ZDetectControlReset', ])); }); @@ -226,6 +233,11 @@ suite.test("knownDynamicGateFamilies", () => { // Other. 'grad^t1', 'grad^-t1', - 'Detector', + 'XDetector', + 'YDetector', + 'ZDetector', + 'XDetectControlReset', + 'YDetectControlReset', + 'ZDetectControlReset', ])); }); diff --git a/test/gates/Detector.test.js b/test/gates/Detector.test.js index a16489bb..5764182b 100644 --- a/test/gates/Detector.test.js +++ b/test/gates/Detector.test.js @@ -24,7 +24,7 @@ const circuit = (diagram, ...extras) => CircuitDefinition.fromTextDiagram(new Ma ...extras, ['-', undefined], ['●', Gates.Controls.Control], - ['D', Gates.Detector], + ['D', Gates.Detectors.ZDetector], ['X', Gates.HalfTurns.X], ['Z', Gates.HalfTurns.Z], ['H', Gates.HalfTurns.H], From 5c6167b013626905d1c5383d4dcb79fb60289d2a Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 01:23:10 -0300 Subject: [PATCH 04/38] Add imaginary gate --- src/gates/AllGates.js | 21 ++++++++++++--------- src/gates/Joke_ImaginaryGate.js | 26 ++++++++++++++++++++++++++ src/gates/Joke_ZeroGate.js | 2 +- test/circuit/Serializer.test.js | 2 +- 4 files changed, 40 insertions(+), 11 deletions(-) create mode 100644 src/gates/Joke_ImaginaryGate.js diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 9e5c04be..73a9f46c 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -25,6 +25,7 @@ import {ErrorInjectionGate} from "src/gates/Debug_ErrorInjectionGate.js" import {ExponentiatingGates} from "src/gates/ExponentiatingGates.js" import {FourierTransformGates} from "src/gates/FourierTransformGates.js" import {HalfTurnGates} from "src/gates/HalfTurnGates.js" +import {ImaginaryGate} from "src/gates/Joke_ImaginaryGate.js" import {IncrementGates} from "src/gates/IncrementGates.js" import {InputGates} from "src/gates/InputGates.js" import {InterleaveBitsGates} from "src/gates/InterleaveBitsGates.js" @@ -89,6 +90,7 @@ Gates.ErrorInjection = ErrorInjectionGate; Gates.Exponentiating = ExponentiatingGates; Gates.FourierTransformGates = FourierTransformGates; Gates.HalfTurns = HalfTurnGates; +Gates.ImaginaryGate = ImaginaryGate; Gates.IncrementGates = IncrementGates; Gates.InputGates = InputGates; Gates.InterleaveBitsGates = InterleaveBitsGates; @@ -126,6 +128,7 @@ Gates.KnownToSerializer = [ ErrorInjectionGate, ZeroGate, NeGate, + ImaginaryGate, ...AmplitudeDisplayFamily.all, ...ProbabilityDisplayFamily.all, @@ -240,21 +243,21 @@ Gates.TopToolboxGroups = [ ] }, { - hint: 'Silly', + hint: 'Sampling', gates: [ - ZeroGate, MysteryGateMaker(), - NeGate, undefined, - SpacerGate, undefined, + Detectors.ZDetector, Detectors.ZDetectControlClear, + Detectors.YDetector, Detectors.YDetectControlClear, + Detectors.XDetector, Detectors.XDetectControlClear, ] }, { - hint: 'Sample', + hint: 'Silly', gates: [ - Detectors.ZDetector, Detectors.ZDetectControlClear, - Detectors.YDetector, Detectors.YDetectControlClear, - Detectors.XDetector, Detectors.XDetectControlClear, + ZeroGate, MysteryGateMaker(), + NeGate, ImaginaryGate, + SpacerGate, undefined, ] - } + }, ]; /** @type {!Array}>} */ diff --git a/src/gates/Joke_ImaginaryGate.js b/src/gates/Joke_ImaginaryGate.js new file mode 100644 index 00000000..e80db31d --- /dev/null +++ b/src/gates/Joke_ImaginaryGate.js @@ -0,0 +1,26 @@ +// 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 {GateBuilder} from "src/circuit/Gate.js" +import {Matrix} from "src/math/Matrix.js" +import {Complex} from "src/math/Complex.js" + +const ImaginaryGate = new GateBuilder(). + setSerializedIdAndSymbol("i"). + setTitle("Imaginary Gate"). + setBlurb("Phases everything by i."). + setKnownEffectToMatrix(Matrix.square(Complex.I, 0, 0, Complex.I)). + gate; + +export {ImaginaryGate} diff --git a/src/gates/Joke_ZeroGate.js b/src/gates/Joke_ZeroGate.js index 5b099897..e5b47bb0 100644 --- a/src/gates/Joke_ZeroGate.js +++ b/src/gates/Joke_ZeroGate.js @@ -19,7 +19,7 @@ import {Matrix} from "src/math/Matrix.js" /** @type {!Gate} */ const ZeroGate = new GateBuilder(). setSerializedIdAndSymbol("0"). - setTitle("Zero Gate"). + setTitle("Nothing Gate"). setBlurb("Destroys the universe."). setDrawer(GatePainting.makeLocationIndependentGateDrawer('#666')). setKnownEffectToMatrix(Matrix.square(0, 0, 0, 0)). diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index 8a9786fc..79577e9a 100644 --- a/test/circuit/Serializer.test.js +++ b/test/circuit/Serializer.test.js @@ -191,7 +191,7 @@ const IDS_THAT_SHOULD_BE_KNOWN = [ "revinputA1", "revinputA2", "revinputA3", "revinputA4", "revinputA5", "revinputA6", "revinputA7", "revinputA8", "revinputA9", "revinputA10", "revinputA11", "revinputA12", "revinputA13", "revinputA14", "revinputA15", "revinputA16", "revinputB1", "revinputB2", "revinputB3", "revinputB4", "revinputB5", "revinputB6", "revinputB7", "revinputB8", "revinputB9", "revinputB10", "revinputB11", "revinputB12", "revinputB13", "revinputB14", "revinputB15", "revinputB16", "__error__", - "0", "NeGate", + "0", "NeGate", "i", "H", "X", "Y", "Z", "X^½", "X^⅓", "X^¼", "X^⅛", "X^⅟₁₆", "X^⅟₃₂", From a9205f04ba3a3f2c232133fd23a95b041b6fb97d Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 02:34:04 -0300 Subject: [PATCH 05/38] Analyze subgroups before disabling permutation gates to prevent mixing measured and coherent qubits --- src/circuit/Gate.js | 34 ++++++++++++++- src/circuit/GateColumn.js | 43 +++++++++++++++--- test/circuit/CircuitDefinition.test.js | 60 ++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 7 deletions(-) diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index bafe9020..0e07856e 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -180,6 +180,12 @@ class Gate { * @type {undefined | !function(!int) : !int} */ this.knownBitPermutationFunc = undefined; + /** + * When a permutation factors into sub-permutations over subsets of the bits, this map indexes each bit with + * an id for the group that it belongs to. + * @type {!Array.} + */ + this.knownBitPermutationGroupMasks = undefined; /** * Indicates that a gate is a phasing function, and that it phases each computation basis state by the amount * returned by this function. @@ -278,6 +284,7 @@ class Gate { g.knownPermutationFuncTakingInputs = this.knownPermutationFuncTakingInputs; g.customColumnContextProvider = this.customColumnContextProvider; g.customDisableReasonFinder = this.customDisableReasonFinder; + g.knownBitPermutationGroupMasks = this.knownBitPermutationGroupMasks; return g; } @@ -568,11 +575,12 @@ class GateBuilder { * Provides a function equivalent to how the gate rearranges wires, for checking in tests if the gate's behavior is * correct. * @param {!function(!int) : !int} knownBitPermutationFunc Returns the output of the permutation for a - * given input, assuming the gate is exactly sized to the overall circuit. + * given input, assuming the gate is exactly sized to the overall circuit. * @returns {!GateBuilder} */ setKnownEffectToBitPermutation(knownBitPermutationFunc) { this.gate.knownBitPermutationFunc = knownBitPermutationFunc; + this.gate.knownBitPermutationGroupMasks = permutationGrouping(knownBitPermutationFunc, this.gate.height); this.gate._isDefinitelyUnitary = true; this.gate._stableDuration = Infinity; this.gate._hasNoEffect = false; @@ -928,4 +936,28 @@ class GateBuilder { } } +/** + * @param {!function(!int) : !int} knownBitPermutationFunc Returns the output of the permutation for a + * given input, assuming the gate is exactly sized to the overall circuit. + * @param {!int} height + * @returns {!Array.} + */ +function permutationGrouping(knownBitPermutationFunc, height) { + let seen = new Set(); + let result = []; + for (let i = 0; i < height; i++) { + let mask = 0; + let j = i; + while (!seen.has(j)) { + seen.add(j); + mask |= 1 << j; + j = knownBitPermutationFunc(j); + } + if (mask !== 0) { + result.push(mask); + } + } + return result; +} + export {Gate, GateBuilder} diff --git a/src/circuit/GateColumn.js b/src/circuit/GateColumn.js index fbaca1db..f809ebb8 100644 --- a/src/circuit/GateColumn.js +++ b/src/circuit/GateColumn.js @@ -110,6 +110,21 @@ class GateColumn { return false; } + /** + * @returns {!int} + */ + controlMask() { + let mask = 0; + for (let i = 0; i < this.gates.length; i++) { + if (this.gates[i] !== undefined && + this.gates[i].definitelyHasNoEffect() && + this.gates[i].isControl()) { + mask |= 1 << i; + } + } + return mask; + } + /** * @param {!int} inputMeasureMask * @param {!int} row @@ -170,25 +185,41 @@ class GateColumn { let g = this.gates[row]; let mask = ((1 << g.height) - 1) << row; let maskMeasured = mask & inputMeasureMask; - if (maskMeasured !== 0) { + if (maskMeasured !== 0 && g.knownBitPermutationFunc === undefined) { // Don't try to superpose measured qubits. if (g.effectMightCreateSuperpositions()) { return "no\nremix\n(sorry)"; } + // Don't try to mix measured and coherent qubits, or coherently mix measured qubits. if (g.effectMightPermutesStates()) { - // Only permutations that respect bit boundaries can be performed on mixed qubits. - if (maskMeasured !== mask && (g.knownBitPermutationFunc === undefined || - this.hasMeasuredControl(inputMeasureMask))) { + if (maskMeasured !== mask || this.hasCoherentControl(inputMeasureMask)) { return "no\nremix\n(sorry)"; } + } + } - // Permutations affecting classical states can't have quantum controls. - if (this.hasCoherentControl(inputMeasureMask)) { + // Check permutation subgroups for bad mixing of measured and coherent qubits. + if (g.knownBitPermutationGroupMasks !== undefined) { + for (let maskGroup of g.knownBitPermutationGroupMasks) { + let isSingleton = ((maskGroup - 1) & maskGroup) === 0; + if (isSingleton) { + continue; + } + + maskGroup <<= row; + let hasCoherentQubits = (maskGroup & inputMeasureMask) !== maskGroup; + let hasMeasuredQubits = (maskGroup & inputMeasureMask) !== 0; + let coherentControl = this.hasCoherentControl(inputMeasureMask); + let controlled = this.hasControl(inputMeasureMask); + let coherentControlledMixingOfMeasured = hasMeasuredQubits && coherentControl; + let controlledMixingOfCoherentAndMeasured = hasCoherentQubits && hasMeasuredQubits && controlled; + if (coherentControlledMixingOfMeasured || controlledMixingOfCoherentAndMeasured) { return "no\nremix\n(sorry)"; } } } + return undefined; } diff --git a/test/circuit/CircuitDefinition.test.js b/test/circuit/CircuitDefinition.test.js index 66970dcf..36d076c9 100644 --- a/test/circuit/CircuitDefinition.test.js +++ b/test/circuit/CircuitDefinition.test.js @@ -901,6 +901,66 @@ suite.test("gateAtLocIsDisabledReason", () => { bad(1, 0, `MR`, ['R', Gates.Detectors.XDetectControlClear]); bad(1, 0, `MR`, ['R', Gates.Detectors.YDetectControlClear]); good(1, 0, `MR`, ['R', Gates.Detectors.ZDetectControlClear]); + + // Permutation sub-groups. + good(2, 0, `--P- + --/- + --/- + --/- + H-●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + good(2, 0, `-MP- + --/- + --/- + -M/- + H-●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + good(2, 0, `--P- + --/- + --/- + -M/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + good(2, 0, `-MP- + --/- + --/- + -M/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + good(2, 0, `--P- + -M/- + -M/- + --/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + good(2, 0, `-MP- + -M/- + -M/- + -M/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + bad(2, 0, `--P- + -M/- + -M/- + --/- + H-●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + bad(2, 0, `--P- + -M/- + --/- + --/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + + // Non-trivial subgroup. + good(2, 0, `--P- + -M/- + -M/- + --/- + -M/- + --/- + --/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); + bad(2, 0, `--P- + -M/- + --/- + -M/- + -M/- + --/- + --/- + HM●-`, ['P', Gates.InterleaveBitsGates.InterleaveBitsGateFamily]); }); suite.test("gateAtLocIsDisabledReason_controls", () => { From e859b20355bc40da1d85687d35c76fd667cc09c2 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 02:39:41 -0300 Subject: [PATCH 06/38] Only run travis push checks on master --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 9890ddcb..8f40d6fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,6 +2,8 @@ sudo: false language: node_js node_js: 5.1 install: npm install +branches: + only: master before_script: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start From ab798e9cdca8c44e024fce06bf0f327a4ed1f830 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 02:53:32 -0300 Subject: [PATCH 07/38] Run webgl tests on travis despite detecting potential issues, but ignore just the failures instead of also the succeeding tests --- test/KarmaTestRunner.test.js | 5 ++++- test/TestUtil.js | 14 ++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/test/KarmaTestRunner.test.js b/test/KarmaTestRunner.test.js index 5be71271..cdc5215a 100644 --- a/test/KarmaTestRunner.test.js +++ b/test/KarmaTestRunner.test.js @@ -52,7 +52,10 @@ let promiseRunTest = (suite, name, method) => { return promise.then(() => { result.success = true; if (status.warn_only) { - console.warn(`${suite.name}.${name} passed, but is set to warn_only (${status.warn_only})`) + let msg = status.warn_message !== undefined ? + status.warn_message : + `${suite.name}.${name} passed, but is set to warn_only (${status.warn_only})`; + console.warn(msg); } return finish(); }, ex => { diff --git a/test/TestUtil.js b/test/TestUtil.js index 404e0054..f415e6e4 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -301,6 +301,8 @@ export function assertThrows(func, extraArgCatcher) { /** @type {boolean|undefined} */ let __webGLSupportPresent = undefined; +/** @type {boolean|undefined} */ +let _partialWebGLSupportPresent = undefined; function isWebGLSupportPresent() { if (__webGLSupportPresent === undefined) { __webGLSupportPresent = false; @@ -308,6 +310,8 @@ function isWebGLSupportPresent() { let canvas = document.createElement('canvas'); let ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (ctx instanceof WebGLRenderingContext) { + _partialWebGLSupportPresent = true; + let shader = ctx.createShader(WebGLRenderingContext.VERTEX_SHADER); ctx.shaderSource(shader, ` precision highp float; @@ -327,6 +331,13 @@ function isWebGLSupportPresent() { return __webGLSupportPresent; } +/** + * @returns {!boolean|undefined} + */ +function isPartialWebGLSupportPresent() { + return !isWebGLSupportPresent() && _partialWebGLSupportPresent; +} + let promiseImageDataFromSrc = src => { let img = document.createElement('img'); img.src = src; @@ -409,6 +420,9 @@ export class Suite { status.log.push(msg); assertThat(undefined); // Cancel 'no assertion' warning. return; + } else if (isPartialWebGLSupportPresent()) { + status.warn_only = true; + status.warn_message = `Ignoring ${this.name}.${caseName} failure due to lack of WebGL support.`; } let preTexCount = WglTexturePool.getUnReturnedTextureCount(); From 23128d0c3ec5942f2ce68e2c817230f6de575190 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 02:57:38 -0300 Subject: [PATCH 08/38] Fix partial support test --- test/TestUtil.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/test/TestUtil.js b/test/TestUtil.js index f415e6e4..664e1a04 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -302,7 +302,7 @@ export function assertThrows(func, extraArgCatcher) { /** @type {boolean|undefined} */ let __webGLSupportPresent = undefined; /** @type {boolean|undefined} */ -let _partialWebGLSupportPresent = undefined; +let __onlyPartialWebGLSupportPresent = undefined; function isWebGLSupportPresent() { if (__webGLSupportPresent === undefined) { __webGLSupportPresent = false; @@ -310,7 +310,7 @@ function isWebGLSupportPresent() { let canvas = document.createElement('canvas'); let ctx = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); if (ctx instanceof WebGLRenderingContext) { - _partialWebGLSupportPresent = true; + __webGLSupportPresent = true; let shader = ctx.createShader(WebGLRenderingContext.VERTEX_SHADER); ctx.shaderSource(shader, ` @@ -322,9 +322,8 @@ function isWebGLSupportPresent() { // HACK: tests on travis-ci give this warning when compiling shaders, and then give // bad test results. Checking for it is a workaround to make the build pass. - if (ctx.getShaderInfoLog(shader).indexOf("extension `GL_ARB_gpu_shader5' unsupported") === -1) { - __webGLSupportPresent = true; - } + __onlyPartialWebGLSupportPresent = ( + ctx.getShaderInfoLog(shader).indexOf("extension `GL_ARB_gpu_shader5' unsupported") !== -1); } } } @@ -334,8 +333,8 @@ function isWebGLSupportPresent() { /** * @returns {!boolean|undefined} */ -function isPartialWebGLSupportPresent() { - return !isWebGLSupportPresent() && _partialWebGLSupportPresent; +function isOnlyPartialWebGLSupportPresent() { + return !isWebGLSupportPresent() && __onlyPartialWebGLSupportPresent; } let promiseImageDataFromSrc = src => { @@ -420,7 +419,7 @@ export class Suite { status.log.push(msg); assertThat(undefined); // Cancel 'no assertion' warning. return; - } else if (isPartialWebGLSupportPresent()) { + } else if (isOnlyPartialWebGLSupportPresent()) { status.warn_only = true; status.warn_message = `Ignoring ${this.name}.${caseName} failure due to lack of WebGL support.`; } From 5c9150e0e3617a1ea2c8fb6e2f77943d6bfc4964 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 03:09:12 -0300 Subject: [PATCH 09/38] Ignore extraneous extension support warnings on travis --- src/Config.js | 2 ++ src/webgl/WglShader.js | 12 ++++++++++-- test/KarmaTestRunner.test.js | 12 ++++++------ test/TestUtil.js | 10 +++++++--- 4 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/Config.js b/src/Config.js index e6bccfb8..a5e518fb 100644 --- a/src/Config.js +++ b/src/Config.js @@ -92,4 +92,6 @@ Config.DEFAULT_STROKE_THICKNESS = 1; Config.CHECK_WEB_GL_ERRORS_EVEN_ON_HOT_PATHS = false; Config.SEMI_STABLE_RANDOM_VALUE_LIFETIME_MILLIS = 300; +Config.IGNORED_WEBGL_INFO_TERMS = []; + export {Config} diff --git a/src/webgl/WglShader.js b/src/webgl/WglShader.js index 2ec57a19..5df35f4a 100644 --- a/src/webgl/WglShader.js +++ b/src/webgl/WglShader.js @@ -219,8 +219,16 @@ class WglCompiledShader { let info = gl.getShaderInfoLog(shader); if (info !== '') { - console.warn("WebGLShader: gl.getShaderInfoLog() wasn't empty: " + gl.getShaderInfoLog(shader)); - console.warn("Source code was: " + sourceCode); + let ignored = false; + for (let term in Config.IGNORED_WEBGL_INFO_TERMS) { + if (info.indexOf(term)) { + ignored = true; + } + } + if (!ignored) { + console.warn("WebGLShader: gl.getShaderInfoLog() wasn't empty: " + gl.getShaderInfoLog(shader)); + console.warn("Source code was: " + sourceCode); + } } if (gl.getShaderParameter(shader, WebGLRenderingContext.COMPILE_STATUS) === false) { diff --git a/test/KarmaTestRunner.test.js b/test/KarmaTestRunner.test.js index cdc5215a..181dfcf5 100644 --- a/test/KarmaTestRunner.test.js +++ b/test/KarmaTestRunner.test.js @@ -51,11 +51,8 @@ let promiseRunTest = (suite, name, method) => { return promise.then(() => { result.success = true; - if (status.warn_only) { - let msg = status.warn_message !== undefined ? - status.warn_message : - `${suite.name}.${name} passed, but is set to warn_only (${status.warn_only})`; - console.warn(msg); + if (status.warn_only && !status.ignore_warn_only_on_success) { + console.warn(`${suite.name}.${name} passed, but is set to warn_only (${status.warn_only})`); } return finish(); }, ex => { @@ -72,7 +69,10 @@ let promiseRunTest = (suite, name, method) => { result.log.push(stackMsg); } if (status.warn_only) { - console.warn(`${suite.name}.${name} FAILED, but is set to warn_only (${status.warn_only})`) + let msg = status.warn_failure_message !== undefined ? + status.warn_failure_message : + `${suite.name}.${name} FAILED, but is set to warn_only (${status.warn_only})`; + console.warn(msg); } result.success = status.warn_only; return finish(); diff --git a/test/TestUtil.js b/test/TestUtil.js index 664e1a04..689d2b77 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -322,8 +322,11 @@ function isWebGLSupportPresent() { // HACK: tests on travis-ci give this warning when compiling shaders, and then give // bad test results. Checking for it is a workaround to make the build pass. - __onlyPartialWebGLSupportPresent = ( - ctx.getShaderInfoLog(shader).indexOf("extension `GL_ARB_gpu_shader5' unsupported") !== -1); + let term = "extension `GL_ARB_gpu_shader5' unsupported"; + __onlyPartialWebGLSupportPresent = ctx.getShaderInfoLog(shader).indexOf(term) !== -1; + if (__onlyPartialWebGLSupportPresent) { + Config.IGNORED_WEBGL_INFO_TERMS.push(term); + } } } } @@ -421,7 +424,8 @@ export class Suite { return; } else if (isOnlyPartialWebGLSupportPresent()) { status.warn_only = true; - status.warn_message = `Ignoring ${this.name}.${caseName} failure due to lack of WebGL support.`; + status.ignore_warn_only_on_success = true; + status.warn_failure_message = `Ignoring ${this.name}.${caseName} failure due to lack of WebGL support.`; } let preTexCount = WglTexturePool.getUnReturnedTextureCount(); From a5c578975ca8b10c74a9b6f4d38bd7b9b2d0a2c2 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 03:13:23 -0300 Subject: [PATCH 10/38] Debug travis --- test/TestUtil.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/TestUtil.js b/test/TestUtil.js index 689d2b77..04f5ba45 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -327,6 +327,8 @@ function isWebGLSupportPresent() { if (__onlyPartialWebGLSupportPresent) { Config.IGNORED_WEBGL_INFO_TERMS.push(term); } + console.log('__onlyPartialWebGLSupportPresent', __onlyPartialWebGLSupportPresent); + console.log(Config.IGNORED_WEBGL_INFO_TERMS); } } } @@ -337,7 +339,7 @@ function isWebGLSupportPresent() { * @returns {!boolean|undefined} */ function isOnlyPartialWebGLSupportPresent() { - return !isWebGLSupportPresent() && __onlyPartialWebGLSupportPresent; + return isWebGLSupportPresent() && __onlyPartialWebGLSupportPresent; } let promiseImageDataFromSrc = src => { From 6531bb76ae95d07e79b5aded580ca10ebdba1033 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 03:18:11 -0300 Subject: [PATCH 11/38] More debugging --- src/webgl/WglShader.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/webgl/WglShader.js b/src/webgl/WglShader.js index 5df35f4a..c0d70f06 100644 --- a/src/webgl/WglShader.js +++ b/src/webgl/WglShader.js @@ -226,6 +226,10 @@ class WglCompiledShader { } } if (!ignored) { + console.log(Config.IGNORED_WEBGL_INFO_TERMS); + for (let term in Config.IGNORED_WEBGL_INFO_TERMS) { + console.log(term, info.indexOf(term)); + } console.warn("WebGLShader: gl.getShaderInfoLog() wasn't empty: " + gl.getShaderInfoLog(shader)); console.warn("Source code was: " + sourceCode); } From 862d0aec846a0e8a44597005cc75b64742f98044 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 03:21:43 -0300 Subject: [PATCH 12/38] Remove debug --- src/webgl/WglShader.js | 6 +----- test/TestUtil.js | 3 +-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/webgl/WglShader.js b/src/webgl/WglShader.js index c0d70f06..4268ef4d 100644 --- a/src/webgl/WglShader.js +++ b/src/webgl/WglShader.js @@ -220,16 +220,12 @@ class WglCompiledShader { let info = gl.getShaderInfoLog(shader); if (info !== '') { let ignored = false; - for (let term in Config.IGNORED_WEBGL_INFO_TERMS) { + for (let term of Config.IGNORED_WEBGL_INFO_TERMS) { if (info.indexOf(term)) { ignored = true; } } if (!ignored) { - console.log(Config.IGNORED_WEBGL_INFO_TERMS); - for (let term in Config.IGNORED_WEBGL_INFO_TERMS) { - console.log(term, info.indexOf(term)); - } console.warn("WebGLShader: gl.getShaderInfoLog() wasn't empty: " + gl.getShaderInfoLog(shader)); console.warn("Source code was: " + sourceCode); } diff --git a/test/TestUtil.js b/test/TestUtil.js index 04f5ba45..f614964d 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -326,9 +326,8 @@ function isWebGLSupportPresent() { __onlyPartialWebGLSupportPresent = ctx.getShaderInfoLog(shader).indexOf(term) !== -1; if (__onlyPartialWebGLSupportPresent) { Config.IGNORED_WEBGL_INFO_TERMS.push(term); + console.log('Only partial WebGL support is present. Some tests may fail and be ignored.') } - console.log('__onlyPartialWebGLSupportPresent', __onlyPartialWebGLSupportPresent); - console.log(Config.IGNORED_WEBGL_INFO_TERMS); } } } From 5c30463065ae452b7693739b5ba12a355e92d977 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 03:42:38 -0300 Subject: [PATCH 13/38] Show ignored partial-support webgl errors --- test/KarmaTestRunner.test.js | 8 ++++++++ test/TestUtil.js | 1 + 2 files changed, 9 insertions(+) diff --git a/test/KarmaTestRunner.test.js b/test/KarmaTestRunner.test.js index 181dfcf5..d48f50e2 100644 --- a/test/KarmaTestRunner.test.js +++ b/test/KarmaTestRunner.test.js @@ -73,6 +73,14 @@ let promiseRunTest = (suite, name, method) => { status.warn_failure_message : `${suite.name}.${name} FAILED, but is set to warn_only (${status.warn_only})`; console.warn(msg); + + if (status.warn_show_error) { + for (let logMsg of result.log) { + for (let line of logMsg.split('\n')) { + console.warn('(ignored) ' + line); + } + } + } } result.success = status.warn_only; return finish(); diff --git a/test/TestUtil.js b/test/TestUtil.js index f614964d..00b3235e 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -427,6 +427,7 @@ export class Suite { status.warn_only = true; status.ignore_warn_only_on_success = true; status.warn_failure_message = `Ignoring ${this.name}.${caseName} failure due to lack of WebGL support.`; + status.warn_show_error = true; } let preTexCount = WglTexturePool.getUnReturnedTextureCount(); From 4f7e96f04296d7a1f563e065a1e4b42ac9703c7b Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 03:57:42 -0300 Subject: [PATCH 14/38] Attempting to fix travis webgl issues with shifted mods --- src/gates/AmplitudeDisplay.js | 4 ++-- src/gates/ModularAdditionGates.js | 12 ++---------- src/gates/MultiplyAccumulateGates.js | 6 +++--- test/webgl/ShaderCoders_Base.test.js | 2 +- 4 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/gates/AmplitudeDisplay.js b/src/gates/AmplitudeDisplay.js index cf89fb6a..a2faef91 100644 --- a/src/gates/AmplitudeDisplay.js +++ b/src/gates/AmplitudeDisplay.js @@ -182,7 +182,7 @@ const SPREAD_LENGTH_ACROSS_POLAR_KETS_SHADER = makePseudoShaderWithInputsAndOutp uniform float bit; float xorBit(float v) { - float b = mod(floor(v/bit), 2.0); + float b = floor(mod(floor(v/bit) + 0.5, 2.0)); float d = 1.0 - 2.0*b; return v + bit*d; } @@ -257,7 +257,7 @@ const TO_RATIOS_VS_REPRESENTATIVE_SHADER = makePseudoShaderWithInputsAndOutputAn ], Outputs.vec4(), `vec4 outputFor(float k) { - return vec4(read_ket(k), read_rep(mod(k, len_rep())).xy); + return vec4(read_ket(k), read_rep(floor(mod(k + 0.5, len_rep()))).xy); }`); /** diff --git a/src/gates/ModularAdditionGates.js b/src/gates/ModularAdditionGates.js index 18b84098..29248570 100644 --- a/src/gates/ModularAdditionGates.js +++ b/src/gates/ModularAdditionGates.js @@ -33,16 +33,8 @@ const MODULAR_ADDITION_SHADER = ketShaderPermute( } float d = read_input_A(); d *= factor; - d = mod(d, r); - float result = mod(out_id + r - d, r); - - // Despite sanity, I consistently get result=33 instead of result=0 when out_id=0, d=0, r=33. - // HACK: Fix it by hand. - if (result >= r) { - result -= r; - } - - return result; + d = floor(mod(d + 0.5, r)); + return floor(mod(out_id + r - d + 0.5, r)); `); ModularAdditionGates.PlusAModRFamily = Gate.buildFamily(1, 16, (span, builder) => builder. diff --git a/src/gates/MultiplyAccumulateGates.js b/src/gates/MultiplyAccumulateGates.js index 933739b1..e5f7f2d0 100644 --- a/src/gates/MultiplyAccumulateGates.js +++ b/src/gates/MultiplyAccumulateGates.js @@ -44,10 +44,10 @@ const BIG_MUL_MOD_SHADER_CODE = ` float t = 0.0; float r; for (int k = 0; k < ${Math.ceil(Config.MAX_WIRE_COUNT/MUL_STEP)}; k++) { - r = mod(f, ${1< { let output = BOOL_TYPE_CODER.outputPart; let shader = combinedShaderPartsWithCode([output], ` bool outputFor(float k) { - return mod(k, 3.0) == 1.0; + return floor(mod(k + 0.5, 3.0)) == 1.0; }`); assertThat(shaderWithOutputPartAndArgs(shader, output, []).readBoolOutputs(3)).isEqualTo(new Uint8Array([ From 57ce05dd35cb5213191195f92d662d06f1bda3df Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 04:03:30 -0300 Subject: [PATCH 15/38] More mod-rounding --- src/gates/ModularMultiplicationGates.js | 8 ++++---- src/gates/ModularMultiplyAccumulateGates.js | 2 +- src/gates/MultiplyAccumulateGates.js | 2 +- test/gates/AmplitudeDisplay.test.js | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gates/ModularMultiplicationGates.js b/src/gates/ModularMultiplicationGates.js index 239aeacc..8e40a068 100644 --- a/src/gates/ModularMultiplicationGates.js +++ b/src/gates/ModularMultiplicationGates.js @@ -50,7 +50,7 @@ const MODULAR_INVERSE_SHADER_CODE = ` if (r.y != 1.0) { return -1.0; } - return mod(mod(s.y, modulus) + modulus, modulus); + return floor(mod(floor(mod(s.y + 0.5, modulus)) + modulus + 0.5, modulus)); } `; @@ -70,7 +70,7 @@ const POW_MOD_SHADER_CODE = ` float f = 1.0; for (int k = 0; k < ${Config.MAX_WIRE_COUNT}; k++) { - if (mod(exponent, 2.0) == 1.0) { + if (floor(mod(exponent + 0.5, 2.0)) == 1.0) { exponent -= 1.0; f = big_mul_mod(f, base, modulus); } @@ -169,7 +169,7 @@ const MODULAR_MULTIPLICATION_SHADER = ketShaderPermute( ` float input_a = read_input_A(); float modulus = read_input_R(); - input_a = mod(input_a, modulus); + input_a = floor(mod(input_a + 0.5, modulus)); float v = modular_multiplicative_inverse(input_a, modulus); if (v == -1.0 || out_id >= modulus) { return out_id; @@ -186,7 +186,7 @@ const MODULAR_INVERSE_MULTIPLICATION_SHADER = ketShaderPermute( ` float input_a = read_input_A(); float modulus = read_input_R(); - input_a = mod(input_a, modulus); + input_a = floor(mod(input_a + 0.5, modulus)); if (modular_multiplicative_inverse(input_a, modulus) == -1.0 || out_id >= modulus) { return out_id; } diff --git a/src/gates/ModularMultiplyAccumulateGates.js b/src/gates/ModularMultiplyAccumulateGates.js index 9ea58fc6..0f2b36d8 100644 --- a/src/gates/ModularMultiplyAccumulateGates.js +++ b/src/gates/ModularMultiplyAccumulateGates.js @@ -36,7 +36,7 @@ const MODULAR_MULTIPLY_ACCUMULATE_SHADER = ketShaderPermute( float d = big_mul_mod(factor * a, b, r); - float in_id = mod(out_id - d, r); + float in_id = floor(mod(out_id - d + 0.5, r)); if (in_id < 0.0) { in_id += r; } diff --git a/src/gates/MultiplyAccumulateGates.js b/src/gates/MultiplyAccumulateGates.js index e5f7f2d0..5bb9c486 100644 --- a/src/gates/MultiplyAccumulateGates.js +++ b/src/gates/MultiplyAccumulateGates.js @@ -64,7 +64,7 @@ const MULTIPLY_ACCUMULATE_SHADER = ketShaderPermute( ` float d1 = read_input_A(); float d2 = read_input_B(); - float d = mod(big_mul_mod(d1, d2, span)*factor, span); + float d = floor(mod(big_mul_mod(d1, d2, span)*factor + 0.5, span)); return mod(out_id + span - d, span);`); MultiplyAccumulateGates.Legacy_MultiplyAddFamily = Gate.buildFamily(3, 16, (span, builder) => builder. diff --git a/test/gates/AmplitudeDisplay.test.js b/test/gates/AmplitudeDisplay.test.js index ca597e83..c5b403e3 100644 --- a/test/gates/AmplitudeDisplay.test.js +++ b/test/gates/AmplitudeDisplay.test.js @@ -44,7 +44,7 @@ suite.testUsingWebGL("amplitudesToPolarKets", () => { 25,new Complex(3,4).phase(),25,0, 2,Math.PI*3/4,2,0, 0.25,Math.PI/2,0.25,0 - ]), 0.0001); + ]), 0.001); input.deallocByDepositingInPool(); }); From b40b950ea05f7abd97930c6928095a361ed3ca41 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 8 Jun 2018 12:55:28 -0300 Subject: [PATCH 16/38] Visual and init-click bug fixes - Fix YDetectControlReset drawing wrong control bulb - Fix wire initial states highlighting and toggling when dragging gates near them and releasing --- src/gates/Detector.js | 2 +- src/ui/DisplayedCircuit.js | 9 ++++----- src/ui/DisplayedInspector.js | 11 +++++++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/gates/Detector.js b/src/gates/Detector.js index 3d9a91fb..e28b973d 100644 --- a/src/gates/Detector.js +++ b/src/gates/Detector.js @@ -258,7 +258,7 @@ function drawControlBulb(args, axis) { args.painter.strokeCircle(p, 5); let r = 5*Math.sqrt(0.5)*1.1; args.painter.strokeLine(p.offsetBy(+r, -r), p.offsetBy(-r, +r)); - args.painter.strokeLine(p.offsetBy(-r, +r), p.offsetBy(+r, -r)); + args.painter.strokeLine(p.offsetBy(-r, -r), p.offsetBy(+r, +r)); break; case 'Z': args.painter.fillCircle(p, 5, "black"); diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index b4894b08..f640b263 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -374,7 +374,7 @@ class DisplayedCircuit { } let rect = this._wireInitialStateClickableRect(row); painter.noteTouchBlocker({rect, cursor: 'pointer'}); - if (hand.pos !== undefined && rect.containsPoint(hand.pos)) { + 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); @@ -948,9 +948,8 @@ class DisplayedCircuit { /** * @param {!Point} pt * @returns {undefined|!int} - * @private */ - _findWireWithInitialStateAreaContaining(pt) { + 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; @@ -977,11 +976,11 @@ class DisplayedCircuit { * @returns {undefined|!DisplayedCircuit} */ tryClick(hand) { - if (hand.pos === undefined) { + if (hand.pos === undefined || hand.heldGate !== undefined) { return undefined; } - let clickedInitialStateWire = this._findWireWithInitialStateAreaContaining(hand.pos); + let clickedInitialStateWire = this.findWireWithInitialStateAreaContaining(hand.pos); if (clickedInitialStateWire !== undefined) { return this.withCircuit(this.circuitDefinition.withSwitchedInitialStateOn(clickedInitialStateWire)) } diff --git a/src/ui/DisplayedInspector.js b/src/ui/DisplayedInspector.js index 9c7b2e5c..c244ed9d 100644 --- a/src/ui/DisplayedInspector.js +++ b/src/ui/DisplayedInspector.js @@ -131,8 +131,15 @@ class DisplayedInspector { if (this.hand.pos === undefined) { return undefined; } - let pos = this.displayedCircuit.findGateWithButtonContaining(this.hand.pos); - return pos === undefined ? undefined : pos.col + ':' + pos.row; + let butBos = this.displayedCircuit.findGateWithButtonContaining(this.hand.pos); + if (butBos !== undefined) { + return `gate-button-${butBos.col}:${butBos.row}`; + } + let initPos = this.displayedCircuit.findWireWithInitialStateAreaContaining(this.hand.pos); + if (initPos !== undefined) { + return `wire-init-${initPos}`; + } + return undefined; } /** From 25849af2cfc2eea931a1e2eab1ba31b9f81709ba Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 13:40:28 -0700 Subject: [PATCH 17/38] Detectors now preserve total probability --- src/gates/Detector.js | 7 ++++++- test/circuit/CircuitStats.test.js | 23 ++++++++++++++++++++++- test/gates/Detector.test.js | 18 ++++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/gates/Detector.js b/src/gates/Detector.js index e28b973d..1f9bcf78 100644 --- a/src/gates/Detector.js +++ b/src/gates/Detector.js @@ -100,7 +100,12 @@ let detectorShader = makePseudoShaderWithInputsAndOutputAndCode( float detectChance = read_detection_weight(0.0) / read_total_weight(0.0); float detection_type = float(rnd < detectChance); float own_type = read_classification(k); - return read_ket(k) * float(detection_type == own_type); + if (detection_type == own_type) { + float matchChance = detectChance * own_type + (1.0 - own_type) * (1.0 - detectChance); + return read_ket(k) / sqrt(matchChance); + } else { + return vec2(0.0, 0.0); + } } `); diff --git a/test/circuit/CircuitStats.test.js b/test/circuit/CircuitStats.test.js index 2cc2fd13..3d7a951d 100644 --- a/test/circuit/CircuitStats.test.js +++ b/test/circuit/CircuitStats.test.js @@ -16,7 +16,6 @@ import {Suite, assertThat, assertTrue} from "test/TestUtil.js" import {CircuitStats} from "src/circuit/CircuitStats.js" import {CircuitDefinition} from "src/circuit/CircuitDefinition.js" -import {Complex} from "src/math/Complex.js" import {GateColumn} from "src/circuit/GateColumn.js" import {Gates} from "src/gates/AllGates.js" import {Matrix} from "src/math/Matrix.js" @@ -367,3 +366,25 @@ suite.testUsingWebGL("initial_states", () => { assertThat(stats.qubitDensityMatrix(9, 4).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([0, +1, 0]); assertThat(stats.qubitDensityMatrix(9, 5).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo([0, -1, 0]); }); + +suite.testUsingWebGL("distillation", () => { + let c = circuit( + ` + -X-X--X-X--X-X--X-X-------X-X--X-X-------X-X------------HTH-0- + -X-X--X-X--X-X-------X-X--X-X-------X-X-------X-X-------HTH-0- + -X-X--X-X-------X-X--X-X-------X-X--X-X------------X-X--HTH-0- + -X-X-------X-X--X-X--X-X-----------------X-X--X-X--X-X--HTH-0- + -X-X----------------------X-X--X-X--X-X--X-X--X-X--X-X-------- + -#T]--#T]--#T]--#T]--#T]--#T]--#T]--#T]--#T]--#T]--#T]-------- + `, + [']', Gates.Detectors.XDetectControlClear], + ['0', Gates.PostSelectionGates.PostSelectOff], + ['#', Gates.Controls.XControl], + ['T', Gates.OtherZ.Z4]); + for (let i = 0; i < 5; i++) { + let stats = CircuitStats.fromCircuitAtTime(c, 0); + assertThat(stats.qubitDensityMatrix(Infinity, 4).qubitDensityMatrixToBlochVector()).isApproximatelyEqualTo( + [0, Math.sqrt(0.5), -Math.sqrt(0.5)]); + assertThat(stats.survivalRate(Infinity)).isApproximatelyEqualTo(1, 0.001); + } +}); diff --git a/test/gates/Detector.test.js b/test/gates/Detector.test.js index 5764182b..dda9fc1e 100644 --- a/test/gates/Detector.test.js +++ b/test/gates/Detector.test.js @@ -88,3 +88,21 @@ suite.testUsingWebGL("collapsed-control-clicks", () => { } } }); + +suite.testUsingWebGL("renormalizes", () => { + // Doesn't decrease survival probability. + let c = circuit( + '-]-D-]-D-]-D-]-', + [']', Gates.Detectors.XDetector], + ['0', Gates.PostSelectionGates.PostSelectOff]); + let stats = CircuitStats.fromCircuitAtTime(c, 0); + assertThat(stats.survivalRate(Infinity)).isApproximatelyEqualTo(1, 0.001); + + // Renormalization doesn't increase survival probability. + let c2 = circuit( + '-]-0-D-]-D-]-D-]-', + [']', Gates.Detectors.XDetector], + ['0', Gates.PostSelectionGates.PostSelectOff]); + let stats2 = CircuitStats.fromCircuitAtTime(c2, 0); + assertThat(stats2.survivalRate(Infinity)).isApproximatelyEqualTo(0.5, 0.001); +}); From 631df9bdd5c6db341a09c8c2e7ff3c3d68480612 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 13:40:56 -0700 Subject: [PATCH 18/38] Renamed |S> initial state to |i> --- src/circuit/CircuitDefinition.js | 18 ++++-------------- src/circuit/Serializer.js | 4 ++-- src/gates/AllGates.js | 12 +++++++++++- test/circuit/CircuitStats.test.js | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/circuit/CircuitDefinition.js b/src/circuit/CircuitDefinition.js index 1b74cfba..41d88984 100644 --- a/src/circuit/CircuitDefinition.js +++ b/src/circuit/CircuitDefinition.js @@ -21,7 +21,7 @@ import {equate_Maps} from "src/base/Equate.js"; import {Gate} from "src/circuit/Gate.js" import {GateColumn} from "src/circuit/GateColumn.js" import {GateShaders} from "src/circuit/GateShaders.js" -import {Gates} from "src/gates/AllGates.js" +import {Gates, INITIAL_STATES_TO_GATES} from "src/gates/AllGates.js" import {Point} from "src/math/Point.js" import {seq, Seq} from "src/base/Seq.js" import {Util} from "src/base/Util.js" @@ -147,7 +147,7 @@ class CircuitDefinition { withSwitchedInitialStateOn(wire) { let m = new Map([...this.customInitialValues.entries()]); let v = m.get(wire); - let cycle = [undefined, '1', '+', '-', 'S', 'S†']; + let cycle = [...INITIAL_STATES_TO_GATES.keys()]; let newVal = cycle[(cycle.indexOf(v) + 1) % cycle.length]; if (newVal === undefined) { m.delete(wire); @@ -901,10 +901,10 @@ class CircuitDefinition { applyInitialStateOperations(ctx) { for (let wire = 0; wire < this.numWires; wire++) { let state = this.customInitialValues.get(wire); - if (!STATE_TO_GATES.has(state)) { + if (!INITIAL_STATES_TO_GATES.has(state)) { throw new DetailedError('Unrecognized initial state.', {state}); } - for (let gate of STATE_TO_GATES.get(state)) { + for (let gate of INITIAL_STATES_TO_GATES.get(state)) { GateShaders.applyMatrixOperation(ctx.withRow(ctx.row + wire), gate.knownMatrixAt(ctx.time)) } } @@ -1117,16 +1117,6 @@ function firstLastMatchInRange(rangeLen, predicate){ return [first, last]; } -/** @type {!Map.>} */ -const STATE_TO_GATES = new Map([ - [undefined, []], - ['1', [Gates.HalfTurns.X]], - ['+', [Gates.HalfTurns.H]], - ['-', [Gates.HalfTurns.H, Gates.HalfTurns.Z]], - ['S', [Gates.HalfTurns.H, Gates.QuarterTurns.SqrtZForward]], - ['S†', [Gates.HalfTurns.H, Gates.QuarterTurns.SqrtZBackward]] -]); - CircuitDefinition.EMPTY = new CircuitDefinition(0, []); export {CircuitDefinition} diff --git a/src/circuit/Serializer.js b/src/circuit/Serializer.js index b246e698..18ca83fb 100644 --- a/src/circuit/Serializer.js +++ b/src/circuit/Serializer.js @@ -21,7 +21,7 @@ import {DetailedError} from "src/base/DetailedError.js" import {Format} from "src/base/Format.js" import {Gate, GateBuilder} from "src/circuit/Gate.js" import {GateColumn} from "src/circuit/GateColumn.js" -import {Gates} from "src/gates/AllGates.js" +import {Gates, INITIAL_STATES_TO_GATES} from "src/gates/AllGates.js" import {Matrix} from "src/math/Matrix.js" import {Util} from "src/base/Util.js" import {notifyAboutRecoveryFromUnexpectedError} from "src/fallback.js" @@ -428,7 +428,7 @@ function _fromJson_InitialState(json) { // 0 is the default. Don't need to do anything. } else if (v === 1) { result.set(i, '1'); - } else if (['+', '-', 'S', 'S†'].indexOf(v) !== -1) { + } else if (INITIAL_STATES_TO_GATES.has(v)) { result.set(i, v); } else { throw new DetailedError('Unrecognized initial state key.', {v, json}); diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 73a9f46c..ca04ffc3 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -334,4 +334,14 @@ Gates.BottomToolboxGroups = [ }, ]; -export {Gates} +/** @type {!Map.>} */ +const INITIAL_STATES_TO_GATES = new Map([ + [undefined, []], + ['1', [Gates.HalfTurns.X]], + ['+', [Gates.HalfTurns.H]], + ['-', [Gates.HalfTurns.H, Gates.HalfTurns.Z]], + ['i', [Gates.HalfTurns.H, Gates.QuarterTurns.SqrtZForward]], + ['-i', [Gates.HalfTurns.H, Gates.QuarterTurns.SqrtZBackward]] +]); + +export {Gates, INITIAL_STATES_TO_GATES} diff --git a/test/circuit/CircuitStats.test.js b/test/circuit/CircuitStats.test.js index 3d7a951d..33c3435d 100644 --- a/test/circuit/CircuitStats.test.js +++ b/test/circuit/CircuitStats.test.js @@ -355,7 +355,7 @@ suite.testUsingWebGL('classical-bit-rotate-with-classical-control-does-fire', () suite.testUsingWebGL("initial_states", () => { let circuit = Serializer.fromJson(CircuitDefinition, { - init: [0, 1, '+', '-', 'S', 'S†'], + init: [0, 1, '+', '-', 'i', '-i'], cols: [], }); let stats = CircuitStats.fromCircuitAtTime(circuit, 0); From ff9f3c39539fea567e28ff1b66221297faab6c54 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 13:41:10 -0700 Subject: [PATCH 19/38] Add magic state distillation example --- html/menu.partial.html | 3 ++- src/ui/menu.js | 36 +++++++++++++++++++++++++++++++++++- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/html/menu.partial.html b/html/menu.partial.html index 0ebb87bc..fd1a1db3 100644 --- a/html/menu.partial.html +++ b/html/menu.partial.html @@ -39,7 +39,7 @@ - diff --git a/src/ui/menu.js b/src/ui/menu.js index a6654855..c3646ccf 100644 --- a/src/ui/menu.js +++ b/src/ui/menu.js @@ -236,6 +236,38 @@ const shorLink = { {"id":"~out","name":"out:","matrix":"{{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}}"} ] }; +const distillLink = { + "cols":[ + ["H","H","H","H","H"], + [1,"Z","Z","Z",1,"⊖"], + [1,"Z","Z",1,"Z",1,"⊖"], + [1,"Z",1,"Z","Z",1,1,"⊖"], + [1,1,"Z","Z","Z",1,1,1,"⊖"], + ["Z","Z","Z","Z","Z",1,1,1,1,"⊖"], + ["Z",1,1,"Z","Z",1,1,1,1,1,"⊖"], + ["Z",1,"Z",1,"Z",1,1,1,1,1,1,"⊖"], + ["Z","Z",1,1,"Z",1,1,1,1,1,1,1,"⊖"], + ["Z",1,"Z","Z",1,1,1,1,1,1,1,1,1,"⊖"], + ["Z","Z",1,"Z",1,1,1,1,1,1,1,1,1,1,"⊖"], + ["Z","Z","Z",1,1,1,1,1,1,1,1,1,1,1,1,"⊖"], + [1,"Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼","Z^¼"], + [1,"H","H","H","H","H","H","H","H","H","H","H","H","H","H","H"], + [1,"Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure","Measure"], + [1,"X","X","X",1,"•"], + [1,"X","X",1,"X",1,"•"], + [1,"X",1,"X","X",1,1,"•"], + [1,1,"X","X","X",1,1,1,"•"], + ["Z","X","X","X","X",1,1,1,1,"•"], + ["Z",1,1,"X","X",1,1,1,1,1,"•"], + ["Z",1,"X",1,"X",1,1,1,1,1,1,"•"], + ["Z","X",1,1,"X",1,1,1,1,1,1,1,"•"], + ["Z",1,"X","X",1,1,1,1,1,1,1,1,1,"•"], + ["Z","X",1,"X",1,1,1,1,1,1,1,1,1,1,"•"], + ["Z","X","X",1,1,1,1,1,1,1,1,1,1,1,1,"•"], + ["X","Chance4"], + ["Amps1","|0⟩⟨0|","|0⟩⟨0|","|0⟩⟨0|","|0⟩⟨0|"] + ] +}; /** * @param {!Revision} revision @@ -275,6 +307,7 @@ function initMenu(revision, obsIsAnyOverlayShowing) { const chshTestAnchor = /** @type {!HTMLAnchorElement} */ document.getElementById('example-chsh-test'); const qftAnchor = /** @type {!HTMLAnchorElement} */ document.getElementById('example-qft'); const shorAnchor = /** @type {!HTMLAnchorElement} */ document.getElementById('example-anchor-shor'); + const distillAnchor = /** @type {!HTMLAnchorElement} */ document.getElementById('example-anchor-distill'); for (let [a, t] of [[groverAnchor, groverLink], [shorAnchor, shorLink], @@ -284,7 +317,8 @@ function initMenu(revision, obsIsAnyOverlayShowing) { [superdenseCodeAnchor, superdenseCodingLink], [symmetryBreakAnchor, symmetryBreakingLink], [chshTestAnchor, chshTestLink], - [qftAnchor, qftLink]]) { + [qftAnchor, qftLink], + [distillAnchor, distillLink]]) { let text = JSON.stringify(t); a.href = "#circuit=" + text; a.onclick = ev => { From 5d2b332c69e358fb71c0ecba9da6c25e54b3ff81 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 13:46:39 -0700 Subject: [PATCH 20/38] Fix density matrix display showing transposed contents --- src/draw/MathPainter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/draw/MathPainter.js b/src/draw/MathPainter.js index 12f19052..18032091 100644 --- a/src/draw/MathPainter.js +++ b/src/draw/MathPainter.js @@ -551,7 +551,7 @@ class MathPainter { let traceCouplingsWith = cellTraceFunc => painter.trace(trace => { for (let row = 0; row < numRows; row++) { for (let col = 0; col < numCols; col++) { - let k = (row * numCols + col) * 2; + let k = (row + col * numRows) * 2; cellTraceFunc( trace, buf[k], @@ -623,7 +623,7 @@ class MathPainter { MathPainter.paintMatrixTooltip(painter, matrix, drawArea, focusPoints, (c, r) => c === r ? `Probability of |${Util.bin(c, n)}⟩` : - `Coupling of |${Util.bin(c, n)}⟩ to ⟨${Util.bin(r, n)}|`, + `Coupling of |${Util.bin(r, n)}⟩ to ⟨${Util.bin(c, n)}|`, (c, r, v) => c === r ? (matrix.cell(c, r).real*100).toFixed(4) + "%" : matrix.cell(c, r).toString(new Format(false, 0, 6, ", "))); From f2d99b270d1736aaa56b661ca4876d2f9055b666 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 13:48:41 -0700 Subject: [PATCH 21/38] Fix transposed tooltip for density matrix display --- src/draw/MathPainter.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/draw/MathPainter.js b/src/draw/MathPainter.js index 18032091..36910ef5 100644 --- a/src/draw/MathPainter.js +++ b/src/draw/MathPainter.js @@ -625,8 +625,8 @@ class MathPainter { `Probability of |${Util.bin(c, n)}⟩` : `Coupling of |${Util.bin(r, n)}⟩ to ⟨${Util.bin(c, n)}|`, (c, r, v) => c === r ? - (matrix.cell(c, r).real*100).toFixed(4) + "%" : - matrix.cell(c, r).toString(new Format(false, 0, 6, ", "))); + (matrix.cell(r, c).real*100).toFixed(4) + "%" : + matrix.cell(r, c).toString(new Format(false, 0, 6, ", "))); } } From 34e02ffd5e1f78d799a5ae3e42e5eb36fa28abd4 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 14:22:05 -0700 Subject: [PATCH 22/38] Fix svd performing permutations incorrectly --- src/math/Matrix.js | 12 +++++++----- test/math/Matrix.test.js | 7 ++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/math/Matrix.js b/src/math/Matrix.js index 9eefeac6..2bcd2647 100644 --- a/src/math/Matrix.js +++ b/src/math/Matrix.js @@ -1343,13 +1343,15 @@ class Matrix { // Fix ordering, so that the singular values are ascending. let permutation = Seq.range(this._width).sortedBy(i => -S.cell(i, i).norm2()).toArray(); for (let i = 0; i < S._width; i++) { - let j = permutation[i]; + let j = permutation.indexOf(i); if (i !== j) { U._inline_colMix_postMultiply(i, j, Matrix.PAULI_X); V._inline_rowMix_preMultiply(i, j, Matrix.PAULI_X); - [S._buffer[i*2], S._buffer[j*2]] = [S._buffer[j*2], S._buffer[i*2]]; - [S._buffer[i*2+1], S._buffer[j*2+1]] = [S._buffer[j*2+1], S._buffer[i*2+1]]; - [permutation[j], permutation[i]] = [permutation[i], permutation[j]] + let si = i*(S._width + 1)*2; + let sj = j*(S._width + 1)*2; + [S._buffer[si], S._buffer[sj]] = [S._buffer[sj], S._buffer[si]]; + [S._buffer[si+1], S._buffer[sj+1]] = [S._buffer[sj+1], S._buffer[si+1]]; + [permutation[j], permutation[i]] = [permutation[i], permutation[j]]; } } @@ -1365,7 +1367,7 @@ class Matrix { } /** - * @param {!int} rowIndex + * @param {!int} colIndex * @returns {!Array.} */ getColumn(colIndex) { diff --git a/test/math/Matrix.test.js b/test/math/Matrix.test.js index 6f592428..78c0f641 100644 --- a/test/math/Matrix.test.js +++ b/test/math/Matrix.test.js @@ -628,6 +628,8 @@ suite.test("singularValueDecomposition", () => { new Complex(2, 3), new Complex(5, 7), new Complex(11, 13), new Complex(17, 19), new Complex(-23, 29), new Complex(31, 37), new Complex(41, -43), new Complex(47, -53), new Complex(59, 61))); + + assertSvdDecompositionWorksFor(Matrix.generateDiagonal(4, k => Complex.polar(1, Math.PI*2/3*k))); }); suite.test("singularValueDecomposition_randomized", () => { @@ -637,7 +639,7 @@ suite.test("singularValueDecomposition_randomized", () => { } }); -suite.test("closestUnitary_2x2", () => { +suite.test("closestUnitary", () => { let i = Complex.I; let ni = i.neg(); assertThat(Matrix.square(0, 0, 0, 0).closestUnitary()). @@ -655,6 +657,9 @@ suite.test("closestUnitary_2x2", () => { 1, -1, 1, -1, 1, ni, -1, i); assertThat(m.closestUnitary(0.001)).isApproximatelyEqualTo(m.times(0.5)); + + let m2 = Matrix.generateDiagonal(4, k => Complex.polar(1, Math.PI*2/3*k)); + assertThat(m2.closestUnitary(0.001)).isApproximatelyEqualTo(m2); }); suite.test("eigenDecomposition", () => { From f09cd086022af4837d32babcd55bb0b85813b87c Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 16:35:36 -0700 Subject: [PATCH 23/38] Add option to export simulation state as JSON - Define Gate.processedStatsToJsonFunc - Tranpose density matrix data for density matrix display - Add processedStatsToJsonFunc to most displays - Track most recent CircuitStats in main - Added "to readable json" methods to Matrix and CircuitStats --- html/export.partial.html | 10 +++++- src/circuit/CircuitStats.js | 57 ++++++++++++++++++++++++++++++- src/circuit/Gate.js | 16 +++++++++ src/draw/MathPainter.js | 6 ++-- src/gates/AmplitudeDisplay.js | 28 ++++++++++++++- src/gates/DensityMatrixDisplay.js | 5 ++- src/gates/ProbabilityDisplay.js | 20 ++++++++++- src/gates/SampleDisplay.js | 7 +++- src/main.js | 5 ++- src/math/Matrix.js | 27 ++++++++++++++- src/ui/exports.js | 24 +++++++++++-- test/TestUtil.js | 2 ++ test/circuit/CircuitStats.test.js | 43 +++++++++++++++++++++++ 13 files changed, 237 insertions(+), 13 deletions(-) diff --git a/html/export.partial.html b/html/export.partial.html index 1b42e0fd..95f1cafc 100644 --- a/html/export.partial.html +++ b/html/export.partial.html @@ -13,11 +13,19 @@ - JSON - Parsable representation of current circuit. + Circuit JSON - Parsable representation of current circuit.
  

         
+ +
+ Simulation Data JSON - Output amplitudes, detector results, display data, etc. +
+   +
+

+        
diff --git a/src/circuit/CircuitStats.js b/src/circuit/CircuitStats.js index bcd78719..8951c4f9 100644 --- a/src/circuit/CircuitStats.js +++ b/src/circuit/CircuitStats.js @@ -18,7 +18,7 @@ import {CircuitShaders} from "src/circuit/CircuitShaders.js" import {KetTextureUtil} from "src/circuit/KetTextureUtil.js" import {Controls} from "src/circuit/Controls.js" import {DetailedError} from "src/base/DetailedError.js" -import {Matrix} from "src/math/Matrix.js" +import {Matrix, complexVectorToReadableJson} from "src/math/Matrix.js" import {Shaders} from "src/webgl/Shaders.js" import {Serializer} from "src/circuit/Serializer.js" import {Util} from "src/base/Util.js" @@ -108,6 +108,48 @@ class CircuitStats { return this._qubitDensities[col][wireIndex]; } + /** + * Converts the circuit stats into an exportable JSON object. + * @returns {!object} + */ + toReadableJson() { + return { + output_amplitudes: complexVectorToReadableJson(this.finalState.getColumn(0)), + time_parameter: this.time, + circuit: Serializer.toJson(this.circuitDefinition), + chance_of_surviving_to_each_column: this._survivalRates, + computed_bloch_vectors_by_column_then_wire: this._qubitDensities.map( + col => col.map(singleQubitDensityMatrixToReadableJson) + ), + displays: this._customStatsToReadableJson() + }; + } + + _customStatsToReadableJson() { + let result = []; + for (let [key, data] of this._customStatsProcessed.entries()) { + let [col, row] = key.split(':'); + row = parseInt(row); + col = parseInt(col); + let gate = this.circuitDefinition.columns[col].gates[row]; + if (gate.processedStatsToJsonFunc !== undefined) { + data = gate.processedStatsToJsonFunc(data); + } + result.push({ + location: { + wire: row, + column: col, + }, + type: { + serialized_id: gate.serializedId, + name: gate.name, + }, + data + }); + } + return result; + } + /** * Determines how often the circuit evaluation survives to the given column, without being post-selected out. * @@ -341,6 +383,19 @@ class CircuitStats { } } +/** + * @param {!Matrix} matrix + */ +function singleQubitDensityMatrixToReadableJson(matrix) { + if (matrix.hasNaN()) { + return null; + } + let [x, y, z] = matrix.qubitDensityMatrixToBlochVector(); + x *= -1; + z *= -1; + return {x, y, z}; +} + CircuitStats.EMPTY = CircuitStats.withNanDataFromCircuitAtTime(CircuitDefinition.EMPTY, 0); export {CircuitStats} diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 0e07856e..9db006b3 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -71,6 +71,11 @@ class Gate { * @type {undefined|!function(!Float32Array, !CircuitDefinition, !int, !int) : *} */ this.customStatPostProcesser = undefined; + /** + * Returns a json form of the custom-stat data, used when exporting it. + * @type {undefined|!function(data: *) : *} + */ + this.processedStatsToJsonFunc = undefined; /** @type {!Array.} A list of size variants of this gate.*/ this.gateFamily = [this]; @@ -262,6 +267,7 @@ class Gate { g.customAfterOperation = this.customAfterOperation; g.customStatTexturesMaker = this.customStatTexturesMaker; g.customStatPostProcesser = this.customStatPostProcesser; + g.processedStatsToJsonFunc = this.processedStatsToJsonFunc; g.width = this.width; g.height = this.height; g.isSingleQubitDisplay = this.isSingleQubitDisplay; @@ -926,6 +932,16 @@ class GateBuilder { return this; } + /** + * Specifies how to convert custom stats data into json when exporting. + * @param {undefined|!function(data: *) : *} jsonFunc + * @returns {!GateBuilder} + */ + setProcessedStatsToJsonFunc(jsonFunc) { + this.gate.processedStatsToJsonFunc = jsonFunc; + return this; + } + /** * @param {*} tag * @returns {!GateBuilder} diff --git a/src/draw/MathPainter.js b/src/draw/MathPainter.js index 36910ef5..26521b6a 100644 --- a/src/draw/MathPainter.js +++ b/src/draw/MathPainter.js @@ -551,7 +551,7 @@ class MathPainter { let traceCouplingsWith = cellTraceFunc => painter.trace(trace => { for (let row = 0; row < numRows; row++) { for (let col = 0; col < numCols; col++) { - let k = (row + col * numRows) * 2; + let k = (row * numCols + col) * 2; cellTraceFunc( trace, buf[k], @@ -625,8 +625,8 @@ class MathPainter { `Probability of |${Util.bin(c, n)}⟩` : `Coupling of |${Util.bin(r, n)}⟩ to ⟨${Util.bin(c, n)}|`, (c, r, v) => c === r ? - (matrix.cell(r, c).real*100).toFixed(4) + "%" : - matrix.cell(r, c).toString(new Format(false, 0, 6, ", "))); + (matrix.cell(c, r).real*100).toFixed(4) + "%" : + matrix.cell(c, r).toString(new Format(false, 0, 6, ", "))); } } diff --git a/src/gates/AmplitudeDisplay.js b/src/gates/AmplitudeDisplay.js index a2faef91..048ca95c 100644 --- a/src/gates/AmplitudeDisplay.js +++ b/src/gates/AmplitudeDisplay.js @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {Complex} from "src/math/Complex.js" import {Config} from "src/Config.js" import {CircuitShaders} from "src/circuit/CircuitShaders.js" import {Gate} from "src/circuit/Gate.js" @@ -19,7 +20,7 @@ import {GatePainting} from "src/draw/GatePainting.js" import {GateShaders} from "src/circuit/GateShaders.js" import {Format} from "src/base/Format.js" import {MathPainter} from "src/draw/MathPainter.js" -import {Matrix} from "src/math/Matrix.js" +import {Matrix, complexVectorToReadableJson, realVectorToReadableJson} from "src/math/Matrix.js" import {Point} from "src/math/Point.js" import {Util} from "src/base/Util.js" import {WglArg} from "src/webgl/WglArg.js" @@ -417,6 +418,30 @@ function paintErrorIfPresent(args, isIncoherent) { } } +/** + * @param customStats + */ +function customStatsToJsonData(customStats) { + let {probabilities, superposition, phaseLockIndex} = customStats; + let result = { + coherence_measure: superposition !== undefined ? 1 : 0, + superposition_phase_locked_state_index: phaseLockIndex === undefined ? null : phaseLockIndex, + probabilities: null, + amplitudes: null, + + }; + if (probabilities !== undefined) { + let n = probabilities._width * probabilities._height; + result['probabilities'] = realVectorToReadableJson( + new Matrix(1, n, probabilities._buffer).getColumn(0).map(e => Math.pow(Complex.realPartOf(e), 2))); + } + if (superposition !== undefined) { + let n = superposition._width * superposition._height; + result['superposition'] = complexVectorToReadableJson(new Matrix(1, n, superposition._buffer).getColumn(0)); + } + return result; +} + let AmplitudeDisplayFamily = Gate.buildFamily(1, 16, (span, builder) => builder. setSerializedId("Amps" + span). setSymbol("Amps"). @@ -428,6 +453,7 @@ let AmplitudeDisplayFamily = Gate.buildFamily(1, 16, (span, builder) => builder. setStatTexturesMaker(ctx => amplitudeDisplayStatTextures(ctx.stateTrader.currentTexture, ctx.controls, ctx.row, span)). setStatPixelDataPostProcessor((val, def) => processOutputs(span, val, def)). + setProcessedStatsToJsonFunc(customStatsToJsonData). setDrawer(AMPLITUDE_DRAWER_FROM_CUSTOM_STATS)); export { diff --git a/src/gates/DensityMatrixDisplay.js b/src/gates/DensityMatrixDisplay.js index 13177556..9bba9214 100644 --- a/src/gates/DensityMatrixDisplay.js +++ b/src/gates/DensityMatrixDisplay.js @@ -144,7 +144,7 @@ function densityPixelsToMatrix(pixels, circuitDefinition, col, row) { } let isMeasuredMask = circuitDefinition.colIsMeasuredMask(col) >> row; - return decohereMeasuredBitsInDensityMatrix(new Matrix(d, d, pixels), isMeasuredMask); + return decohereMeasuredBitsInDensityMatrix(new Matrix(d, d, pixels), isMeasuredMask).transpose(); } /** @@ -186,6 +186,9 @@ function largeDensityMatrixDisplayMaker(span, builder) { setSerializedId("Density" + span). setWidth(span). setDrawer(DENSITY_MATRIX_DRAWER_FROM_CUSTOM_STATS). + setProcessedStatsToJsonFunc(data => { + return {density_matrix: data.toReadableJson()}; + }). setStatTexturesMaker(ctx => densityDisplayStatTexture( ctx.stateTrader.currentTexture, ctx.wireCount, ctx.controls, ctx.row, span)). setStatPixelDataPostProcessor(densityPixelsToMatrix); diff --git a/src/gates/ProbabilityDisplay.js b/src/gates/ProbabilityDisplay.js index 35b7875a..3c15c11e 100644 --- a/src/gates/ProbabilityDisplay.js +++ b/src/gates/ProbabilityDisplay.js @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import {Complex} from "src/math/Complex.js" import {Config} from "src/Config.js" import {Gate} from "src/circuit/Gate.js" import {GatePainting} from "src/draw/GatePainting.js" @@ -20,6 +21,7 @@ import {MathPainter} from "src/draw/MathPainter.js" import {Matrix} from "src/math/Matrix.js" import {Point} from "src/math/Point.js" import {Rect} from "src/math/Rect.js" +import {Seq} from "src/base/Seq.js" import {Shaders} from "src/webgl/Shaders.js" import {Util} from "src/base/Util.js" import {WglConfiguredShader} from "src/webgl/WglConfiguredShader.js" @@ -98,6 +100,20 @@ function probabilityPixelsToColumnVector(pixels, span) { return new Matrix(1, n, buf); } +/** + * @param {!Matrix} data + * @returns {!{probabilities: !float[]}} + */ +function probabilityDataToJson(data) { + return { + probabilities: Seq.range(data.height()).map(k => Complex.realPartOf(data.cell(0, k))).toArray() + }; +} + +/** + * @param {!GateDrawParams} args + * @private + */ function _paintMultiProbabilityDisplay_grid(args) { let {painter, rect: {x, y, w, h}} = args; let n = 1 << args.gate.height; @@ -258,6 +274,7 @@ function multiChanceGateMaker(span, builder) { setStatTexturesMaker(ctx => probabilityStatTexture(ctx.stateTrader.currentTexture, ctx.controlsTexture, ctx.row, span)). setStatPixelDataPostProcessor(pixels => probabilityPixelsToColumnVector(pixels, span)). + setProcessedStatsToJsonFunc(probabilityDataToJson). setDrawer(GatePainting.makeDisplayDrawer(paintMultiProbabilityDisplay)); } @@ -288,5 +305,6 @@ export { ProbabilityDisplayFamily, probabilityStatTexture, probabilityPixelsToColumnVector, - amplitudesToProbabilities + amplitudesToProbabilities, + probabilityDataToJson, }; diff --git a/src/gates/SampleDisplay.js b/src/gates/SampleDisplay.js index da2a86a5..8a961370 100644 --- a/src/gates/SampleDisplay.js +++ b/src/gates/SampleDisplay.js @@ -19,7 +19,11 @@ import {MathPainter} from "src/draw/MathPainter.js" import {Point} from "src/math/Point.js" import {Rect} from "src/math/Rect.js" import {Util} from "src/base/Util.js" -import {probabilityStatTexture, probabilityPixelsToColumnVector} from "src/gates/ProbabilityDisplay.js" +import { + probabilityStatTexture, + probabilityPixelsToColumnVector, + probabilityDataToJson +} from "src/gates/ProbabilityDisplay.js" /** * @param {!GateDrawParams} args @@ -106,6 +110,7 @@ let SampleDisplayFamily = Gate.buildFamily(1, 16, (span, builder) => builder. probabilityStatTexture(ctx.stateTrader.currentTexture, ctx.controlsTexture, ctx.row, span)). setStatPixelDataPostProcessor(e => probabilityPixelsToColumnVector(e, span)). promiseHasNoNetEffectOnStateVectorButStillRequiresDynamicRedraw(). + setProcessedStatsToJsonFunc(probabilityDataToJson). setDrawer(GatePainting.makeDisplayDrawer(paintSampleDisplay)). setExtraDisableReasonFinder(args => args.isNested ? "can't\nnest\ndisplays\n(sorry)" : undefined)); diff --git a/src/main.js b/src/main.js index bdb556d3..9fe9bbc5 100644 --- a/src/main.js +++ b/src/main.js @@ -19,6 +19,7 @@ hookErrorHandler(); import {doDetectIssues} from "src/issues.js" doDetectIssues(); +import {CircuitStats} from "src/circuit/CircuitStats.js" import {CooldownThrottle} from "src/base/CooldownThrottle.js" import {Config} from "src/Config.js" import {DisplayedInspector} from "src/ui/DisplayedInspector.js" @@ -79,6 +80,7 @@ const inspectorDiv = document.getElementById("inspectorDiv"); /** @type {ObservableValue.} */ const displayed = new ObservableValue( DisplayedInspector.empty(new Rect(0, 0, canvas.clientWidth, canvas.clientHeight))); +const mostRecentStats = new ObservableValue(CircuitStats.EMPTY); /** @type {!Revision} */ let revision = Revision.startingAt(displayed.get().snapshot()); @@ -132,6 +134,7 @@ const redrawNow = () => { let shown = syncArea(displayed.get()).previewDrop(); let stats = simulate(shown.displayedCircuit.circuitDefinition); + mostRecentStats.set(stats); let size = desiredCanvasSizeFor(shown); canvas.width = size.w; @@ -275,7 +278,7 @@ canvasDiv.addEventListener('mouseleave', () => { let obsIsAnyOverlayShowing = new ObservableSource(); initUrlCircuitSync(revision); -initExports(revision, obsIsAnyOverlayShowing.observable()); +initExports(revision, mostRecentStats, obsIsAnyOverlayShowing.observable()); initForge(revision, obsIsAnyOverlayShowing.observable()); initUndoRedo(revision, obsIsAnyOverlayShowing.observable()); initClear(revision, obsIsAnyOverlayShowing.observable()); diff --git a/src/math/Matrix.js b/src/math/Matrix.js index 2bcd2647..78cc4f0d 100644 --- a/src/math/Matrix.js +++ b/src/math/Matrix.js @@ -70,6 +70,13 @@ class Matrix { return this._buffer; } + /** + * Encodes the matrix into JSON that could easily be read by humans or processed by external programs. + * @returns {*} + */ + toReadableJson() { + return seq(this.rows()).map(complexVectorToReadableJson).toArray(); + } /** * @returns {!Array.>} */ @@ -1426,4 +1433,22 @@ Matrix.PAULI_Z = Matrix.square(1, 0, 0, -1); */ Matrix.HADAMARD = Matrix.square(1, 1, 1, -1).times(Math.sqrt(0.5)); -export {Matrix} +/** + * Encodes a complex vector into JSON that could easily be read by humans or processed by external programs. + * @param {!Iterable.} vector + * @returns {*} + */ +function complexVectorToReadableJson(vector) { + return seq(vector).map(e => {return {real: Complex.realPartOf(e), imag: Complex.imagPartOf(e)}; }).toArray(); +} + +/** + * Encodes a real vector into JSON that could easily be read by humans or processed by external programs. + * @param {!Iterable.} vector + * @returns {*} + */ +function realVectorToReadableJson(vector) { + return seq(vector).map(Complex.realPartOf).toArray(); +} + +export {Matrix, complexVectorToReadableJson, realVectorToReadableJson} diff --git a/src/ui/exports.js b/src/ui/exports.js index b0fbd63d..2685b515 100644 --- a/src/ui/exports.js +++ b/src/ui/exports.js @@ -14,6 +14,7 @@ import {Config} from "src/Config.js" import {ObservableValue} from "src/base/Obs.js" +import {Serializer} from "src/circuit/Serializer.js" import {selectAndCopyToClipboard} from "src/browser/Clipboard.js" import {fromJsonText_CircuitDefinition} from "src/circuit/Serializer.js" import {saveFile} from "src/browser/SaveFile.js" @@ -23,9 +24,10 @@ const obsExportsIsShowing = exportsIsVisible.observable().whenDifferent(); /** * @param {!Revision} revision + * @param {!ObservableValue.} mostRecentStats * @param {!Observable.} obsIsAnyOverlayShowing */ -function initExports(revision, obsIsAnyOverlayShowing) { +function initExports(revision, mostRecentStats, obsIsAnyOverlayShowing) { // Show/hide exports overlay. (() => { const exportButton = /** @type {!HTMLButtonElement} */ document.getElementById('export-button'); @@ -52,9 +54,14 @@ function initExports(revision, obsIsAnyOverlayShowing) { * @param {!HTMLButtonElement} button * @param {!HTMLElement} contentElement * @param {!HTMLElement} resultElement + * @param {undefined|!function(): !string} contentMaker */ - const setupButtonElementCopyToClipboard = (button, contentElement, resultElement) => + const setupButtonElementCopyToClipboard = (button, contentElement, resultElement, contentMaker=undefined) => button.addEventListener('click', () => { + if (contentMaker !== undefined) { + contentElement.innerText = contentMaker(); + } + //noinspection UnusedCatchParameterJS,EmptyCatchBlockJS try { selectAndCopyToClipboard(contentElement); @@ -100,6 +107,19 @@ function initExports(revision, obsIsAnyOverlayShowing) { }); })(); + // Export final output. + (() => { + const outputTextElement = /** @type {HTMLPreElement} */ document.getElementById('export-amplitudes-pre'); + const copyButton = /** @type {HTMLButtonElement} */ document.getElementById('export-amplitudes-button'); + const copyResultElement = /** @type {HTMLElement} */ document.getElementById('export-amplitudes-result'); + obsIsAnyOverlayShowing.subscribe(_ => { outputTextElement.innerText = '[not generated yet]'; }); + setupButtonElementCopyToClipboard( + copyButton, + outputTextElement, + copyResultElement, + () => JSON.stringify(mostRecentStats.get().toReadableJson(), null, ' ')); + })(); + // Export offline copy. (() => { const downloadButton = /** @type {HTMLButtonElement} */ document.getElementById('download-offline-copy-button'); diff --git a/test/TestUtil.js b/test/TestUtil.js index 00b3235e..898db329 100644 --- a/test/TestUtil.js +++ b/test/TestUtil.js @@ -90,6 +90,8 @@ function isApproximatelyEqualToHelper(subject, other, epsilon) { return true; } else if (subject instanceof Object && subject.toString() === "[object Object]") { return isApproximatelyEqualToHelperDestructured(subject, other, epsilon); + } else if (subject === other) { + return true; } else { fail('Expected ' + describe(subject) + ' to have an isApproximatelyEqualTo method'); return false; diff --git a/test/circuit/CircuitStats.test.js b/test/circuit/CircuitStats.test.js index 33c3435d..b3dac2b5 100644 --- a/test/circuit/CircuitStats.test.js +++ b/test/circuit/CircuitStats.test.js @@ -388,3 +388,46 @@ suite.testUsingWebGL("distillation", () => { assertThat(stats.survivalRate(Infinity)).isApproximatelyEqualTo(1, 0.001); } }); + +suite.testUsingWebGL("toReadableJson", () => { + let c = circuit( + ` + --%D + H@/- + `, + ['%', Gates.Displays.ProbabilityDisplayFamily], + ['D', Gates.Detectors.ZDetector] + ); + let stats = CircuitStats.fromCircuitAtTime(c, 0.5); + let json = stats.toReadableJson(); + assertThat(json).isApproximatelyEqualTo({ + circuit: Serializer.toJson(c), + output_amplitudes: [ + {real: Math.sqrt(0.5), imag: 0}, + {real: 0, imag: 0}, + {real: Math.sqrt(0.5), imag: 0}, + {real: 0, imag: 0}, + ], + time_parameter: 0.5, + chance_of_surviving_to_each_column: [1, 1, 1, 1], + computed_bloch_vectors_by_column_then_wire: [ + [null, null], + [null, {x: +1, y: 0, z: 0}], + [null, null], + [null, null], + [{x: 0, y: 0, z: +1}, {x: +1, y: 0, z: 0}], + ], + displays: [ + { + location: {wire: 0, column: 2}, + type: {serialized_id: "Chance2", name: "Probability Display"}, + data: {probabilities: [0.5, 0, 0.5, 0]} + }, + { + location: {wire: 0, column: 3}, + type: {serialized_id: "ZDetector", name: "Z Axis Detector"}, + data: false + } + ] + }) +}); From 6da666c6fca583c14943588a7873f357b2af594f Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 16:46:48 -0700 Subject: [PATCH 24/38] Allow dragging columns out of the circuit --- src/ui/DisplayedCircuit.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index f640b263..2c0a57e1 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -103,6 +103,8 @@ class DisplayedCircuit { } /** + * 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 */ @@ -661,6 +663,13 @@ class DisplayedCircuit { 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 && From 32efb93466db6c9dddc2c97fda94a83f3c2a00f9 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 17:02:46 -0700 Subject: [PATCH 25/38] Don't add touch blockers to circuit area when drawing custom circuit gates in toolbox --- src/draw/Painter.js | 14 +++++++++++++- src/ui/DisplayedToolbox.js | 2 ++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/draw/Painter.js b/src/draw/Painter.js index f70f93b6..dded626c 100644 --- a/src/draw/Painter.js +++ b/src/draw/Painter.js @@ -56,13 +56,25 @@ class Painter { * @type {!RestartableRng} */ this.rng = rng; + + this._ignoringTouchBlockers = 0; + } + + startIgnoringIncomingTouchBlockers() { + this._ignoringTouchBlockers += 1 + } + + stopIgnoringIncomingTouchBlockers() { + this._ignoringTouchBlockers -= 1 } /** * @param {!{rect: !Rect, cursor: undefined|!string}} blocker */ noteTouchBlocker(blocker) { - this.touchBlockers.push(blocker); + if (this._ignoringTouchBlockers === 0) { + this.touchBlockers.push(blocker); + } } /** diff --git a/src/ui/DisplayedToolbox.js b/src/ui/DisplayedToolbox.js index dce22dd7..a87ca07a 100644 --- a/src/ui/DisplayedToolbox.js +++ b/src/ui/DisplayedToolbox.js @@ -290,6 +290,7 @@ class DisplayedToolbox { */ static _paintGate(painter, gate, rect, isHighlighted, stats) { let drawer = gate.customDrawer || GatePainting.DEFAULT_DRAWER; + painter.startIgnoringIncomingTouchBlockers(); drawer(new GateDrawParams( painter, true, // inToolbox @@ -302,6 +303,7 @@ class DisplayedToolbox { undefined, // positionInCircuit [], // focusPoints undefined)); // customStatsForCircuitPos + painter.stopIgnoringIncomingTouchBlockers(); } /** From f2e4e8b1768781887914cdd5702b05a8473ac1a3 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 20:21:07 -0700 Subject: [PATCH 26/38] Add option to omit amplitudes from simulator export output --- html/export.partial.html | 6 ++++-- html/forge.partial.html | 2 +- src/circuit/CircuitStats.js | 10 +++++++--- src/ui/exports.js | 7 +++++-- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/html/export.partial.html b/html/export.partial.html index 95f1cafc..af7d65a9 100644 --- a/html/export.partial.html +++ b/html/export.partial.html @@ -23,9 +23,11 @@
Simulation Data JSON - Output amplitudes, detector results, display data, etc.
-   +   + +
-

+            

         
diff --git a/html/forge.partial.html b/html/forge.partial.html index 329865fa..6db612c3 100644 --- a/html/forge.partial.html +++ b/html/forge.partial.html @@ -31,7 +31,7 @@