diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 1413e942..526cc474 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -34,8 +34,16 @@ class Gate { this.width = 1; /** @type {!int} The number of wires the gate spans on a circuit. */ this.height = 1; - /** @type {undefined|!int} A custom value that gets serialized. Each gate may use it to determine behavior. */ + /** @type {undefined|!string|!number|!Array} A custom value that gets serialized. + * Each gate may use it to determine behavior. */ this.param = undefined; + /** + * Updates gate properties based on a new parameter value. + * Called by `Gate.withParam` before returning its result. + * @type {!function(gate: !Gate): undefined} + * @private + */ + this._withParamRecomputeFunc = g => {}; /** @type {undefined|!function(!GateDrawParams) : void} Draws the gate. A default is used when undefined. */ this.customDrawer = undefined; @@ -295,6 +303,7 @@ class Gate { g._requiredContextKeys = this._requiredContextKeys; g._knownMatrixFunc = this._knownMatrixFunc; g._stableDuration = this._stableDuration; + g._withParamRecomputeFunc = this._withParamRecomputeFunc; g._hasNoEffect = this._hasNoEffect; g._effectPermutesStates = this._effectPermutesStates; g._effectCreatesSuperpositions = this._effectCreatesSuperpositions; @@ -312,13 +321,14 @@ class Gate { } /** - * Sets an arbitrary number, saved and restored with the circuit, that the gate's custom functions may use. - * @param {undefined|!int} value + * Sets an arbitrary json value, saved and restored with the circuit, that the gate's custom functions may use. + * @param {undefined|!string|!number|!Array} value * @returns {!Gate} */ withParam(value) { let g = this._copy(); g.param = value; + g._withParamRecomputeFunc(g); return g; } @@ -399,7 +409,7 @@ class Gate { */ knownMatrixAt(time) { return this._knownMatrix !== undefined ? this._knownMatrix : - this._knownMatrixFunc !== undefined ? this._knownMatrixFunc(time) : + this._knownMatrixFunc !== undefined ? this._knownMatrixFunc(time, this.param) : undefined; } @@ -740,7 +750,7 @@ class GateBuilder { } /** - * @param {!function(time : !number) : !Matrix} timeToMatrixFunc + * @param {!function(time : !number, gateParam: *) : !Matrix} timeToMatrixFunc * @returns {!GateBuilder} */ setEffectToTimeVaryingMatrix(timeToMatrixFunc) { @@ -750,6 +760,16 @@ class GateBuilder { return this; } + /** + * A function called by `Gate.withParam` before returning its result. + * @param {!function(gate: !Gate): undefined} withParamRecomputeFunc + * @returns {!GateBuilder} + */ + setWithParamPropertyRecomputeFunc(withParamRecomputeFunc) { + this.gate._withParamRecomputeFunc = withParamRecomputeFunc; + return this; + } + /** * Sets a custom circuit-update function to run when simulating this gate. * @param {undefined|!function(!CircuitEvalContext)} circuitUpdateFunc diff --git a/src/circuit/Serializer.js b/src/circuit/Serializer.js index 18ca83fb..e9d0e3f5 100644 --- a/src/circuit/Serializer.js +++ b/src/circuit/Serializer.js @@ -211,9 +211,6 @@ let fromJson_Gate_props = json => { let matrix = json["matrix"]; let circuit = json["circuit"]; let param = json["arg"]; - if (param !== undefined && (!Number.isInteger(param) || param < 0 || param > 1<<16)) { - throw new DetailedError("Gate arg not int in [0, 2^16].", {json}); - } let symbol = json.name !== undefined ? json.name : id.startsWith('~') ? '' : id; diff --git a/src/draw/GateDrawParams.js b/src/draw/GateDrawParams.js index e5e87dc2..dc2da6e2 100644 --- a/src/draw/GateDrawParams.js +++ b/src/draw/GateDrawParams.js @@ -18,6 +18,7 @@ class GateDrawParams { /** * @param {!Painter} painter + * @param {!Hand} hand * @param {!boolean} isInToolbox * @param {!boolean} isHighlighted * @param {!boolean} isResizeShowing @@ -30,6 +31,7 @@ class GateDrawParams { * @param {undefined|*} customStatsForCircuitPos */ constructor(painter, + hand, isInToolbox, isHighlighted, isResizeShowing, @@ -42,6 +44,8 @@ class GateDrawParams { customStatsForCircuitPos) { /** @type {!Painter} */ this.painter = painter; + /** @type {!Hand} */ + this.hand = hand; /** @type {!boolean} */ this.isInToolbox = isInToolbox; /** @type {!boolean} */ diff --git a/src/draw/GatePainting.js b/src/draw/GatePainting.js index 91bd5b82..9f1d07cd 100644 --- a/src/draw/GatePainting.js +++ b/src/draw/GatePainting.js @@ -126,8 +126,9 @@ GatePainting.paintResizeTab = args => { /** * @param {!GateDrawParams} args * @param {undefined|!string=undefined} symbolOverride + * @param {!boolean=} allowExponent */ -GatePainting.paintGateSymbol = (args, symbolOverride=undefined) => { +GatePainting.paintGateSymbol = (args, symbolOverride=undefined, allowExponent=true) => { let painter = args.painter; let rect = args.rect.paddedBy(-2); if (symbolOverride === undefined) { @@ -136,7 +137,8 @@ GatePainting.paintGateSymbol = (args, symbolOverride=undefined) => { let {symbol, offsetY} = _paintSymbolHandleLines(args.painter, symbolOverride, rect); painter.ctx.font = GATE_SYMBOL_FONT; // So that measure-text calls return the right stuff. - let parts = symbol.split("^"); + let splitIndex = allowExponent ? symbol.indexOf('^') : -1; + let parts = splitIndex === -1 ? [symbol] : [symbol.substr(0, splitIndex), symbol.substr(splitIndex + 1)]; if (parts.length !== 2 || parts[0] === "" || parts[1] === "") { painter.print( symbol, @@ -369,10 +371,20 @@ GatePainting.makeCycleDrawer = (xScale=1, yScale=1, tScale=1, zeroAngle=0) => ar if (args.isInToolbox && !args.isHighlighted) { return; } - let τ = 2 * Math.PI; - let t = Util.properMod(-args.stats.time * τ * tScale, τ); + GatePainting.paintCycleState(args, args.stats.time * 2 * Math.PI * tScale, xScale, yScale, zeroAngle); +}; + +/** + * @param {!GateDrawParams} args + * @param {!number} angle + * @param {!number} xScale + * @param {!number} yScale + * @param {!number} zeroAngle + */ +GatePainting.paintCycleState = (args, angle, xScale=1, yScale=1, zeroAngle=0) => { + let t = Util.properMod(-angle, 2 * Math.PI); let c = args.rect.center(); - let r = 0.4 * args.rect.w; + let r = 16; args.painter.ctx.save(); @@ -386,7 +398,7 @@ GatePainting.makeCycleDrawer = (xScale=1, yScale=1, tScale=1, zeroAngle=0) => ar args.painter.ctx.beginPath(); args.painter.ctx.moveTo(0, 0); args.painter.ctx.lineTo(0, r); - args.painter.ctx.arc(0, 0, r, τ/4, τ/4 + t, true); + args.painter.ctx.arc(0, 0, r, Math.PI/2, Math.PI/2 + t, true); args.painter.ctx.lineTo(0, 0); args.painter.ctx.closePath(); args.painter.ctx.stroke(); @@ -419,13 +431,18 @@ function _wireY(args, offset) { * @param {!Rect} wholeRect * @returns {!Rect} */ -GatePainting.gateButtonRect = wholeRect => wholeRect.bottomHalf().skipTop(6).paddedBy(-7); +GatePainting.gateButtonRect = wholeRect => { + if (wholeRect.h > 50) { + return wholeRect.bottomHalf().skipTop(6).paddedBy(-7); + } + return wholeRect.bottomHalf().paddedBy(+2); +}; /** * @param {!GateDrawParams} args */ GatePainting.paintGateButton = args => { - if (!args.isHighlighted || args.isInToolbox) { + if (!args.isHighlighted || args.isInToolbox || args.hand.isHoldingSomething()) { return; } @@ -443,7 +460,7 @@ GatePainting.paintGateButton = args => { buttonRect.w, buttonRect.h); args.painter.strokeRect(buttonRect, 'black'); -} +}; /** diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 27544b78..190b4b10 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -60,8 +60,6 @@ import {VariousYGates} from "src/gates/VariousYGates.js" import {VariousZGates} from "src/gates/VariousZGates.js" import {XorGates} from "src/gates/XorGates.js" import {ZeroGate} from "src/gates/Joke_ZeroGate.js" -import {MysteryGateMaker} from "src/gates/Joke_MysteryGate.js" - import {seq} from "src/base/Seq.js" let Gates = {}; @@ -229,14 +227,6 @@ Gates.TopToolboxGroups = [ VariousXGates.X4, VariousXGates.X4i, ] }, - { - hint: "Sixteenths", - gates: [ - VariousZGates.Z8, VariousZGates.Z8i, - VariousYGates.Y8, VariousYGates.Y8i, - VariousXGates.X8, VariousXGates.X8i, - ] - }, { hint: "Spinning", gates: [ @@ -245,6 +235,14 @@ Gates.TopToolboxGroups = [ PoweringGates.XForward, PoweringGates.XBackward, ] }, + { + hint: "Formulaic", + gates: [ + ParametrizedRotationGates.FormulaicRotationZ, ParametrizedRotationGates.FormulaicRotationRz, + ParametrizedRotationGates.FormulaicRotationY, ParametrizedRotationGates.FormulaicRotationRy, + ParametrizedRotationGates.FormulaicRotationX, ParametrizedRotationGates.FormulaicRotationRx, + ] + }, { hint: "Parametrized", gates: [ diff --git a/src/gates/ExponentiatingGates.js b/src/gates/ExponentiatingGates.js index 6daf7c90..12b5f425 100644 --- a/src/gates/ExponentiatingGates.js +++ b/src/gates/ExponentiatingGates.js @@ -101,4 +101,4 @@ ExponentiatingGates.all = [ ExponentiatingGates.ZForward ]; -export {ExponentiatingGates} +export {ExponentiatingGates, XExp, YExp, ZExp} diff --git a/src/gates/InputGates.js b/src/gates/InputGates.js index d73ad960..7a097d20 100644 --- a/src/gates/InputGates.js +++ b/src/gates/InputGates.js @@ -112,6 +112,13 @@ let makeSetInputGate = key => new GateBuilder(). return oldGate.withParam(val); }). + setExtraDisableReasonFinder(args => { + let p = args.gate.param; + if (!Number.isInteger(p) || p < 0 || p > 1<<16) { + return 'bad\nvalue'; + } + return undefined; + }). gate. withParam(2); diff --git a/src/gates/ParametrizedRotationGates.js b/src/gates/ParametrizedRotationGates.js index 42af425d..9c2eddbb 100644 --- a/src/gates/ParametrizedRotationGates.js +++ b/src/gates/ParametrizedRotationGates.js @@ -14,12 +14,44 @@ import {GateBuilder} from "src/circuit/Gate.js" import {GatePainting} from "src/draw/GatePainting.js" +import {Complex, PARSE_COMPLEX_TOKEN_MAP_RAD} from "src/math/Complex.js" +import {Matrix} from "src/math/Matrix.js" import {ketArgs, ketShader, ketShaderPhase, ketInputGateShaderCode} from "src/circuit/KetShaderUtil.js" import {WglArg} from "src/webgl/WglArg.js" import {Util} from "src/base/Util.js"; +import {parseFormula} from "src/math/FormulaParser.js"; +import {XExp, YExp, ZExp} from "src/gates/ExponentiatingGates.js"; +import {Config} from "src/Config.js"; let ParametrizedRotationGates = {}; +/** + * @param {!string} pattern + * @param {!int} xyz + * @param {!number} tScale + * @returns {!function(args: !GateDrawParams)} + */ +function configurableRotationDrawer(pattern, xyz, tScale) { + let xScale = [1, 0.5, -1][xyz]; + let yScale = [1, 1, -0.5][xyz]; + return args => { + GatePainting.paintBackground(args, Config.TIME_DEPENDENT_HIGHLIGHT_COLOR); + GatePainting.paintOutline(args); + let text = pattern; + if (!args.isInToolbox) { + text = text.split('f(t)').join(args.gate.param); + } + GatePainting.paintGateSymbol(args, text, pattern.indexOf('^') !== -1); + GatePainting.paintGateButton(args); + + let isStable = args.gate.stableDuration() === Infinity; + if ((!args.isInToolbox || args.isHighlighted) && !isStable) { + let rads = tScale * parseTimeFormula(args.gate.param, args.stats.time*2-1, false) || 0; + GatePainting.paintCycleState(args, rads, xScale, yScale); + } + }; +} + /** * @param {!GateDrawParams} args */ @@ -167,6 +199,183 @@ ParametrizedRotationGates.ZToMinusA = new GateBuilder(). promiseEffectOnlyPhases(). gate; +/** + * @param {!string} formula + * @param {undefined|!number} time + * @param {!boolean} warn + * @returns {undefined|!number} + */ +function parseTimeFormula(formula, time, warn) { + let tokenMap = new Map([...PARSE_COMPLEX_TOKEN_MAP_RAD.entries()]); + if (time !== undefined) { + tokenMap.set('t', time); + } + try { + let angle = Complex.from(parseFormula(formula, tokenMap)); + if (Math.abs(angle.imag) > 0.0001) { + throw new Error(`Non-real angle: ${formula} = ${angle}`); + } + return angle.real; + } catch (ex) { + if (warn) { + console.warn(ex); + } + return undefined; + } +} + +/** + * @param {!GateCheckArgs} args + * @returns {undefined|!string} + */ +function badFormulaDetector(args) { + if (typeof args.gate.param === 'number') { + return args.gate.param; + } else if (typeof args.gate.param === 'string') { + for (let t of [0.01, 0.63, 0.98]) { + if (parseTimeFormula(args.gate.param, t, false) === undefined) { + return 'bad\nformula'; + } + } + return undefined; + } else { + return 'bad\nvalue'; + } +} + +/** + * @param {!Gate} gate + */ +function updateUsingFormula(gate) { + let stable = parseTimeFormula(gate.param, undefined, false) !== undefined; + gate._stableDuration = stable ? Infinity : 0; + + if (typeof gate.param === 'string') { + gate.width = Math.ceil((gate.param.length+1)/5); + gate.alternate = gate._copy(); + gate.alternate.alternate = gate; + if (gate.param.startsWith('-(') && gate.param.endsWith(')')) { + gate.alternate.param = gate.param.substring(2, gate.param.length - 1); + } else { + gate.alternate.param = '-(' + gate.param + ')'; + } + } else { + gate.width = 1; + gate.alternate = gate; + } +} + +/** + * @param {!string} quantityName + * @returns {!function(gate: !Gate): !Gate} + */ +function angleClicker(quantityName) { + return oldGate => { + let txt = prompt( + `Enter a formula to use for the ${quantityName}.\n` + + "\n" + + "The formula can depend on the time variable t.\n" + + "Time t starts at -1, grows to +1 over time, then jumps back to -1.\n" + + "Invalid results will default to 0.\n" + + "\n" + + "Available constants: e, pi\n" + + "Available functions: cos, sin, acos, asin, tan, atan, ln, sqrt, exp\n" + + "Available operators: + * / - ^", + '' + oldGate.param); + if (txt === null || txt.trim() === '') { + return oldGate; + } + return oldGate.withParam(txt); + }; +} + +ParametrizedRotationGates.FormulaicRotationX = new GateBuilder(). + setSerializedIdAndSymbol("X^ft"). + setTitle("Formula X Rotation"). + setBlurb("Rotates around X by an amount determined by a formula."). + setDrawer(configurableRotationDrawer('X^f(t)', 0, Math.PI)). + setWidth(2). + setExtraDisableReasonFinder(badFormulaDetector). + setOnClickGateFunc(angleClicker("X gate's exponent")). + setEffectToTimeVaryingMatrix((t, formula) => { + let exponent = parseTimeFormula(formula, t*2-1, true) || 0; + return Matrix.fromPauliRotation(exponent/2, 0, 0); + }). + setWithParamPropertyRecomputeFunc(updateUsingFormula). + promiseEffectIsUnitary(). + gate.withParam('sin(pi t)'); + +ParametrizedRotationGates.FormulaicRotationY = new GateBuilder(). + setSerializedIdAndSymbol("Y^ft"). + setTitle("Formula Y Rotation"). + setBlurb("Rotates around Y by an amount determined by a formula."). + setDrawer(configurableRotationDrawer('Y^f(t)', 1, Math.PI)). + setWidth(2). + setExtraDisableReasonFinder(badFormulaDetector). + setOnClickGateFunc(angleClicker("Y gate's exponent")). + setEffectToTimeVaryingMatrix((t, formula) => { + let exponent = parseTimeFormula(formula, t*2-1, true) || 0; + return Matrix.fromPauliRotation(0, exponent/2, 0); + }). + setWithParamPropertyRecomputeFunc(updateUsingFormula). + promiseEffectIsUnitary(). + gate.withParam('sin(pi t)'); + +ParametrizedRotationGates.FormulaicRotationZ = new GateBuilder(). + setSerializedIdAndSymbol("Z^ft"). + setTitle("Formula Z Rotation"). + setBlurb("Rotates around Z by an amount determined by a formula."). + setDrawer(configurableRotationDrawer('Z^f(t)', 2, Math.PI)). + setWidth(2). + setExtraDisableReasonFinder(badFormulaDetector). + setOnClickGateFunc(angleClicker("Z gate's exponent")). + setEffectToTimeVaryingMatrix((t, formula) => { + let exponent = parseTimeFormula(formula, t*2-1, true) || 0; + return Matrix.fromPauliRotation(0, 0, exponent/2); + }). + setWithParamPropertyRecomputeFunc(updateUsingFormula). + promiseEffectOnlyPhases(). + gate.withParam('sin(pi t)'); + +ParametrizedRotationGates.FormulaicRotationRx = new GateBuilder(). + setSerializedIdAndSymbol("Rxft"). + setTitle("Formula Rx Gate"). + setBlurb("Rotates around X by an angle in radians determined by a formula."). + setDrawer(configurableRotationDrawer('Rx(f(t))', 0, 1)). + setWidth(2). + setExtraDisableReasonFinder(badFormulaDetector). + setOnClickGateFunc(angleClicker("Rx gate's angle in radians")). + setEffectToTimeVaryingMatrix((t, formula) => XExp((parseTimeFormula(formula, t*2-1, true) || 0) / Math.PI / 4)). + setWithParamPropertyRecomputeFunc(updateUsingFormula). + promiseEffectIsUnitary(). + gate.withParam('pi t^2'); + +ParametrizedRotationGates.FormulaicRotationRy = new GateBuilder(). + setSerializedIdAndSymbol("Ryft"). + setTitle("Formula Ry Gate"). + setBlurb("Rotates around Y by an angle in radians determined by a formula."). + setDrawer(configurableRotationDrawer('Ry(f(t))', 1, 1)). + setWidth(2). + setExtraDisableReasonFinder(badFormulaDetector). + setOnClickGateFunc(angleClicker("Ry gate's angle in radians")). + setEffectToTimeVaryingMatrix((t, formula) => YExp((parseTimeFormula(formula, t*2-1, true) || 0) / Math.PI / 4)). + setWithParamPropertyRecomputeFunc(updateUsingFormula). + promiseEffectIsUnitary(). + gate.withParam('pi t^2'); + +ParametrizedRotationGates.FormulaicRotationRz = new GateBuilder(). + setSerializedIdAndSymbol("Rzft"). + setTitle("Formula Rz Gate"). + setBlurb("Rotates around Z by an angle in radians determined by a formula."). + setDrawer(configurableRotationDrawer('Rz(f(t))', 2, 1)). + setWidth(2). + setExtraDisableReasonFinder(badFormulaDetector). + setOnClickGateFunc(angleClicker("Rz gate's angle in radians")). + setEffectToTimeVaryingMatrix((t, formula) => ZExp((parseTimeFormula(formula, t*2-1, true) || 0) / Math.PI / 4)). + setWithParamPropertyRecomputeFunc(updateUsingFormula). + promiseEffectOnlyPhases(). + gate.withParam('pi t^2'); + ParametrizedRotationGates.all =[ ParametrizedRotationGates.XToA, ParametrizedRotationGates.XToMinusA, @@ -174,6 +383,12 @@ ParametrizedRotationGates.all =[ ParametrizedRotationGates.YToMinusA, ParametrizedRotationGates.ZToA, ParametrizedRotationGates.ZToMinusA, + ParametrizedRotationGates.FormulaicRotationX, + ParametrizedRotationGates.FormulaicRotationY, + ParametrizedRotationGates.FormulaicRotationZ, + ParametrizedRotationGates.FormulaicRotationRx, + ParametrizedRotationGates.FormulaicRotationRy, + ParametrizedRotationGates.FormulaicRotationRz, ]; export {ParametrizedRotationGates} diff --git a/src/main.js b/src/main.js index ee1e6955..6fd93d86 100644 --- a/src/main.js +++ b/src/main.js @@ -43,6 +43,8 @@ import {initTitleSync} from "src/ui/title.js" import {simulate} from "src/ui/sim.js" import {GatePainting} from "src/draw/GatePainting.js" import {GATE_CIRCUIT_DRAWER} from "src/ui/DisplayedCircuit.js" +import {GateColumn} from "src/circuit/GateColumn.js"; +import {Point} from "src/math/Point.js"; initSerializer( GatePainting.LABEL_DRAWER, GatePainting.MATRIX_DRAWER, @@ -133,6 +135,9 @@ const redrawNow = () => { } let shown = syncArea(displayed.get()).previewDrop(); + if (displayed.get().hand.isHoldingSomething() && !shown.hand.isHoldingSomething()) { + shown = shown.withHand(shown.hand.withHeldGateColumn(new GateColumn([]), new Point(0, 0))) + } let stats = simulate(shown.displayedCircuit.circuitDefinition); mostRecentStats.set(stats); @@ -168,7 +173,7 @@ canvasDiv.addEventListener('click', ev => { } let clicked = syncArea(curInspector.withHand(curInspector.hand.withPos(pt))).tryClick(); if (clicked !== undefined) { - revision.commit(clicked.snapshot()); + revision.commit(clicked.afterTidyingUp().snapshot()); } }); @@ -182,7 +187,8 @@ watchDrags(canvasDiv, let oldInspector = displayed.get(); let newHand = oldInspector.hand.withPos(pt); let newInspector = syncArea(oldInspector.withHand(newHand)); - clickDownGateButtonKey = ev.ctrlKey ? undefined : newInspector.tryGetHandOverButtonKey(); + clickDownGateButtonKey = ( + ev.ctrlKey || ev.shiftKey || ev.altKey ? undefined : newInspector.tryGetHandOverButtonKey()); if (clickDownGateButtonKey !== undefined) { displayed.set(newInspector); return; diff --git a/src/math/Complex.js b/src/math/Complex.js index 3015b404..8a785e8a 100644 --- a/src/math/Complex.js +++ b/src/math/Complex.js @@ -17,7 +17,9 @@ import {Format, UNICODE_FRACTIONS} from "src/base/Format.js" import {Util} from "src/base/Util.js" import {parseFormula} from "src/math/FormulaParser.js" -const PARSE_COMPLEX_TOKEN_MAP = new Map(); +const PARSE_COMPLEX_TOKEN_MAP_ALL = new Map(); +const PARSE_COMPLEX_TOKEN_MAP_RAD = new Map(); +const PARSE_COMPLEX_TOKEN_MAP_DEG = new Map(); /** * Represents a complex number like `a + b i`, where `a` and `b` are real values and `i` is the square root of -1. @@ -184,7 +186,7 @@ class Complex { * @returns {!Complex} */ static parse(text) { - return Complex.from(parseFormula(text, PARSE_COMPLEX_TOKEN_MAP)); + return Complex.from(parseFormula(text, PARSE_COMPLEX_TOKEN_MAP_DEG)); } /** @@ -312,6 +314,29 @@ class Complex { return Complex.polar(Math.exp(this.real), this.imag); } + /** + * @returns {!Complex} + */ + cos() { + let z = this.times(Complex.I); + return z.exp().plus(z.neg().exp()).times(0.5); + } + + /** + * @returns {!Complex} + */ + sin() { + let z = this.times(Complex.I); + return z.exp().minus(z.neg().exp()).dividedBy(new Complex(0, 2)); + } + + /** + * @returns {!Complex} + */ + tan() { + return this.sin().dividedBy(this.cos()); + } + /** * Returns the natural logarithm of the receiving complex value. * @returns {!Complex} @@ -381,71 +406,105 @@ Complex.ONE = new Complex(1, 0); */ Complex.I = new Complex(0, 1); -PARSE_COMPLEX_TOKEN_MAP.set("i", Complex.I); -PARSE_COMPLEX_TOKEN_MAP.set("e", Complex.from(Math.E)); -PARSE_COMPLEX_TOKEN_MAP.set("pi", Complex.from(Math.PI)); -PARSE_COMPLEX_TOKEN_MAP.set("(", "("); -PARSE_COMPLEX_TOKEN_MAP.set(")", ")"); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("i", Complex.I); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("e", Complex.from(Math.E)); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("pi", Complex.from(Math.PI)); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("(", "("); +PARSE_COMPLEX_TOKEN_MAP_ALL.set(")", ")"); for (let {character, value} of UNICODE_FRACTIONS) { //noinspection JSUnusedAssignment - PARSE_COMPLEX_TOKEN_MAP.set(character, value); + PARSE_COMPLEX_TOKEN_MAP_ALL.set(character, value); } -PARSE_COMPLEX_TOKEN_MAP.set("sqrt", { +PARSE_COMPLEX_TOKEN_MAP_ALL.set("sqrt", { unary_action: e => Complex.from(e).raisedTo(0.5), priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("exp", { +PARSE_COMPLEX_TOKEN_MAP_ALL.set("exp", { unary_action: e => Complex.from(e).exp(), priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("ln", { +PARSE_COMPLEX_TOKEN_MAP_ALL.set("ln", { unary_action: e => Complex.from(e).ln(), priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("cos", { +PARSE_COMPLEX_TOKEN_MAP_ALL.set("^", { + binary_action: (a, b) => Complex.from(a).raisedTo(b), + priority: 3}); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("*", { + binary_action: (a, b) => Complex.from(a).times(b), + priority: 2}); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("/", { + binary_action: (a, b) => Complex.from(a).dividedBy(b), + priority: 2}); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("-", { + unary_action: e => Complex.from(e).neg(), + binary_action: (a, b) => Complex.from(a).minus(b), + priority: 1}); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("+", { + unary_action: e => e, + binary_action: (a, b) => Complex.from(a).plus(b), + priority: 1}); +PARSE_COMPLEX_TOKEN_MAP_ALL.set("√", PARSE_COMPLEX_TOKEN_MAP_ALL.get("sqrt")); + +PARSE_COMPLEX_TOKEN_MAP_DEG.set("cos", { + unary_action: e => new Complex(Math.PI/180, 0).times(e).cos(), + priority: 4}); +PARSE_COMPLEX_TOKEN_MAP_DEG.set("sin", { + unary_action: e => new Complex(Math.PI/180, 0).times(e).sin(), + priority: 4}); +PARSE_COMPLEX_TOKEN_MAP_DEG.set("asin", { unary_action: e => { - let z = Complex.from(e).times(new Complex(0, Math.PI/180)); - return z.exp().plus(z.neg().exp()).times(0.5); + if (Complex.imagPartOf(e) !== 0) { + throw new DetailedError("asin input out of range", {e}); + } + return Complex.from(Math.asin(Complex.realPartOf(e))*180/Math.PI); }, priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("sin", { +PARSE_COMPLEX_TOKEN_MAP_DEG.set("acos", { unary_action: e => { - let z = Complex.from(e).times(new Complex(0, Math.PI/180)); - return z.exp().minus(z.neg().exp()).dividedBy(new Complex(0, 2)); + if (Complex.imagPartOf(e) !== 0) { + throw new DetailedError("acos input out of range", {e}); + } + return Complex.from(Math.acos(Complex.realPartOf(e))*180/Math.PI); }, priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("asin", { +PARSE_COMPLEX_TOKEN_MAP_DEG.set("arccos", PARSE_COMPLEX_TOKEN_MAP_DEG.get("acos")); +PARSE_COMPLEX_TOKEN_MAP_DEG.set("arcsin", PARSE_COMPLEX_TOKEN_MAP_DEG.get("asin")); + +PARSE_COMPLEX_TOKEN_MAP_RAD.set("cos", { + unary_action: e => Complex.from(e).cos(), + priority: 4}); +PARSE_COMPLEX_TOKEN_MAP_RAD.set("sin", { + unary_action: e => Complex.from(e).sin(), + priority: 4}); +PARSE_COMPLEX_TOKEN_MAP_RAD.set("tan", { + unary_action: e => Complex.from(e).tan(), + priority: 4}); +PARSE_COMPLEX_TOKEN_MAP_RAD.set("asin", { unary_action: e => { if (Complex.imagPartOf(e) !== 0) { throw new DetailedError("asin input out of range", {e}); } - return Complex.from(Math.asin(Complex.realPartOf(e))*180/Math.PI); + return Complex.from(Math.asin(Complex.realPartOf(e))); }, priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("acos", { +PARSE_COMPLEX_TOKEN_MAP_RAD.set("acos", { unary_action: e => { if (Complex.imagPartOf(e) !== 0) { throw new DetailedError("acos input out of range", {e}); } - return Complex.from(Math.acos(Complex.realPartOf(e))*180/Math.PI); + return Complex.from(Math.acos(Complex.realPartOf(e))); }, priority: 4}); -PARSE_COMPLEX_TOKEN_MAP.set("^", { - binary_action: (a, b) => Complex.from(a).raisedTo(b), - priority: 3}); -PARSE_COMPLEX_TOKEN_MAP.set("*", { - binary_action: (a, b) => Complex.from(a).times(b), - priority: 2}); -PARSE_COMPLEX_TOKEN_MAP.set("/", { - binary_action: (a, b) => Complex.from(a).dividedBy(b), - priority: 2}); -PARSE_COMPLEX_TOKEN_MAP.set("-", { - unary_action: e => Complex.from(e).neg(), - binary_action: (a, b) => Complex.from(a).minus(b), - priority: 1}); -PARSE_COMPLEX_TOKEN_MAP.set("+", { - unary_action: e => e, - binary_action: (a, b) => Complex.from(a).plus(b), - priority: 1}); -PARSE_COMPLEX_TOKEN_MAP.set("√", PARSE_COMPLEX_TOKEN_MAP.get("sqrt")); -PARSE_COMPLEX_TOKEN_MAP.set("arccos", PARSE_COMPLEX_TOKEN_MAP.get("acos")); -PARSE_COMPLEX_TOKEN_MAP.set("arcsin", PARSE_COMPLEX_TOKEN_MAP.get("asin")); +PARSE_COMPLEX_TOKEN_MAP_RAD.set("atan", { + unary_action: e => { + if (Complex.imagPartOf(e) !== 0) { + throw new DetailedError("atan input out of range", {e}); + } + return Complex.from(Math.atan(Complex.realPartOf(e))); + }, + priority: 4}); + +for (let [k, v] of PARSE_COMPLEX_TOKEN_MAP_ALL.entries()) { + PARSE_COMPLEX_TOKEN_MAP_DEG.set(k, v); + PARSE_COMPLEX_TOKEN_MAP_RAD.set(k, v); +} -export {Complex} +export {Complex, PARSE_COMPLEX_TOKEN_MAP_DEG, PARSE_COMPLEX_TOKEN_MAP_RAD} diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index c438f8bd..d643045a 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -533,6 +533,7 @@ class DisplayedCircuit { } drawer(new GateDrawParams( painter, + hand, false, isHighlighted && !isResizeHighlighted, isResizeShowing, diff --git a/src/ui/DisplayedInspector.js b/src/ui/DisplayedInspector.js index 485f7bbc..b6449b4c 100644 --- a/src/ui/DisplayedInspector.js +++ b/src/ui/DisplayedInspector.js @@ -121,7 +121,19 @@ class DisplayedInspector { Config.GATE_RADIUS*2 + Config.WIRE_SPACING*(gate.width-1), Config.GATE_RADIUS*2 + Config.WIRE_SPACING*(gate.height-1)); let drawer = gate.customDrawer || GatePainting.DEFAULT_DRAWER; - drawer(new GateDrawParams(painter, false, true, true, false, rect, gate, stats, undefined, [], undefined)); + drawer(new GateDrawParams( + painter, + this.hand, + false, + true, + true, + false, + rect, + gate, + stats, + undefined, + [], + undefined)); } /** diff --git a/src/ui/DisplayedToolbox.js b/src/ui/DisplayedToolbox.js index a87ca07a..699a85ed 100644 --- a/src/ui/DisplayedToolbox.js +++ b/src/ui/DisplayedToolbox.js @@ -211,7 +211,7 @@ class DisplayedToolbox { _paintStandardContents(painter) { // Gates. for (let groupIndex = 0; groupIndex < this.toolboxGroups.length; groupIndex++) { - this._paintGatesInGroup(painter, groupIndex); + this._paintGatesInGroup(painter, Hand.EMPTY, groupIndex); } // Title of toolbox. @@ -233,7 +233,7 @@ class DisplayedToolbox { for (let groupIndex = 0; groupIndex < this.toolboxGroups.length; groupIndex++) { // Custom gates. if (groupIndex >= this._originalGroups.length) { - this._paintGatesInGroup(painter, groupIndex); + this._paintGatesInGroup(painter, hand, groupIndex); } // Keep scroll blockers positioned over gates. @@ -253,10 +253,11 @@ class DisplayedToolbox { /** * @param {!Painter} painter + * @param {!Hand} hand * @param {!int} groupIndex * @private */ - _paintGatesInGroup(painter, groupIndex) { + _paintGatesInGroup(painter, hand, groupIndex) { let group = this.toolboxGroups[groupIndex]; let r = this.groupLabelRect(groupIndex); painter.print( @@ -276,23 +277,25 @@ class DisplayedToolbox { continue; } let rect = this.gateDrawRect(groupIndex, gateIndex); - DisplayedToolbox._paintGate(painter, gate, rect, false, CircuitStats.EMPTY); + DisplayedToolbox._paintGate(painter, hand, gate, rect, false, CircuitStats.EMPTY); } } /** * @param {!Painter} painter + * @param {!Hand} hand * @param {!Gate} gate * @param {!Rect} rect * @param {!boolean} isHighlighted * @param {!CircuitStats} stats * @private */ - static _paintGate(painter, gate, rect, isHighlighted, stats) { + static _paintGate(painter, hand, gate, rect, isHighlighted, stats) { let drawer = gate.customDrawer || GatePainting.DEFAULT_DRAWER; painter.startIgnoringIncomingTouchBlockers(); drawer(new GateDrawParams( painter, + hand, true, // inToolbox isHighlighted, false, // isResizeShowing @@ -320,7 +323,7 @@ class DisplayedToolbox { } // Draw highlight. - DisplayedToolbox._paintGate(painter, f.gate, f.rect, true, stats); + DisplayedToolbox._paintGate(painter, hand, f.gate, f.rect, true, stats); // Size tooltip. painter.ctx.save(); diff --git a/src/ui/Hand.js b/src/ui/Hand.js index 351d2937..9094b7b6 100644 --- a/src/ui/Hand.js +++ b/src/ui/Hand.js @@ -66,6 +66,13 @@ class Hand { this.resizingGateSlot = resizingGateSlot; } + /** + * @returns {!boolean} + */ + isHoldingSomething() { + return this.heldGate !== undefined || this.heldColumn !== undefined || this.heldRow !== undefined; + } + /** * @param {!Painter} painter */ diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index 9ce23371..ba4b7902 100644 --- a/test/circuit/Serializer.test.js +++ b/test/circuit/Serializer.test.js @@ -195,6 +195,8 @@ const IDS_THAT_SHOULD_BE_KNOWN = [ "0", "NeGate", "i", "-i", "√i", "√-i", "H", "X", "Y", "Z", + "X^ft", "Y^ft", "Z^ft", + "Rxft", "Ryft", "Rzft", "X^½", "X^⅓", "X^¼", "X^⅛", "X^⅟₁₆", "X^⅟₃₂", "X^-½", "X^-⅓", "X^-¼", "X^-⅛", "X^-⅟₁₆", "X^-⅟₃₂", "Y^½", "Y^⅓", "Y^¼", "Y^⅛", "Y^⅟₁₆", "Y^⅟₃₂", diff --git a/test/gates/AllGates.test.js b/test/gates/AllGates.test.js index 265a8f4d..0c8c9861 100644 --- a/test/gates/AllGates.test.js +++ b/test/gates/AllGates.test.js @@ -213,18 +213,12 @@ suite.test("knownDynamicGateFamilies", () => { // Dynamic displays. 'Sample1', // Qubit rotating gates. - 'X^t', - 'Y^t', - 'Z^t', - 'X^-t', - 'Y^-t', - 'Z^-t', - 'e^iXt', - 'e^iYt', - 'e^iZt', - 'e^-iXt', - 'e^-iYt', - 'e^-iZt', + 'X^t', 'Y^t', 'Z^t', + 'X^-t', 'Y^-t', 'Z^-t', + 'X^ft', 'Y^ft', 'Z^ft', + 'Rxft', 'Ryft', 'Rzft', + 'e^iXt', 'e^iYt', 'e^iZt', + 'e^-iXt', 'e^-iYt', 'e^-iZt', // Discrete cycles. 'Counting1', 'Uncounting1', diff --git a/test/gates/ParametrizedRotationGates.test.js b/test/gates/ParametrizedRotationGates.test.js index 44028123..b8103faf 100644 --- a/test/gates/ParametrizedRotationGates.test.js +++ b/test/gates/ParametrizedRotationGates.test.js @@ -180,3 +180,94 @@ suite.testUsingWebGL('ZToMinusA', () => { --/- 1-/-`)).isApproximatelyEqualTo(state(1, Complex.polar(1, Math.PI*11/8)), 0.0001); }); + +suite.testUsingWebGL('formulaic_formulas', () => { + let f = (text, t) => CircuitStats.fromCircuitAtTime(CircuitDefinition.fromTextDiagram(new Map([ + ['H', Gates.HalfTurns.H], + ['t', Gates.ParametrizedRotationGates.FormulaicRotationZ.withParam(text)], + ['-', undefined], + ]), 'Ht'), (t+1)/2).finalState.cell(0, 1).ln().imag / Math.PI; + + assertThat(f('0.5', 0.1)).isApproximatelyEqualTo(0.5); + assertThat(f('0.5', 0.2)).isApproximatelyEqualTo(0.5); + assertThat(f('t', 0.1)).isApproximatelyEqualTo(0.1); + assertThat(f('t', 0.2)).isApproximatelyEqualTo(0.2); + assertThat(f('t t', 0.1)).isApproximatelyEqualTo(0.1*0.1); + assertThat(f('cos(pi t)', 1/3)).isApproximatelyEqualTo(0.5); + assertThat(f('sin(pi t)', 1/3)).isApproximatelyEqualTo(Math.sqrt(3/4)); + assertThat(f('tan(t)', 1/3)).isApproximatelyEqualTo(Math.tan(1/3)); + + assertThat(f('-t', 1/3)).isApproximatelyEqualTo(-1/3); + assertThat(f('t+t', 1/3)).isApproximatelyEqualTo(2/3); + assertThat(f('t*t', 1/3)).isApproximatelyEqualTo(1/9); + assertThat(f('t-1', 1/3)).isApproximatelyEqualTo(-2/3); + assertThat(f('t/2', 1/3)).isApproximatelyEqualTo(1/6); + + assertThat(f('ln(e)', 1/3)).isApproximatelyEqualTo(1); + assertThat(f('sqrt(t)', 1/3)).isApproximatelyEqualTo(Math.sqrt(1/3)); + assertThat(f('acos(t)', 1/3)).isApproximatelyEqualTo(Math.acos(1/3)-2); + assertThat(f('asin(t)', 1/3)).isApproximatelyEqualTo(Math.asin(1/3)); + assertThat(f('atan(t)', 1/3)).isApproximatelyEqualTo(Math.atan(1/3)); + assertThat(f('ln(t)', 1/3)).isApproximatelyEqualTo(Math.log(1/3)+2); + assertThat(f('exp(t)', 1/3)).isApproximatelyEqualTo(Math.exp(1/3)-2); +}); + +suite.testUsingWebGL('formulaic_matrices', () => { + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationZ.withParam('0.5').knownMatrixAt(0.1) + ).isApproximatelyEqualTo(Matrix.square(1, 0, 0, Complex.I)); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationZ.withParam('t').knownMatrixAt(0.75) + ).isApproximatelyEqualTo(Matrix.square(1, 0, 0, Complex.I)); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationZ.withParam('t').knownMatrixAt(1) + ).isApproximatelyEqualTo(Matrix.square(1, 0, 0, -1)); + + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRz.withParam('0.5 pi').knownMatrixAt(0.1) + ).isApproximatelyEqualTo(Matrix.square(Complex.polar(1, -Math.PI/4), 0, 0, Complex.polar(1, Math.PI/4))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRz.withParam('t pi').knownMatrixAt(0.75) + ).isApproximatelyEqualTo(Matrix.square(Complex.polar(1, -Math.PI/4), 0, 0, Complex.polar(1, Math.PI/4))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRz.withParam('t pi').knownMatrixAt(1) + ).isApproximatelyEqualTo(Matrix.square(Complex.I.neg(), 0, 0, Complex.I)); + + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationX.withParam('0.5').knownMatrixAt(0.1) + ).isApproximatelyEqualTo(Matrix.square( + new Complex(0.5, 0.5), new Complex(0.5, -0.5), + new Complex(0.5, -0.5), new Complex(0.5, 0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationX.withParam('t').knownMatrixAt(0.75) + ).isApproximatelyEqualTo(Matrix.square( + new Complex(0.5, 0.5), new Complex(0.5, -0.5), + new Complex(0.5, -0.5), new Complex(0.5, 0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationX.withParam('t').knownMatrixAt(1) + ).isApproximatelyEqualTo(Matrix.square(0, 1, 1, 0)); + + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRx.withParam('0.5 pi').knownMatrixAt(0.1) + ).isApproximatelyEqualTo(Matrix.square( + 1, Complex.I.neg(), + Complex.I.neg(), 1).times(Math.sqrt(0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRx.withParam('t pi').knownMatrixAt(0.75) + ).isApproximatelyEqualTo(Matrix.square( + 1, Complex.I.neg(), + Complex.I.neg(), 1).times(Math.sqrt(0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRx.withParam('t pi').knownMatrixAt(1) + ).isApproximatelyEqualTo(Matrix.square(0, Complex.I.neg(), Complex.I.neg(), 0)); + + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationY.withParam('0.5').knownMatrixAt(0.1) + ).isApproximatelyEqualTo(Matrix.square( + new Complex(0.5, 0.5), new Complex(-0.5, -0.5), + new Complex(0.5, 0.5), new Complex(0.5, 0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationY.withParam('t').knownMatrixAt(0.75) + ).isApproximatelyEqualTo(Matrix.square( + new Complex(0.5, 0.5), new Complex(-0.5, -0.5), + new Complex(0.5, 0.5), new Complex(0.5, 0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationY.withParam('t').knownMatrixAt(1) + ).isApproximatelyEqualTo(Matrix.square(0, Complex.I.neg(), Complex.I, 0)); + + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRy.withParam('0.5 pi').knownMatrixAt(0.1) + ).isApproximatelyEqualTo(Matrix.square( + 1, -1, + 1, 1).times(Math.sqrt(0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRy.withParam('t pi').knownMatrixAt(0.75) + ).isApproximatelyEqualTo(Matrix.square( + 1, -1, + 1, 1).times(Math.sqrt(0.5))); + assertThat(Gates.ParametrizedRotationGates.FormulaicRotationRy.withParam('t pi').knownMatrixAt(1) + ).isApproximatelyEqualTo(Matrix.square(0, -1, 1, 0)); +}); diff --git a/test/math/Complex.test.js b/test/math/Complex.test.js index 34964f70..8d8dfecf 100644 --- a/test/math/Complex.test.js +++ b/test/math/Complex.test.js @@ -444,3 +444,9 @@ suite.test("raisedTo", () => { assertThat(new Complex(2, 3).raisedTo(new Complex(5, 7))).isApproximatelyEqualTo( new Complex(0.1525582909989, 0.6079153491494)); }); + +suite.test("trig", () => { + assertThat(Complex.from(0.2).cos()).isApproximatelyEqualTo(Complex.from(Math.cos(0.2))); + assertThat(Complex.from(0.2).sin()).isApproximatelyEqualTo(Complex.from(Math.sin(0.2))); + assertThat(Complex.from(0.2).tan()).isApproximatelyEqualTo(Complex.from(Math.tan(0.2))); +});