From 5332bcedc5d0fb24d3b0fc488c1b71dcc258bdd6 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 8 Jun 2019 11:08:38 -0700 Subject: [PATCH 01/19] Fix conditional incoherent amplitude displays not normalizing magnitudes Fixes https://github.com/Strilanc/Quirk/issues/434 --- src/gates/AmplitudeDisplay.js | 4 +++- test/gates/AmplitudeDisplay.test.js | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/gates/AmplitudeDisplay.js b/src/gates/AmplitudeDisplay.js index d76795ce..3068f9d9 100644 --- a/src/gates/AmplitudeDisplay.js +++ b/src/gates/AmplitudeDisplay.js @@ -131,7 +131,9 @@ function processOutputs(span, pixelGroups, circuitDefinition) { let incoherentUnity = 0; for (let i = 0; i < n; i++) { incoherentUnity += rawIncoherentKetPixels[i]; - incoherentKetPixels[i << 1] = Math.sqrt(rawIncoherentKetPixels[i]); + } + for (let i = 0; i < n; i++) { + incoherentKetPixels[i << 1] = Math.sqrt(rawIncoherentKetPixels[i] / incoherentUnity); } if (isNaN(incoherentUnity) || incoherentUnity < 0.000001) { return { diff --git a/test/gates/AmplitudeDisplay.test.js b/test/gates/AmplitudeDisplay.test.js index da0b773f..c982125c 100644 --- a/test/gates/AmplitudeDisplay.test.js +++ b/test/gates/AmplitudeDisplay.test.js @@ -229,3 +229,15 @@ suite.testUsingWebGL("AmplitudesDisplayIncoherent_hadamard", () => { ]); assertThat(out.displays[0].data.coherence_measure).isLessThan(0.85); }); + +suite.testUsingWebGL("AmplitudesDisplayIncoherent_conditioned", () => { + let stats = CircuitStats.fromCircuitAtTime( + Serializer.fromJson(CircuitDefinition, {"cols":[[1,"•","Z"],["•","Amps1"]],"init":["+","+","+"]}), + 0); + let out = stats.toReadableJson(); + assertThat(out.displays[0].data.incoherentKet).isApproximatelyEqualTo([ + Math.sqrt(0.5), + Math.sqrt(0.5), + ]); + assertThat(out.displays[0].data.coherence_measure).isLessThan(0.85); +}); From dd3e3dbe360bd1a88b832cfcbd265118efac66c3 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 8 Jun 2019 11:23:08 -0700 Subject: [PATCH 02/19] Stop probabilityPixelsToColumnVector from reading pixel data beyond 1< { + let stats = CircuitStats.fromCircuitAtTime( + Serializer.fromJson(CircuitDefinition, {"cols":[["Sample1"]]}), + 0); + let out = stats.toReadableJson(); + assertThat(out.displays[0].data.probabilities).isApproximatelyEqualTo([ + 1, + 0, + ]); +}); From da22e114c86c469769183f3b74b178afa10d5a6f Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 04:07:02 -0300 Subject: [PATCH 03/19] Fix amplitude display not showing measurement defered warning --- src/gates/AmplitudeDisplay.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gates/AmplitudeDisplay.js b/src/gates/AmplitudeDisplay.js index 3068f9d9..24a1c777 100644 --- a/src/gates/AmplitudeDisplay.js +++ b/src/gates/AmplitudeDisplay.js @@ -306,7 +306,8 @@ function paintErrorIfPresent(args, indicatorAlpha) { let err = undefined; let {col, row} = args.positionInCircuit; let measured = ((args.stats.circuitDefinition.colIsMeasuredMask(col) >> row) & ((1 << args.gate.height) - 1)) !== 0; - if (measured && indicatorAlpha > 0.9) { + if (measured) { + indicatorAlpha = 0; err = args.gate.width <= 2 ? '(w/ measure defer)' : '(assuming measurement deferred)'; } else if (indicatorAlpha < 0.999) { err = 'incoherent'; From 4cbf0ac2b49c6122197f761df56dcde18a255118 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 04:10:27 -0300 Subject: [PATCH 04/19] Fix tooltip for detectors getting click state wrong --- src/gates/Detector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gates/Detector.js b/src/gates/Detector.js index 857a7926..855b0cc3 100644 --- a/src/gates/Detector.js +++ b/src/gates/Detector.js @@ -396,8 +396,8 @@ function makeDetectControlClearGate(axis) { function makeDetector(axis) { let state = new Map([ ['X', '|0⟩-|1⟩'], - ['Y', '|0⟩+i|1⟩'], - ['Z', '|0⟩'], + ['Y', '|0⟩-i|1⟩'], + ['Z', '|1⟩'], ]).get(axis); let builder = new GateBuilder(). setSerializedIdAndSymbol(`${axis}Detector`). From 25e189d2aeee52380801f49fb2e937af2eaffad3 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 05:16:28 -0300 Subject: [PATCH 05/19] Implement up gate alternation dragging - Grabbing a gate with alt held swiches to its alternate - Alternate of control is anti-control, alternate of rotation is opposite rotation, etc - Add 'alternate' property to Gate - Specify alternates for existing gates - Refactor 'silly' toolbox section into 'scalar' --- src/circuit/Gate.js | 56 ++++++++++++--- src/gates/AllGates.js | 30 ++++++--- src/gates/ArithmeticGates.js | 2 + src/gates/BitCountGates.js | 1 + src/gates/ComparisonGates.js | 3 + src/gates/Controls.js | 3 + src/gates/CountingGates.js | 1 + src/gates/CycleBitsGates.js | 1 + src/gates/ExponentiatingGates.js | 3 + src/gates/FourierTransformGates.js | 1 + src/gates/IncrementGates.js | 1 + src/gates/InterleaveBitsGates.js | 1 + src/gates/Joke_ImaginaryGate.js | 37 +++++++++- src/gates/ModularAdditionGates.js | 1 + src/gates/ModularIncrementGates.js | 1 + src/gates/ModularMultiplicationGates.js | 2 + src/gates/ModularMultiplyAccumulateGates.js | 1 + src/gates/MultiplicationGates.js | 1 + src/gates/MultiplyAccumulateGates.js | 3 + src/gates/ParametrizedRotationGates.js | 3 + src/gates/PhaseGradientGates.js | 2 + src/gates/PostSelectionGates.js | 3 + src/gates/PoweringGates.js | 3 + src/gates/QuarterTurnGates.js | 75 ++++++++++++--------- src/gates/VariousXGates.js | 20 ++++-- src/gates/VariousYGates.js | 20 ++++-- src/gates/VariousZGates.js | 15 +++-- src/main.js | 4 +- src/ui/DisplayedCircuit.js | 32 ++++++--- src/ui/DisplayedInspector.js | 5 +- test/circuit/Serializer.test.js | 2 +- 31 files changed, 251 insertions(+), 82 deletions(-) diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 9e50e93c..c27ea2d0 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -173,6 +173,11 @@ class Gate { * @private */ this._isDefinitelyUnitary = false; + /** + * The alternate gate for this one, used when shift+alt dragging. + * @type {!Gate} + */ + this.alternate = this; /** * Returns context provided by this gate to other gates in the same column (or later columns in some cases). * @param {!int} qubit @@ -235,22 +240,23 @@ class Gate { * @param {!string} name * @param {!string} blurb * @param {undefined|!string} serializedId + * @param {undefined|!Gate=} alternate * @returns {!Gate} */ - static fromKnownMatrix(symbol, matrix, name='', blurb='', serializedId=undefined) { + static fromKnownMatrix(symbol, matrix, name='', blurb='', serializedId=undefined, alternate=undefined) { if (!(matrix instanceof Matrix)) { throw new DetailedError("Bad matrix.", {symbol, matrix, name, blurb}); } - let g = new Gate(); - g.symbol = symbol; - g.serializedId = serializedId === undefined ? symbol : serializedId; - g.name = name; - g.blurb = blurb; - g._isDefinitelyUnitary = matrix.isUnitary(0.01); - g._hasNoEffect = matrix.isIdentity(); - g._stableDuration = Infinity; - g._knownMatrix = matrix; - return g; + let builder = new GateBuilder(). + setSymbol(symbol). + setSerializedId(serializedId === undefined ? symbol : serializedId). + setTitle(name). + setBlurb(blurb). + setKnownEffectToMatrix(matrix); + if (alternate !== undefined) { + builder = builder.setAlternate(alternate); + } + return builder.gate; } /** @@ -263,6 +269,7 @@ class Gate { g.symbol = this.symbol; g.name = this.name; g.blurb = this.blurb; + g.alternate = this.alternate; g.serializedId = this.serializedId; g.onClickGateFunc = this.onClickGateFunc; g.tag = this.tag; @@ -489,6 +496,33 @@ class GateBuilder { return this; } + /** + * @param {!{all: !Array., ofSize: !function(!int) : !Gate}} alternateFamily + * @returns {!GateBuilder} + */ + setAlternateFromFamily(alternateFamily) { + return this.setAlternate(alternateFamily.ofSize(this.gate.height)); + } + + /** + * @param {!Gate} alternate + * @returns {!GateBuilder} + */ + setAlternate(alternate) { + if (alternate === undefined) { + throw new Error("alternate === undefined"); + } + if (alternate.height !== this.gate.height) { + throw new Error("alternate.height !== this.gate.height"); + } + if (alternate.alternate !== alternate) { + throw new Error("alternate.alternate !== alternate"); + } + alternate.alternate = this.gate; + this.gate.alternate = alternate; + return this; + } + /** * Specifies the id to use when serializing/parsing this gate (instead of defaulting to the symbol). * @param {!string} serializedId diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index ca04ffc3..5298f380 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -25,7 +25,12 @@ 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 { + ImaginaryGate, + AntiImaginaryGate, + SqrtImaginaryGate, + AntiSqrtImaginaryGate +} 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" @@ -91,6 +96,9 @@ Gates.Exponentiating = ExponentiatingGates; Gates.FourierTransformGates = FourierTransformGates; Gates.HalfTurns = HalfTurnGates; Gates.ImaginaryGate = ImaginaryGate; +Gates.AntiImaginaryGate = AntiImaginaryGate; +Gates.SqrtImaginaryGate = SqrtImaginaryGate; +Gates.AntiSqrtImaginaryGate = AntiSqrtImaginaryGate; Gates.IncrementGates = IncrementGates; Gates.InputGates = InputGates; Gates.InterleaveBitsGates = InterleaveBitsGates; @@ -129,6 +137,9 @@ Gates.KnownToSerializer = [ ZeroGate, NeGate, ImaginaryGate, + AntiImaginaryGate, + SqrtImaginaryGate, + AntiSqrtImaginaryGate, ...AmplitudeDisplayFamily.all, ...ProbabilityDisplayFamily.all, @@ -250,14 +261,6 @@ Gates.TopToolboxGroups = [ Detectors.XDetector, Detectors.XDetectControlClear, ] }, - { - hint: 'Silly', - gates: [ - ZeroGate, MysteryGateMaker(), - NeGate, ImaginaryGate, - SpacerGate, undefined, - ] - }, ]; /** @type {!Array}>} */ @@ -332,6 +335,15 @@ Gates.BottomToolboxGroups = [ ModularMultiplicationGates.TimesInverseBToTheAModRFamily.ofSize(2), ] }, + { + hint: 'Scalar', + gates: [ + SpacerGate, ZeroGate, + NeGate, undefined, + ImaginaryGate, AntiImaginaryGate, + SqrtImaginaryGate, AntiSqrtImaginaryGate, + ] + }, ]; /** @type {!Map.>} */ diff --git a/src/gates/ArithmeticGates.js b/src/gates/ArithmeticGates.js index 73368765..e301f477 100644 --- a/src/gates/ArithmeticGates.js +++ b/src/gates/ArithmeticGates.js @@ -53,6 +53,7 @@ ArithmeticGates.Legacy_AdditionFamily = Gate.buildFamily(2, 16, (span, builder) setKnownEffectToPermutation(chunkedScaledAdditionPermutationMaker(span, 1))); ArithmeticGates.Legacy_SubtractionFamily = Gate.buildFamily(2, 16, (span, builder) => builder. + setAlternateFromFamily(ArithmeticGates.Legacy_AdditionFamily). setSerializedId("sub" + span). setSymbol("b-=a"). setTitle("Subtraction Gate"). @@ -76,6 +77,7 @@ ArithmeticGates.PlusAFamily = Gate.buildFamily(1, 16, (span, builder) => builder setKnownEffectToParametrizedPermutation((v, a) => (v + a) & ((1 << span) - 1))); ArithmeticGates.MinusAFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(ArithmeticGates.PlusAFamily). setSerializedId("-=A" + span). setSymbol("−A"). setTitle("Subtraction Gate [input A]"). diff --git a/src/gates/BitCountGates.js b/src/gates/BitCountGates.js index 65cc4586..d378e716 100644 --- a/src/gates/BitCountGates.js +++ b/src/gates/BitCountGates.js @@ -47,6 +47,7 @@ BitCountGates.PlusBitCountAFamily = Gate.buildFamily(1, 16, (span, builder) => b setKnownEffectToParametrizedPermutation((t, a) => (t + Util.numberOfSetBits(a)) & ((1 << span) - 1))); BitCountGates.MinusBitCountAFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(BitCountGates.PlusBitCountAFamily). setSerializedIdAndSymbol("-cntA" + span). setSymbol("-1s(A)"). setTitle("Bit Un-Count Gate"). diff --git a/src/gates/ComparisonGates.js b/src/gates/ComparisonGates.js index 5ceecf7c..eacc5543 100644 --- a/src/gates/ComparisonGates.js +++ b/src/gates/ComparisonGates.js @@ -47,6 +47,7 @@ ComparisonGates.ALessThanB = new GateBuilder(). gate; ComparisonGates.AGreaterThanB = new GateBuilder(). + setAlternate(ComparisonGates.ALessThanB). setSerializedId("^A>B"). setSymbol("⊕A>B"). setTitle("Greater-Than Gate"). @@ -67,6 +68,7 @@ ComparisonGates.ALessThanOrEqualToB = new GateBuilder(). gate; ComparisonGates.AGreaterThanOrEqualToB = new GateBuilder(). + setAlternate(ComparisonGates.ALessThanOrEqualToB). setSerializedId("^A>=B"). setSymbol("⊕A≥B"). setTitle("At-Least Gate"). @@ -87,6 +89,7 @@ ComparisonGates.AEqualToB = new GateBuilder(). gate; ComparisonGates.ANotEqualToB = new GateBuilder(). + setAlternate(ComparisonGates.AEqualToB). setSerializedId("^A!=B"). setSymbol("⊕A≠B"). setTitle("Inequality Gate"). diff --git a/src/gates/Controls.js b/src/gates/Controls.js index 20fb018b..1b72690f 100644 --- a/src/gates/Controls.js +++ b/src/gates/Controls.js @@ -37,6 +37,7 @@ Controls.Control = new GateBuilder(). gate; Controls.AntiControl = new GateBuilder(). + setAlternate(Controls.Control). setSerializedIdAndSymbol("◦"). setTitle("Anti-Control"). setBlurb("Conditions on a qubit being OFF.\nGates in the same column only apply to states meeting the condition."). @@ -80,6 +81,7 @@ Controls.XAntiControl = new GateBuilder(). gate; Controls.XControl = new GateBuilder(). + setAlternate(Controls.XAntiControl). setSerializedId("⊖"). // The drawn +/- convention was changed, but the serialized id must stay the same. setSymbol("⊕"). setTitle("X-Axis Control"). @@ -135,6 +137,7 @@ Controls.YAntiControl = new GateBuilder(). gate; Controls.YControl = new GateBuilder(). + setAlternate(Controls.YAntiControl). setSerializedId("(/)"). // The drawn cross/slash convention was changed, but the serialized id must stay the same. setSymbol("⊗"). setTitle("Y-Axis Control"). diff --git a/src/gates/CountingGates.js b/src/gates/CountingGates.js index ac188d7c..b1c339e8 100644 --- a/src/gates/CountingGates.js +++ b/src/gates/CountingGates.js @@ -133,6 +133,7 @@ CountingGates.CountingFamily = Gate.buildFamily(1, 16, (span, builder) => builde setKnownEffectToTimeVaryingPermutation((t, i) => offsetPermutation(t, +1, span, i))); CountingGates.UncountingFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(CountingGates.CountingFamily). setSerializedId("Uncounting" + span). setSymbol("-⌈t⌉"). setTitle("Down Counting Gate"). diff --git a/src/gates/CycleBitsGates.js b/src/gates/CycleBitsGates.js index a1ccf786..a44fe2d9 100644 --- a/src/gates/CycleBitsGates.js +++ b/src/gates/CycleBitsGates.js @@ -83,6 +83,7 @@ CycleBitsGates.CycleBitsFamily = Gate.buildFamily(2, 16, (span, builder) => buil setKnownEffectToBitPermutation(i => (i + 1) % span)); CycleBitsGates.ReverseCycleBitsFamily = Gate.buildFamily(2, 16, (span, builder) => builder. + setAlternateFromFamily(CycleBitsGates.CycleBitsFamily). setSerializedId(">>" + span). setSymbol(">>>"). setTitle("Right Rotate"). diff --git a/src/gates/ExponentiatingGates.js b/src/gates/ExponentiatingGates.js index 861dd7fc..6daf7c90 100644 --- a/src/gates/ExponentiatingGates.js +++ b/src/gates/ExponentiatingGates.js @@ -45,6 +45,7 @@ ExponentiatingGates.XForward = new GateBuilder(). gate; ExponentiatingGates.XBackward = new GateBuilder(). + setAlternate(ExponentiatingGates.XForward). setSerializedIdAndSymbol("e^iXt"). setTitle("X-Exponentiating Gate (backward)"). setBlurb("Left-hand rotation around the X axis.\nPasses through ±iX instead of X."). @@ -63,6 +64,7 @@ ExponentiatingGates.YForward = new GateBuilder(). gate; ExponentiatingGates.YBackward = new GateBuilder(). + setAlternate(ExponentiatingGates.YForward). setSerializedIdAndSymbol("e^iYt"). setTitle("Y-Exponentiating Gate (backward)"). setBlurb("Left-hand rotation around the Y axis.\nPasses through ±iY instead of Y."). @@ -81,6 +83,7 @@ ExponentiatingGates.ZForward = new GateBuilder(). gate; ExponentiatingGates.ZBackward = new GateBuilder(). + setAlternate(ExponentiatingGates.ZForward). setSerializedIdAndSymbol("e^iZt"). setTitle("Z-Exponentiating Gate (backward)"). setBlurb("Left-hand rotation around the Z axis.\nPasses through ±iZ instead of Z."). diff --git a/src/gates/FourierTransformGates.js b/src/gates/FourierTransformGates.js index c7d36088..ab8af4be 100644 --- a/src/gates/FourierTransformGates.js +++ b/src/gates/FourierTransformGates.js @@ -89,6 +89,7 @@ FourierTransformGates.FourierTransformFamily = Gate.buildFamily(1, 16, (span, bu FourierTransformGates.InverseFourierTransformFamily = Gate.buildFamily(1, 16, (span, builder) => builder. setSerializedId("QFT†" + span). setSymbol("QFT^†"). + setAlternateFromFamily(FourierTransformGates.FourierTransformFamily). setTitle("Inverse Fourier Transform Gate"). setBlurb("Transforms from/to phase frequency space."). setActualEffectToUpdateFunc(ctx => applyBackwardGradientShaders(ctx, span)). diff --git a/src/gates/IncrementGates.js b/src/gates/IncrementGates.js index 3ea9e2d5..b1bba2c3 100644 --- a/src/gates/IncrementGates.js +++ b/src/gates/IncrementGates.js @@ -19,6 +19,7 @@ IncrementGates.IncrementFamily = Gate.buildFamily(1, 16, (span, builder) => buil setKnownEffectToPermutation(t => (t + 1) & ((1 << span) - 1))); IncrementGates.DecrementFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(IncrementGates.IncrementFamily). setSerializedId("dec" + span). setSymbol("−1"). setTitle("Decrement Gate"). diff --git a/src/gates/InterleaveBitsGates.js b/src/gates/InterleaveBitsGates.js index 3734c7f5..5d006d0a 100644 --- a/src/gates/InterleaveBitsGates.js +++ b/src/gates/InterleaveBitsGates.js @@ -124,6 +124,7 @@ InterleaveBitsGates.InterleaveBitsGateFamily = Gate.buildFamily(4, 16, (span, bu setKnownEffectToBitPermutation(b => interleaveBit(b, span))); InterleaveBitsGates.DeinterleaveBitsGateFamily = Gate.buildFamily(4, 16, (span, builder) => builder. + setAlternateFromFamily(InterleaveBitsGates.InterleaveBitsGateFamily). setSerializedId("split" + span). setSymbol("Deinterleave"). setTitle("Deinterleave"). diff --git a/src/gates/Joke_ImaginaryGate.js b/src/gates/Joke_ImaginaryGate.js index b473c04b..37f5c26d 100644 --- a/src/gates/Joke_ImaginaryGate.js +++ b/src/gates/Joke_ImaginaryGate.js @@ -28,4 +28,39 @@ const ImaginaryGate = new GateBuilder(). setKnownEffectToMatrix(Matrix.square(Complex.I, 0, 0, Complex.I)). gate; -export {ImaginaryGate} +const AntiImaginaryGate = new GateBuilder(). + setAlternate(ImaginaryGate). + setSerializedIdAndSymbol("-i"). + setTitle("Anti-Imaginary Gate"). + setBlurb("Phases everything by -i."). + setDrawer(args => { + GatePainting.paintLocationIndependentFrame(args); + GatePainting.paintGateSymbol(args); + }). + setKnownEffectToMatrix(Matrix.square(Complex.I.neg(), 0, 0, Complex.I.neg())). + gate; + +const SqrtImaginaryGate = new GateBuilder(). + setSerializedIdAndSymbol("√i"). + setTitle("Half Imaginary Gate"). + setBlurb("Phases everything by √i."). + setDrawer(args => { + GatePainting.paintLocationIndependentFrame(args); + GatePainting.paintGateSymbol(args); + }). + setKnownEffectToMatrix(Matrix.square(1, 0, 0, 1).times(new Complex(Math.sqrt(0.5), Math.sqrt(0.5)))). + gate; + +const AntiSqrtImaginaryGate = new GateBuilder(). + setAlternate(SqrtImaginaryGate). + setSerializedIdAndSymbol("√-i"). + setTitle("Half Anti-Imaginary Gate"). + setBlurb("Phases everything by √-i."). + setDrawer(args => { + GatePainting.paintLocationIndependentFrame(args); + GatePainting.paintGateSymbol(args); + }). + setKnownEffectToMatrix(Matrix.square(1, 0, 0, 1).times(new Complex(Math.sqrt(0.5), -Math.sqrt(0.5)))). + gate; + +export {AntiImaginaryGate, ImaginaryGate, SqrtImaginaryGate, AntiSqrtImaginaryGate} diff --git a/src/gates/ModularAdditionGates.js b/src/gates/ModularAdditionGates.js index 29248570..1b7a998a 100644 --- a/src/gates/ModularAdditionGates.js +++ b/src/gates/ModularAdditionGates.js @@ -50,6 +50,7 @@ ModularAdditionGates.PlusAModRFamily = Gate.buildFamily(1, 16, (span, builder) = setKnownEffectToParametrizedPermutation((t, a, r) => t < r ? (t + a) % r : t)); ModularAdditionGates.MinusAModRFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(ModularAdditionGates.PlusAModRFamily). setSerializedId("-AmodR" + span). setSymbol("−A\nmod R"). setTitle("Modular Subtraction Gate"). diff --git a/src/gates/ModularIncrementGates.js b/src/gates/ModularIncrementGates.js index 7a1747ea..d0d40888 100644 --- a/src/gates/ModularIncrementGates.js +++ b/src/gates/ModularIncrementGates.js @@ -63,6 +63,7 @@ ModularIncrementGates.IncrementModRFamily = Gate.buildFamily(1, 16, (span, build setKnownEffectToParametrizedPermutation((t, a) => t < a ? (t + 1) % a : t)); ModularIncrementGates.DecrementModRFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(ModularIncrementGates.IncrementModRFamily). setSerializedId("decmodR" + span). setSymbol("−1\nmod R"). setTitle("Modular Decrement Gate"). diff --git a/src/gates/ModularMultiplicationGates.js b/src/gates/ModularMultiplicationGates.js index 8e40a068..f4c6baf5 100644 --- a/src/gates/ModularMultiplicationGates.js +++ b/src/gates/ModularMultiplicationGates.js @@ -226,6 +226,7 @@ ModularMultiplicationGates.TimesAModRFamily = Gate.buildFamily(1, 16, (span, bui setKnownEffectToParametrizedPermutation(modularMultiply)); ModularMultiplicationGates.TimesAModRInverseFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(ModularMultiplicationGates.TimesAModRFamily). setSerializedId("/AmodR" + span). setSymbol("×A^-1\nmod R"). setTitle("Modular Division Gate"). @@ -253,6 +254,7 @@ ModularMultiplicationGates.TimesBToTheAModRFamily = Gate.buildFamily(1, 16, (spa setKnownEffectToParametrizedPermutation((t, a, b, r) => modularPowerMultiply(t, b, a, r))); ModularMultiplicationGates.TimesInverseBToTheAModRFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(ModularMultiplicationGates.TimesBToTheAModRFamily). setSerializedId("/BToAmodR" + span). setSymbol("×B^-A\nmod R"). setTitle("Modular Power Division Gate"). diff --git a/src/gates/ModularMultiplyAccumulateGates.js b/src/gates/ModularMultiplyAccumulateGates.js index 0f2b36d8..2c9d4c2f 100644 --- a/src/gates/ModularMultiplyAccumulateGates.js +++ b/src/gates/ModularMultiplyAccumulateGates.js @@ -60,6 +60,7 @@ ModularMultiplyAccumulateGates.PlusABModRFamily = Gate.buildFamily(1, 16, (span, setKnownEffectToParametrizedPermutation((t, a, b, r) => t < r ? (t + a*b) % r : t)); ModularMultiplyAccumulateGates.MinusABModRFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(ModularMultiplyAccumulateGates.PlusABModRFamily). setSerializedId("-ABmodR" + span). setSymbol("−AB\nmod R"). setTitle("Modular Multiply-Subtract Gate"). diff --git a/src/gates/MultiplicationGates.js b/src/gates/MultiplicationGates.js index 5027cdac..6406bb52 100644 --- a/src/gates/MultiplicationGates.js +++ b/src/gates/MultiplicationGates.js @@ -62,6 +62,7 @@ MultiplicationGates.TimesAFamily = Gate.buildFamily(1, 16, (span, builder) => bu setKnownEffectToParametrizedPermutation((x, a) => modularMultiply(x, a, 1< builder. + setAlternateFromFamily(MultiplicationGates.TimesAFamily). setSerializedId("/A" + span). setSymbol("×A^-1"). setTitle("Inverse Multiplication Gate"). diff --git a/src/gates/MultiplyAccumulateGates.js b/src/gates/MultiplyAccumulateGates.js index 5bb9c486..51c5c1c1 100644 --- a/src/gates/MultiplyAccumulateGates.js +++ b/src/gates/MultiplyAccumulateGates.js @@ -85,6 +85,7 @@ MultiplyAccumulateGates.Legacy_MultiplyAddFamily = Gate.buildFamily(3, 16, (span setKnownEffectToPermutation(makeScaledMultiplyAddPermutation(span, +1))); MultiplyAccumulateGates.Legacy_MultiplySubtractFamily = Gate.buildFamily(3, 16, (span, builder) => builder. + setAlternateFromFamily(MultiplyAccumulateGates.Legacy_MultiplyAddFamily). setSerializedId("c-=ab" + span). setSymbol("c-=ab"). setTitle("Multiply-Subtract Gate"). @@ -113,6 +114,7 @@ MultiplyAccumulateGates.MultiplyAddInputsFamily = Gate.buildFamily(1, 16, (span, setKnownEffectToParametrizedPermutation((t, a, b) => (t + a*b) & ((1 << span) - 1))); MultiplyAccumulateGates.MultiplySubtractInputsFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(MultiplyAccumulateGates.MultiplyAddInputsFamily). setSerializedId("-=AB" + span). setSymbol("−AB"). setTitle("Multiply-Subtract Gate [Inputs A, B]"). @@ -135,6 +137,7 @@ MultiplyAccumulateGates.SquareAddInputFamily = Gate.buildFamily(1, 16, (span, bu setKnownEffectToParametrizedPermutation((t, a) => (t + a*a) & ((1 << span) - 1))); MultiplyAccumulateGates.SquareSubtractInputFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(MultiplyAccumulateGates.SquareAddInputFamily). setSerializedId("-=AA" + span). setSymbol("-A^2"). setTitle("Square-Subtract Gate [Input A]"). diff --git a/src/gates/ParametrizedRotationGates.js b/src/gates/ParametrizedRotationGates.js index fabfa6b3..42af425d 100644 --- a/src/gates/ParametrizedRotationGates.js +++ b/src/gates/ParametrizedRotationGates.js @@ -90,6 +90,7 @@ ParametrizedRotationGates.XToA = new GateBuilder(). gate; ParametrizedRotationGates.XToMinusA = new GateBuilder(). + setAlternate(ParametrizedRotationGates.XToA). setSerializedId("X^(-A/2^n)"). setSymbol("X^-A/2ⁿ"). setTitle("Parametrized -X Gate"). @@ -120,6 +121,7 @@ ParametrizedRotationGates.YToA = new GateBuilder(). gate; ParametrizedRotationGates.YToMinusA = new GateBuilder(). + setAlternate(ParametrizedRotationGates.YToA). setSerializedId("Y^(-A/2^n)"). setSymbol("Y^-A/2ⁿ"). setTitle("Parametrized -Y Gate"). @@ -150,6 +152,7 @@ ParametrizedRotationGates.ZToA = new GateBuilder(). gate; ParametrizedRotationGates.ZToMinusA = new GateBuilder(). + setAlternate(ParametrizedRotationGates.ZToA). setSerializedId("Z^(-A/2^n)"). setSymbol("Z^-A/2ⁿ"). setTitle("Parametrized -Z Gate"). diff --git a/src/gates/PhaseGradientGates.js b/src/gates/PhaseGradientGates.js index 26d1df3b..9a903d20 100644 --- a/src/gates/PhaseGradientGates.js +++ b/src/gates/PhaseGradientGates.js @@ -55,6 +55,7 @@ PhaseGradientGates.PhaseGradientFamily = Gate.buildFamily(1, 16, (span, builder) setKnownEffectToPhaser(k => k / (2 << span))); PhaseGradientGates.PhaseDegradientFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(PhaseGradientGates.PhaseGradientFamily). setSerializedId("PhaseUngradient" + span). setSymbol("Grad^-½"). setTitle("Inverse Half Gradient Gate"). @@ -79,6 +80,7 @@ PhaseGradientGates.DynamicPhaseGradientFamily = Gate.buildFamily(1, 16, (span, b setDrawer(GatePainting.makeCycleDrawer(-1, -1, 1, -Math.PI / 2))); PhaseGradientGates.DynamicPhaseDegradientFamily = Gate.buildFamily(1, 16, (span, builder) => builder. + setAlternateFromFamily(PhaseGradientGates.DynamicPhaseGradientFamily). setSerializedId("grad^-t" + span). setSymbol("Grad^-t"). setTitle("Inverse Cycling Gradient Gate"). diff --git a/src/gates/PostSelectionGates.js b/src/gates/PostSelectionGates.js index 54650f87..7ca534d0 100644 --- a/src/gates/PostSelectionGates.js +++ b/src/gates/PostSelectionGates.js @@ -45,6 +45,7 @@ PostSelectionGates.PostSelectOff = new GateBuilder(). /** @type {!Gate} */ PostSelectionGates.PostSelectOn = new GateBuilder(). + setAlternate(PostSelectionGates.PostSelectOff). setSerializedIdAndSymbol("|1⟩⟨1|"). setTitle("Postselect On"). setBlurb("Keeps On states, discards/retries Off states."). @@ -64,6 +65,7 @@ PostSelectionGates.PostSelectAntiX = new GateBuilder(). /** @type {!Gate} */ PostSelectionGates.PostSelectX = new GateBuilder(). + setAlternate(PostSelectionGates.PostSelectAntiX). setSerializedId("|-⟩⟨-|"). // The +/- drawing convention was switched, but the serialized id must stay the same. setSymbol("|⊕⟩⟨⊕|"). setTitle("Postselect X-On"). @@ -84,6 +86,7 @@ PostSelectionGates.PostSelectAntiY = new GateBuilder(). /** @type {!Gate} */ PostSelectionGates.PostSelectY = new GateBuilder(). + setAlternate(PostSelectionGates.PostSelectAntiY). setSerializedId("|/⟩⟨/|"). // The cross/slash convention was switched, but the serialized id must stay the same. setSymbol("|⊗⟩⟨⊗|"). setTitle("Postselect Y-On"). diff --git a/src/gates/PoweringGates.js b/src/gates/PoweringGates.js index ec3f36b2..2c39593d 100644 --- a/src/gates/PoweringGates.js +++ b/src/gates/PoweringGates.js @@ -45,6 +45,7 @@ PoweringGates.XForward = new GateBuilder(). gate; PoweringGates.XBackward = new GateBuilder(). + setAlternate(PoweringGates.XForward). setSerializedIdAndSymbol("X^-t"). setTitle("X-Raising Gate (backward)"). setBlurb("Left-handed cycle from no-op to X."). @@ -63,6 +64,7 @@ PoweringGates.YForward = new GateBuilder(). gate; PoweringGates.YBackward = new GateBuilder(). + setAlternate(PoweringGates.YForward). setSerializedIdAndSymbol("Y^-t"). setTitle("Y-Raising Gate (backward)"). setBlurb("Left-handed cycle from no-op to Y."). @@ -81,6 +83,7 @@ PoweringGates.ZForward = new GateBuilder(). gate; PoweringGates.ZBackward = new GateBuilder(). + setAlternate(PoweringGates.ZForward). setSerializedIdAndSymbol("Z^-t"). setTitle("Z-Raising Gate (backward)"). setBlurb("Left-handed cycle from no-op to Z."). diff --git a/src/gates/QuarterTurnGates.js b/src/gates/QuarterTurnGates.js index b6aff93e..c076ce56 100644 --- a/src/gates/QuarterTurnGates.js +++ b/src/gates/QuarterTurnGates.js @@ -12,54 +12,63 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {Gate} from "src/circuit/Gate.js" +import {Gate, GateBuilder} from "src/circuit/Gate.js" import {Matrix} from "src/math/Matrix.js" let QuarterTurnGates = {}; /** @type {!Gate} */ -QuarterTurnGates.SqrtXForward = Gate.fromKnownMatrix( - "X^½", - Matrix.fromPauliRotation(0.25, 0, 0), - "√X Gate", - "Principle square root of Not."); +QuarterTurnGates.SqrtXForward = new GateBuilder(). + setSerializedIdAndSymbol('X^½'). + setTitle("√X Gate"). + setBlurb("Principle square root of Not."). + setKnownEffectToMatrix(Matrix.fromPauliRotation(0.25, 0, 0)). + gate; /** @type {!Gate} */ -QuarterTurnGates.SqrtXBackward = Gate.fromKnownMatrix( - "X^-½", - Matrix.fromPauliRotation(0.75, 0, 0), - "X^-½ Gate", - "Adjoint square root of Not."); +QuarterTurnGates.SqrtXBackward = new GateBuilder(). + setAlternate(QuarterTurnGates.SqrtXForward). + setSerializedIdAndSymbol('X^-½'). + setTitle("X^-½ Gate"). + setBlurb("Adjoint square root of Not."). + setKnownEffectToMatrix(Matrix.fromPauliRotation(0.75, 0, 0)). + gate; /** @type {!Gate} */ -QuarterTurnGates.SqrtYForward = Gate.fromKnownMatrix( - "Y^½", - Matrix.fromPauliRotation(0, 0.25, 0), - "√Y Gate", - "Principle square root of Y."); +QuarterTurnGates.SqrtYForward = new GateBuilder(). + setSerializedIdAndSymbol('Y^½'). + setTitle("√Y Gate"). + setBlurb("Principle square root of Y."). + setKnownEffectToMatrix(Matrix.fromPauliRotation(0, 0.25, 0)). + gate; /** @type {!Gate} */ -QuarterTurnGates.SqrtYBackward = Gate.fromKnownMatrix( - "Y^-½", - Matrix.fromPauliRotation(0, 0.75, 0), - "Y^-½ Gate", - "Adjoint square root of Y."); +QuarterTurnGates.SqrtYBackward = new GateBuilder(). + setAlternate(QuarterTurnGates.SqrtYForward). + setSerializedIdAndSymbol('Y^-½'). + setTitle("Y^-½ Gate"). + setBlurb("Adjoint square root of Y."). + setKnownEffectToMatrix(Matrix.fromPauliRotation(0, 0.75, 0)). + gate; /** @type {!Gate} */ -QuarterTurnGates.SqrtZForward = Gate.fromKnownMatrix( - "S", - Matrix.fromPauliRotation(0, 0, 0.25), - "√Z Gate", - "Principle square root of Z.\nAlso known as the 'S' gate.", - "Z^½"); +QuarterTurnGates.SqrtZForward = new GateBuilder(). + setSerializedId('Z^½'). + setSymbol('S'). + setTitle("√Z Gate"). + setBlurb("Principle square root of Z.\nAlso known as the 'S' gate."). + setKnownEffectToMatrix(Matrix.fromPauliRotation(0, 0, 0.25)). + gate; /** @type {!Gate} */ -QuarterTurnGates.SqrtZBackward = Gate.fromKnownMatrix( - "S^-1", - Matrix.fromPauliRotation(0, 0, 0.75), - "Z^-½ Gate", - "Adjoint square root of Z.", - "Z^-½"); +QuarterTurnGates.SqrtZBackward = new GateBuilder(). + setAlternate(QuarterTurnGates.SqrtZForward). + setSerializedId('Z^-½'). + setSymbol('S^-1'). + setTitle("Z^-½ Gate"). + setBlurb("Adjoint square root of Z."). + setKnownEffectToMatrix(Matrix.fromPauliRotation(0, 0, 0.75)). + gate; QuarterTurnGates.all = [ QuarterTurnGates.SqrtXForward, diff --git a/src/gates/VariousXGates.js b/src/gates/VariousXGates.js index de8cb738..f8eb5d55 100644 --- a/src/gates/VariousXGates.js +++ b/src/gates/VariousXGates.js @@ -26,7 +26,9 @@ VariousXGates.X3i = Gate.fromKnownMatrix( "X^-⅓", Matrix.fromPauliRotation(-1 / 6, 0, 0), "X^-⅓ Gate", - "Adjoint third root of X."); + "Adjoint third root of X.", + undefined, + VariousXGates.X3); VariousXGates.X4 = Gate.fromKnownMatrix( "X^¼", Matrix.fromPauliRotation(1 / 8, 0, 0), @@ -36,7 +38,9 @@ VariousXGates.X4i = Gate.fromKnownMatrix( "X^-¼", Matrix.fromPauliRotation(-1 / 8, 0, 0), "X^-¼ Gate", - "Adjoint fourth root of X."); + "Adjoint fourth root of X.", + undefined, + VariousXGates.X4); VariousXGates.X8 = Gate.fromKnownMatrix( "X^⅛", Matrix.fromPauliRotation(1 / 16, 0, 0), @@ -46,7 +50,9 @@ VariousXGates.X8i = Gate.fromKnownMatrix( "X^-⅛", Matrix.fromPauliRotation(-1 / 16, 0, 0), "X^-⅛ Gate", - "Adjoint eighth root of X."); + "Adjoint eighth root of X.", + undefined, + VariousXGates.X8); VariousXGates.X16 = Gate.fromKnownMatrix( "X^⅟₁₆", Matrix.fromPauliRotation(1 / 32, 0, 0), @@ -56,7 +62,9 @@ VariousXGates.X16i = Gate.fromKnownMatrix( "X^-⅟₁₆", Matrix.fromPauliRotation(-1 / 32, 0, 0), "X^-⅟₁₆ Gate", - "Adjoint sixteenth root of X."); + "Adjoint sixteenth root of X.", + undefined, + VariousXGates.X16); VariousXGates.X32 = Gate.fromKnownMatrix( "X^⅟₃₂", Matrix.fromPauliRotation(1 / 64, 0, 0), @@ -66,7 +74,9 @@ VariousXGates.X32i = Gate.fromKnownMatrix( "X^-⅟₃₂", Matrix.fromPauliRotation(-1 / 64, 0, 0), "X^-⅟₃₂ Gate", - "Adjoint 32'nd root of X."); + "Adjoint 32'nd root of X.", + undefined, + VariousXGates.X32); VariousXGates.all =[ VariousXGates.X3, diff --git a/src/gates/VariousYGates.js b/src/gates/VariousYGates.js index d2986aea..bbf0b0b6 100644 --- a/src/gates/VariousYGates.js +++ b/src/gates/VariousYGates.js @@ -26,7 +26,9 @@ VariousYGates.Y3i = Gate.fromKnownMatrix( "Y^-⅓", Matrix.fromPauliRotation(0, -1 / 6, 0), "Y^-⅓ Gate", - "Adjoint third root of Y."); + "Adjoint third root of Y.", + undefined, + VariousYGates.Y3); VariousYGates.Y4 = Gate.fromKnownMatrix( "Y^¼", Matrix.fromPauliRotation(0, 1 / 8, 0), @@ -36,7 +38,9 @@ VariousYGates.Y4i = Gate.fromKnownMatrix( "Y^-¼", Matrix.fromPauliRotation(0, -1 / 8, 0), "Y^-¼ Gate", - "Adjoint fourth root of Y."); + "Adjoint fourth root of Y.", + undefined, + VariousYGates.Y4); VariousYGates.Y8 = Gate.fromKnownMatrix( "Y^⅛", Matrix.fromPauliRotation(0, 1 / 16, 0), @@ -46,7 +50,9 @@ VariousYGates.Y8i = Gate.fromKnownMatrix( "Y^-⅛", Matrix.fromPauliRotation(0, -1 / 16, 0), "Y^-⅛ Gate", - "Adjoint eighth root of Y."); + "Adjoint eighth root of Y.", + undefined, + VariousYGates.Y8); VariousYGates.Y16 = Gate.fromKnownMatrix( "Y^⅟₁₆", Matrix.fromPauliRotation(0, 1 / 32, 0), @@ -56,7 +62,9 @@ VariousYGates.Y16i = Gate.fromKnownMatrix( "Y^-⅟₁₆", Matrix.fromPauliRotation(0, -1 / 32, 0), "Y^-⅟₁₆ Gate", - "Adjoint sixteenth root of Y."); + "Adjoint sixteenth root of Y.", + undefined, + VariousYGates.Y16); VariousYGates.Y32 = Gate.fromKnownMatrix( "Y^⅟₃₂", Matrix.fromPauliRotation(0, 1 / 64, 0), @@ -66,7 +74,9 @@ VariousYGates.Y32i = Gate.fromKnownMatrix( "Y^-⅟₃₂", Matrix.fromPauliRotation(0, -1 / 64, 0), "Y^-⅟₃₂ Gate", - "Adjoint 32'nd root of Y."); + "Adjoint 32'nd root of Y.", + undefined, + VariousYGates.Y32); VariousYGates.all =[ VariousYGates.Y3, diff --git a/src/gates/VariousZGates.js b/src/gates/VariousZGates.js index 9163255c..d131109d 100644 --- a/src/gates/VariousZGates.js +++ b/src/gates/VariousZGates.js @@ -26,7 +26,9 @@ VariousZGates.Z3i = Gate.fromKnownMatrix( "Z^-⅓", Matrix.fromPauliRotation(0, 0, -1 / 6), "Z^-⅓ Gate", - "Adjoint third root of Z."); + "Adjoint third root of Z.", + undefined, + VariousZGates.Z3); VariousZGates.Z4 = Gate.fromKnownMatrix( "T", Matrix.fromPauliRotation(0, 0, 1 / 8), @@ -38,7 +40,8 @@ VariousZGates.Z4i = Gate.fromKnownMatrix( Matrix.fromPauliRotation(0, 0, -1 / 8), "Z^-¼ Gate", "Adjoint fourth root of Z.", - "Z^-¼"); + "Z^-¼", + VariousZGates.Z4); VariousZGates.Z8 = Gate.fromKnownMatrix( "Z^⅛", Matrix.fromPauliRotation(0, 0, 1 / 16), @@ -48,7 +51,9 @@ VariousZGates.Z8i = Gate.fromKnownMatrix( "Z^-⅛", Matrix.fromPauliRotation(0, 0, -1 / 16), "Z^-⅛ Gate", - "Adjoint eighth root of Z."); + "Adjoint eighth root of Z.", + undefined, + VariousZGates.Z8); VariousZGates.Z16 = Gate.fromKnownMatrix( "Z^⅟₁₆", Matrix.fromPauliRotation(0, 0, 1 / 32), @@ -58,7 +63,9 @@ VariousZGates.Z16i = Gate.fromKnownMatrix( "Z^-⅟₁₆", Matrix.fromPauliRotation(0, 0, -1 / 32), "Z^-⅟₁₆ Gate", - "Adjoint 16'th root of Z."); + "Adjoint 16'th root of Z.", + undefined, + VariousZGates.Z16); VariousZGates.Z32 = Gate.fromKnownMatrix( "Z^⅟₃₂", diff --git a/src/main.js b/src/main.js index 945f7b71..e34ae11c 100644 --- a/src/main.js +++ b/src/main.js @@ -197,7 +197,7 @@ watchDrags(canvasDiv, revision.startedWorkingOnCommit(); displayed.set( syncArea(oldInspector.withHand(newHand).withJustEnoughWires(newInspector.hand, 1)). - afterGrabbing(ev.shiftKey, ev.ctrlKey)); + afterGrabbing(ev.shiftKey, ev.ctrlKey, false, ev.altKey)); ev.preventDefault(); }, @@ -250,7 +250,7 @@ canvasDiv.addEventListener('mousedown', ev => { let newHand = displayed.get().hand.withPos(eventPosRelativeTo(ev, canvas)); let newInspector = syncArea(displayed.get()). withHand(newHand). - afterGrabbing(false, false, true). // Grab the gate. + afterGrabbing(false, false, true, false). // Grab the gate. withHand(newHand). // Lose the gate. afterTidyingUp(). withJustEnoughWires(newHand, 0); diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index aa61018d..c438f8bd 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -1071,15 +1071,16 @@ class DisplayedCircuit { * @param {!boolean=false} duplicate * @param {!boolean=false} wholeColumn * @param {!boolean=false} ignoreResizeTabs + * @param {!boolean=false} alt Whether or not to replace grabbed gates with their alternates. * @returns {!{newCircuit: !DisplayedCircuit, newHand: !Hand}} */ - tryGrab(hand, duplicate=false, wholeColumn=false, ignoreResizeTabs=false) { + tryGrab(hand, duplicate=false, wholeColumn=false, ignoreResizeTabs=false, alt=false) { if (wholeColumn) { - let grabRowResult = this._tryGrabRow(hand); + let grabRowResult = this._tryGrabRow(hand, alt); if (grabRowResult !== undefined) { return grabRowResult; } - return this._tryGrabWholeColumn(hand, duplicate) || {newCircuit: this, newHand: hand}; + return this._tryGrabWholeColumn(hand, duplicate, alt) || {newCircuit: this, newHand: hand}; } let newHand = hand; @@ -1092,14 +1093,15 @@ class DisplayedCircuit { } } - return newCircuit._tryGrabGate(newHand, duplicate) || {newCircuit, newHand}; + return newCircuit._tryGrabGate(newHand, duplicate, alt) || {newCircuit, newHand}; } /** * @param {!Hand} hand + * @param {!boolean} alt * @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}} */ - _tryGrabRow(hand) { + _tryGrabRow(hand, alt) { if (hand.pos === undefined) { return undefined; } @@ -1118,6 +1120,9 @@ class DisplayedCircuit { let {newCircuit, initialState, rowGates} = this._cutRow(wire); let holdOffset = new Point(0, hand.pos.y - r.y); + if (alt) { + rowGates = rowGates.map(e => e === undefined ? e : e.alternate); + } return { newCircuit: this.withCircuit(newCircuit), newHand: hand.withHeldRow({initialState, gates: rowGates}, holdOffset) @@ -1152,10 +1157,11 @@ class DisplayedCircuit { /** * @param {!Hand} hand - * @param {!boolean=} duplicate + * @param {!boolean} duplicate + * @param {!boolean} alt * @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}} */ - _tryGrabGate(hand, duplicate=false) { + _tryGrabGate(hand, duplicate, alt) { if (hand.isBusy() || hand.pos === undefined) { return undefined; } @@ -1167,6 +1173,9 @@ class DisplayedCircuit { let {col, row, offset} = foundPt; let gate = this.circuitDefinition.columns[col].gates[row]; + if (alt) { + gate = gate.alternate; + } let remainingGates = seq(this.circuitDefinition.columns[col].gates).toArray(); if (!duplicate) { @@ -1219,10 +1228,11 @@ class DisplayedCircuit { /** * @param {!Hand} hand * @param {!boolean} duplicate + * @param {!boolean} alt Whether or not to replace grabbed gates with their alternates. * @returns {undefined|!{newCircuit: !DisplayedCircuit, newHand: !Hand}} * @private */ - _tryGrabWholeColumn(hand, duplicate) { + _tryGrabWholeColumn(hand, duplicate, alt) { if (hand.isBusy() || hand.pos === undefined) { return undefined; } @@ -1238,9 +1248,13 @@ class DisplayedCircuit { } let holdOffset = new Point(0, this.wireIndexAt(hand.pos.y) * Config.WIRE_SPACING + Config.WIRE_SPACING/2); + let grabbedGates = this.circuitDefinition.columns[col]; + if (alt) { + grabbedGates = new GateColumn(grabbedGates.gates.map(e => e === undefined ? e : e.alternate)); + } return { newCircuit: this.withCircuit(this.circuitDefinition.withColumns(newCols)), - newHand: hand.withHeldGateColumn(this.circuitDefinition.columns[col], holdOffset) + newHand: hand.withHeldGateColumn(grabbedGates, holdOffset) }; } diff --git a/src/ui/DisplayedInspector.js b/src/ui/DisplayedInspector.js index 5dde7dec..485f7bbc 100644 --- a/src/ui/DisplayedInspector.js +++ b/src/ui/DisplayedInspector.js @@ -154,15 +154,16 @@ class DisplayedInspector { * @param {!boolean=false} duplicate * @param {!boolean=false} wholeCol * @param {!boolean=false} ignoreResizeTabs + * @param {!boolean=false} alt * @returns {!DisplayedInspector} */ - afterGrabbing(duplicate=false, wholeCol=false, ignoreResizeTabs=false) { + afterGrabbing(duplicate=false, wholeCol=false, ignoreResizeTabs=false, alt=false) { let hand = this.hand; let circuit = this.displayedCircuit; hand = this.displayedToolboxTop.tryGrab(hand); hand = this.displayedToolboxBottom.tryGrab(hand); - let obj = circuit.tryGrab(hand, duplicate, wholeCol, ignoreResizeTabs); + let obj = circuit.tryGrab(hand, duplicate, wholeCol, ignoreResizeTabs, alt); hand = obj.newHand; circuit = obj.newCircuit; diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index 79577e9a..a6d8ba0d 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", "i", + "0", "NeGate", "i", "-i", "√i", "√-i", "H", "X", "Y", "Z", "X^½", "X^⅓", "X^¼", "X^⅛", "X^⅟₁₆", "X^⅟₃₂", From 1653c6b6e261940a147a095444995e00dcf4f8f8 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 05:17:51 -0300 Subject: [PATCH 06/19] Fix breaking change on Travis end --- .travis.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8f40d6fa..965f2bbd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,8 @@ node_js: 5.1 install: npm install branches: only: master -before_script: - - export DISPLAY=:99.0 - - sh -e /etc/init.d/xvfb start +services: + - xvfb script: npm run test-travis addons: firefox: "latest" From 2d0e00af6b8ad0956fff2982638946f62d2d623c Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 19:43:34 -0300 Subject: [PATCH 07/19] Add parity controls - Add rawControls property to CircuitEvalContext - Add Util.popcnt - Add default output for Util.powerOfTwoness - Add Parity section to toolbox --- src/base/Util.js | 33 ++++- src/circuit/CircuitComputeUtil.js | 3 + src/circuit/CircuitDefinition.js | 20 ++- src/circuit/CircuitEvalContext.js | 6 + src/circuit/CircuitStats.js | 1 + src/circuit/Controls.js | 36 ++++- src/circuit/Gate.js | 15 ++- src/gates/AllGates.js | 8 ++ src/gates/Controls.js | 125 +++++++++++++++++- test/CircuitOperationTestUtil.js | 2 + test/base/Util.test.js | 21 +++ test/circuit/CircuitDefinition.test.js | 9 +- test/circuit/Serializer.test.js | 1 + test/gates/AllGates.test.js | 4 + test/gates/Controls.test.js | 85 ++++++++++++ .../gates/Impossible_UniversalNotGate.test.js | 1 + 16 files changed, 344 insertions(+), 26 deletions(-) diff --git a/src/base/Util.js b/src/base/Util.js index f35004ef..4c0f6f62 100644 --- a/src/base/Util.js +++ b/src/base/Util.js @@ -152,6 +152,24 @@ class Util { return p; } + /** + * Counts the number of set bits in an integer. + * + * @param {!int} i + * @returns {!int} + */ + static popcnt(i) { + if (i < 0) { + return Math.POSITIVE_INFINITY; + } + let t = 0; + while (i > 0) { + i &= i - 1; + t++; + } + return t; + } + /** * Determines how multiply-even a number is; how many times you can divide it by 2 before getting an odd result. * Odd numbers have 0 power-of-two-ness, multiples of 2 that aren't multiples of 4 have 1 power-of-two-ness, @@ -160,18 +178,19 @@ class Util { * Note that zero has infinite power-of-two-ness. * * @param {!int} i - * @returns {!int} + * @param {T=} zeroResult The value to return when i == 0. Defaults to positive infinity (because you can divide + * zero by two as many times as you want and still get an integer). + * @returns {T|!int} + * @template T */ - static powerOfTwoness(i) { + static powerOfTwoness(i, zeroResult=Math.POSITIVE_INFINITY) { if (i === 0) { - return Math.POSITIVE_INFINITY; + return zeroResult; } if (i < 0) { - return Util.powerOfTwoness(-i); + return Util.powerOfTwoness(-i, zeroResult); } - let lowMask = i ^ (i - 1); - let lowBit = i & lowMask; - return Math.round(Math.log2(lowBit)); + return Math.round(Math.log2(i & ~(i - 1))); } /** diff --git a/src/circuit/CircuitComputeUtil.js b/src/circuit/CircuitComputeUtil.js index 5ef3c8f5..b93af72a 100644 --- a/src/circuit/CircuitComputeUtil.js +++ b/src/circuit/CircuitComputeUtil.js @@ -133,6 +133,7 @@ function _extractStateStatsNeededByCircuitColumn( circuitDefinition.numWires, ctx.controls, ctx.controlsTexture, + ctx.controls, ctx.stateTrader, Util.mergeMaps( ctx.customContextFromGates, @@ -185,6 +186,7 @@ function _advanceStateWithCircuitDefinitionColumn( ctx.wireCount, ctx.controls, ctx.controlsTexture, + controls, trader, colContext); let mainCtx = new CircuitEvalContext( @@ -193,6 +195,7 @@ function _advanceStateWithCircuitDefinitionColumn( ctx.wireCount, controls, controlTex, + controls, trader, colContext); diff --git a/src/circuit/CircuitDefinition.js b/src/circuit/CircuitDefinition.js index 2b9201a3..5bbe9d5a 100644 --- a/src/circuit/CircuitDefinition.js +++ b/src/circuit/CircuitDefinition.js @@ -876,18 +876,30 @@ class CircuitDefinition { if (col < 0 || col >= this.columns.length) { return Controls.NONE; } - let result = Controls.NONE; let column = this.columns[col]; + let includeMask = 0; + let desireMask = 0; + let parityMask = 0; for (let i = 0; i < column.gates.length; i++) { let gate = column.gates[i]; if (gate !== undefined && this.gateAtLocIsDisabledReason(col, i) === undefined) { let bit = gate.controlBit(); - if (bit !== undefined) { - result = result.and(Controls.bit(i, bit)); + if (bit === 'parity') { + parityMask |= 1 << i; + } else if (bit !== undefined) { + includeMask |= 1 << i; + if (bit) { + desireMask |= 1 << i; + } } } } - return result; + if (parityMask !== 0) { + let parityBit = parityMask & ~(parityMask - 1); + desireMask |= parityBit; + includeMask |= parityBit; + } + return new Controls(includeMask, desireMask, parityMask); } /** diff --git a/src/circuit/CircuitEvalContext.js b/src/circuit/CircuitEvalContext.js index 357738d5..7583e5f1 100644 --- a/src/circuit/CircuitEvalContext.js +++ b/src/circuit/CircuitEvalContext.js @@ -26,6 +26,8 @@ class CircuitEvalContext { * @param {!int} wireCount * @param {!Controls} controls * @param {!WglTexture} controlsTexture + * @param {!Controls} rawControls The controls of the gate column, made available so that before/after operations + * can use this information (even though they are not themselves controlled). * @param {!WglTextureTrader} stateTrader * @param {!Map.} customContextFromGates */ @@ -34,6 +36,7 @@ class CircuitEvalContext { wireCount, controls, controlsTexture, + rawControls, stateTrader, customContextFromGates) { /** @type {!number} */ @@ -47,6 +50,8 @@ class CircuitEvalContext { this.wireCount = wireCount; /** @type {!Controls} */ this.controls = controls; + /** @type {!Controls} */ + this.rawControls = rawControls; /** @type {!WglTexture} */ this.controlsTexture = controlsTexture; /** @type {!WglTextureTrader} */ @@ -75,6 +80,7 @@ class CircuitEvalContext { this.wireCount, this.controls, this.controlsTexture, + this.rawControls, this.stateTrader, this.customContextFromGates); } diff --git a/src/circuit/CircuitStats.js b/src/circuit/CircuitStats.js index 80801fad..a886feff 100644 --- a/src/circuit/CircuitStats.js +++ b/src/circuit/CircuitStats.js @@ -344,6 +344,7 @@ class CircuitStats { numWires, Controls.NONE, controlTex, + Controls.NONE, stateTrader, new Map()), circuitDefinition, diff --git a/src/circuit/Controls.js b/src/circuit/Controls.js index c4a70924..aad76e26 100644 --- a/src/circuit/Controls.js +++ b/src/circuit/Controls.js @@ -25,17 +25,25 @@ class Controls { /** * @param {!int} inclusionMask. * @param {!int} desiredValueMask + * @param {!int=0} parityMask * @property {!int} inclusionMask. * @property {!int} desiredValueMask */ - constructor(inclusionMask, desiredValueMask) { + constructor(inclusionMask, desiredValueMask, parityMask=0) { if ((desiredValueMask & ~inclusionMask) !== 0) { throw new DetailedError("Desired un-included bits", {inclusionMask, desiredValueMask}); } + if (parityMask !== 0 && Util.popcnt(inclusionMask & parityMask) !== 1) { + throw new DetailedError("Exactly one parity bit must be in the inclusion mask", + {inclusionMask, parityMask}); + } + /** @type {!int} */ this.inclusionMask = inclusionMask; /** @type {!int} */ this.desiredValueMask = desiredValueMask; + /** @type {!int} */ + this.parityMask = parityMask; } /** @@ -57,7 +65,8 @@ class Controls { isEqualTo(other) { return other instanceof Controls && this.inclusionMask === other.inclusionMask && - this.desiredValueMask === other.desiredValueMask; + this.desiredValueMask === other.desiredValueMask && + this.parityMask === other.parityMask; } /** @@ -68,12 +77,20 @@ class Controls { return "No Controls"; } - return "Controls: ...__" + Seq.naturals(). - takeWhile(i => (1< (1< this.desiredValueFor(e)). map(e => e === undefined ? "_" : e ? "1" : "0"). reverse(). join(""); + if (this.parityMask !== 0) { + result += "\n parity: ...__" + range. + map(e => this.parityMask & (1 << e)). + map(e => e ? "1" : "_"). + reverse(). + join("") + } + return result; } /** @@ -113,9 +130,13 @@ class Controls { if ((other.desiredValueMask & this.inclusionMask) !== (this.desiredValueMask & other.inclusionMask)) { throw new DetailedError("Contradictory controls.", {"this": this, other}) } + if ((other.parityMask & this.inclusionMask) !== 0 || (this.parityMask & other.inclusionMask) !== 0) { + throw new DetailedError("Can't intersect parity controls.", {"this": this, other}) + } return new Controls( this.inclusionMask | other.inclusionMask, - this.desiredValueMask | other.desiredValueMask); + this.desiredValueMask | other.desiredValueMask, + this.parityMask | other.parityMask); } /** @@ -125,7 +146,8 @@ class Controls { shift(offset) { return new Controls( this.inclusionMask << offset, - this.desiredValueMask << offset) + this.desiredValueMask << offset, + this.parityMask << offset) } } diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index c27ea2d0..67c5cff1 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -152,9 +152,10 @@ class Gate { /** * Determines if this gate conditions or anti-conditions other operations or not. - * Note that 'False' means 'anti-control', not 'not a control'. Use undefined for 'not a control'. + * Note that 'False' means 'anti-control', not 'not a control'. Use undefined for 'not a control'. Also, + * this value may be set to "parity" to indicate a parity control. * Non-computational-basis controls also use this mechanism, but with before/after operations. - * @type {undefined|!boolean} + * @type {undefined|!string|!boolean} * @private */ this._controlBit = undefined; @@ -417,7 +418,7 @@ class Gate { } /** - * @returns {undefined|!boolean} + * @returns {undefined|!string|!boolean} */ controlBit() { return this._controlBit; @@ -827,6 +828,11 @@ class GateBuilder { } /** + * Indicates that the gate may temporarily, but not permanently, transform the system state. + * + * The gate may also be part of a pair that together permanently changes the state (e.g. an input gate with an + * addition gate), but the other gate in the pair will not be marked as having no effect. + * * @returns {!GateBuilder} */ promiseHasNoNetEffectOnStateVector() { @@ -858,7 +864,8 @@ class GateBuilder { /** * Sets meta-properties to indicate a gate is a control. - * @param {!boolean} bit: Whether gate is a control or anti-control. Use before/after operations for flexibility. + * @param {!boolean|!string} bit: Whether gate is a control (True), anti-control (False), or parity control + * ("parity"). Use before/after operations for flexibility. * @param {!boolean} guaranteedClassical Whether or not the control can be used to control permutations of classical * wires, even if placed on a coherent wire. * @returns {!GateBuilder} diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 5298f380..27544b78 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -261,6 +261,14 @@ Gates.TopToolboxGroups = [ Detectors.XDetector, Detectors.XDetectControlClear, ] }, + { + hint: "Parity", + gates: [ + Controls.ZParityControl, undefined, + Controls.YParityControl, undefined, + Controls.XParityControl, undefined, + ] + }, ]; /** @type {!Array}>} */ diff --git a/src/gates/Controls.js b/src/gates/Controls.js index 1b72690f..062f3dad 100644 --- a/src/gates/Controls.js +++ b/src/gates/Controls.js @@ -17,6 +17,10 @@ import {GatePainting} from "src/draw/GatePainting.js" import {GateShaders} from "src/circuit/GateShaders.js" import {HalfTurnGates} from "src/gates/HalfTurnGates.js" import {QuarterTurnGates} from "src/gates/QuarterTurnGates.js" +import {Config} from "src/Config.js" +import {ketArgs, ketShaderPermute} from "src/circuit/KetShaderUtil.js"; +import {WglArg} from "src/webgl/WglArg.js"; +import {Util} from "src/base/Util.js"; let Controls = {}; @@ -167,13 +171,132 @@ Controls.YControl = new GateBuilder(). }). gate; +const PARITY_SHADER = ketShaderPermute( + ` + uniform float parityMask; + `, + ` + float bitPos = 1.0; + float result = 0.5; + for (int i = 0; i < ${Config.MAX_WIRE_COUNT}; i++) { + float maskBit = mod(floor(parityMask/bitPos), 2.0); + float posBit = mod(floor(full_out_id/bitPos), 2.0); + float flip = maskBit * posBit; + result += flip; + bitPos *= 2.0; + } + return mod(result, 2.0) - 0.5;`, + 1); + +/** + * Applies a multi-target CNOT operation, merging the parities onto a single qubit (or reversing that process). + * + * Note that this method is invoked for each parity control, but only the last one in the column is supposed to + * perform the operation (or, when uncomputing, the first one). + * + * @param {!CircuitEvalContext} ctx + * @param {!boolean} order + */ +function parityGatherScatter(ctx, order) { + let c = ctx.rawControls; + let isLast = 2 << ctx.row > c.parityMask; + let isFirst = 1 << ctx.row === (c.parityMask & ~(c.parityMask - 1)); + if (order ? isLast : isFirst) { + ctx.applyOperation(PARITY_SHADER.withArgs( + ...ketArgs(ctx.withRow(Util.ceilLg2(c.parityMask & c.inclusionMask))), + WglArg.float('parityMask', c.parityMask) + )); + } +} + +/** + * @param {!string} name + * @returns {!function(args: !GateDrawParams)} + */ +function parityDrawer(name) { + return args => { + if (args.isInToolbox || args.isHighlighted) { + GatePainting.paintBackground(args); + GatePainting.paintOutline(args); + } + let center = args.rect.paddedBy(-10); + args.painter.fillRect(center); + args.painter.strokeRect(center); + args.painter.fillRect(center.paddedBy(-4).skipBottom(-6).skipTop(-6)); + args.painter.printLine(name, center, 0.5, undefined, undefined, undefined, 0); + args.painter.printLine('par', center, 0.5, 'red', 10, undefined, 1); + } +} + +Controls.XParityControl = new GateBuilder(). + setSerializedIdAndSymbol("xpar"). + setTitle("Parity Control (X)"). + setBlurb("Includes a qubit's X observable in the column parity control.\n" + + "Gates in the same column only apply if an odd number of parity controls are satisfied."). + setActualEffectToUpdateFunc(() => {}). + promiseEffectIsStable(). + promiseEffectIsUnitary(). + promiseHasNoNetEffectOnStateVector(). + markAsControlExpecting('parity'). + setSetupCleanupEffectToUpdateFunc( + ctx => { + HalfTurnGates.H.customOperation(ctx); + parityGatherScatter(ctx, true); + }, + ctx => { + parityGatherScatter(ctx, false); + HalfTurnGates.H.customOperation(ctx); + }). + setDrawer(parityDrawer('X')). + gate; + +Controls.YParityControl = new GateBuilder(). + setSerializedIdAndSymbol("ypar"). + setTitle("Parity Control (Y)"). + setBlurb("Includes a qubit's Y observable in the column parity control.\n" + + "Gates in the same column only apply if an odd number of parity controls are satisfied."). + setActualEffectToUpdateFunc(() => {}). + promiseEffectIsStable(). + promiseEffectIsUnitary(). + promiseHasNoNetEffectOnStateVector(). + markAsControlExpecting('parity'). + setSetupCleanupEffectToUpdateFunc( + ctx => { + GateShaders.applyMatrixOperation(ctx, QuarterTurnGates.SqrtXForward._knownMatrix); + parityGatherScatter(ctx, true); + }, + ctx => { + parityGatherScatter(ctx, false); + GateShaders.applyMatrixOperation(ctx, QuarterTurnGates.SqrtXBackward._knownMatrix); + }). + setDrawer(parityDrawer('Y')). + gate; + +Controls.ZParityControl = new GateBuilder(). + setSerializedIdAndSymbol("zpar"). + setTitle("Parity Control (Z)"). + setBlurb("Includes a qubit's Z observable in the column parity control.\n" + + "Gates in the same column only apply if an odd number of parity controls are satisfied."). + promiseHasNoNetEffectOnStateVector(). + markAsControlExpecting('parity'). + setSetupCleanupEffectToUpdateFunc( + ctx => parityGatherScatter(ctx, true), + ctx => parityGatherScatter(ctx, false)). + setActualEffectToUpdateFunc(() => {}). + promiseEffectIsUnitary(). + setDrawer(parityDrawer('Z')). + gate; + Controls.all = [ Controls.Control, Controls.AntiControl, Controls.XAntiControl, Controls.XControl, Controls.YAntiControl, - Controls.YControl + Controls.YControl, + Controls.XParityControl, + Controls.YParityControl, + Controls.ZParityControl, ]; export {Controls} diff --git a/test/CircuitOperationTestUtil.js b/test/CircuitOperationTestUtil.js index ff0c8771..220e4524 100644 --- a/test/CircuitOperationTestUtil.js +++ b/test/CircuitOperationTestUtil.js @@ -208,6 +208,7 @@ function assertThatCircuitMutationActsLikeMatrix_single(updateAction, matrix, fo wireCount, controls, controlsTexture, + controls, trader, new Map()); updateAction(ctx); @@ -255,6 +256,7 @@ function assertThatCircuitUpdateActsLikePermutation(wireCount, updateAction, per wireCount, Controls.NONE, controlsTexture, + Controls.NONE, trader, new Map()); updateAction(ctx); diff --git a/test/base/Util.test.js b/test/base/Util.test.js index 3ba641c5..10ae3e64 100644 --- a/test/base/Util.test.js +++ b/test/base/Util.test.js @@ -54,6 +54,24 @@ suite.test("ceilLg2", () => { assertThat(Util.ceilLg2((1<<20)+1)).isEqualTo(21); }); +suite.test("popcnt", () => { + assertThat(Util.popcnt(-2)).isEqualTo(Math.POSITIVE_INFINITY); + assertThat(Util.popcnt(-1)).isEqualTo(Math.POSITIVE_INFINITY); + assertThat(Util.popcnt(0)).isEqualTo(0); + assertThat(Util.popcnt(1)).isEqualTo(1); + assertThat(Util.popcnt(2)).isEqualTo(1); + assertThat(Util.popcnt(3)).isEqualTo(2); + assertThat(Util.popcnt(4)).isEqualTo(1); + assertThat(Util.popcnt(5)).isEqualTo(2); + assertThat(Util.popcnt(6)).isEqualTo(2); + assertThat(Util.popcnt(7)).isEqualTo(3); + assertThat(Util.popcnt(8)).isEqualTo(1); + assertThat(Util.popcnt(9)).isEqualTo(2); + assertThat(Util.popcnt((1<<20)-1)).isEqualTo(20); + assertThat(Util.popcnt((1<<20))).isEqualTo(1); + assertThat(Util.popcnt((1<<20)+1)).isEqualTo(2); +}); + suite.test("floorLg2", () => { assertThat(Util.floorLg2(0)).isEqualTo(0); assertThat(Util.floorLg2(1)).isEqualTo(0); @@ -105,7 +123,10 @@ suite.test("ceilingPowerOf2", () => { suite.test("powerOfTwoness", () => { assertThat(Util.powerOfTwoness(-2)).isEqualTo(1); assertThat(Util.powerOfTwoness(-1)).isEqualTo(0); + assertThat(Util.powerOfTwoness(-1, 'z')).isEqualTo(0); assertThat(Util.powerOfTwoness(0)).isEqualTo(Math.POSITIVE_INFINITY); + assertThat(Util.powerOfTwoness(0, 'z')).isEqualTo('z'); + assertThat(Util.powerOfTwoness(1, 'z')).isEqualTo(0); assertThat(Util.powerOfTwoness(1)).isEqualTo(0); assertThat(Util.powerOfTwoness(2)).isEqualTo(1); assertThat(Util.powerOfTwoness(3)).isEqualTo(0); diff --git a/test/circuit/CircuitDefinition.test.js b/test/circuit/CircuitDefinition.test.js index 0b7d5cb6..bab7788b 100644 --- a/test/circuit/CircuitDefinition.test.js +++ b/test/circuit/CircuitDefinition.test.js @@ -583,14 +583,17 @@ suite.test("colControls", () => { assertThat(c.colControls(17)).isEqualTo(Controls.bit(0, true)); assertThat(c.colControls(102)).isEqualTo(Controls.NONE); - let c2 = circuit(`--●○○- - -X○●s- - ---s●-`); + let c2 = circuit(`--●○○-P- + -X○●s-P- + ---s●-X-`, ['P', Gates.Controls.ZParityControl]); assertThat(c2.colControls(0)).isEqualTo(Controls.NONE); assertThat(c2.colControls(1)).isEqualTo(Controls.NONE); assertThat(c2.colControls(2)).isEqualTo(new Controls(3, 1)); assertThat(c2.colControls(3)).isEqualTo(new Controls(3, 2)); assertThat(c2.colControls(4)).isEqualTo(new Controls(5, 4)); + assertThat(c2.colControls(5)).isEqualTo(Controls.NONE); + assertThat(c2.colControls(6)).isEqualTo(new Controls(1, 1, 3)); + assertThat(c2.colControls(7)).isEqualTo(Controls.NONE); }); suite.test("locIsControlWireStarter", () => { diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index a6d8ba0d..9ce23371 100644 --- a/test/circuit/Serializer.test.js +++ b/test/circuit/Serializer.test.js @@ -178,6 +178,7 @@ suite.test("roundTrip_circuitDefinition", () => { const IDS_THAT_SHOULD_BE_KNOWN = [ "•", "◦", "⊕", "⊖", "⊗", "(/)", + "xpar", "ypar", "zpar", "|0⟩⟨0|", "|1⟩⟨1|", "|+⟩⟨+|", "|-⟩⟨-|", "|X⟩⟨X|", "|/⟩⟨/|", "Measure", "XDetector", "YDetector", "ZDetector", diff --git a/test/gates/AllGates.test.js b/test/gates/AllGates.test.js index a59c45df..e6fb9563 100644 --- a/test/gates/AllGates.test.js +++ b/test/gates/AllGates.test.js @@ -53,6 +53,7 @@ let reconstructMatrixFromGateCustomOperation = (gate, time) => { numQubits, Controls.NONE, control, + Controls.NONE, trader, new Map()); gate.customOperation(ctx); @@ -184,6 +185,9 @@ suite.test("knownDoNothingGateFamilies", () => { // Operation modifiers technically do things, but we assign the effects to the operation itself. '•', '◦', + 'xpar', + 'ypar', + 'zpar', 'inputA1', 'inputB1', 'inputR1', diff --git a/test/gates/Controls.test.js b/test/gates/Controls.test.js index e160d032..1e0dac4e 100644 --- a/test/gates/Controls.test.js +++ b/test/gates/Controls.test.js @@ -21,6 +21,11 @@ import {Gates} from "src/gates/AllGates.js" import {Complex} from "src/math/Complex.js" import {Matrix} from "src/math/Matrix.js" +import {Util} from "src/base/Util.js" +import {advanceStateWithCircuit} from "src/circuit/CircuitComputeUtil.js"; +import { + assertThatCircuitUpdateActsLikeMatrix, +} from "test/CircuitOperationTestUtil.js"; let suite = new Suite("Gates.Controls"); @@ -105,3 +110,83 @@ suite.testUsingWebGL('Y-control', () => { assertControlOverlapState(c, 0, [1, i]); assertControlOverlapState(c, 1, [-1, i]); }); + +suite.testUsingWebGL('Z-parity', () => { + assertThatCircuitUpdateActsLikeMatrix( + ctx => advanceStateWithCircuit( + ctx, + new CircuitDefinition(2, [new GateColumn([ + Gates.Controls.ZParityControl, + Gates.HalfTurns.Z + ])]), + false), + Matrix.generateDiagonal(1 << 2, i => i === 3 ? -1 : 1)); + + assertThatCircuitUpdateActsLikeMatrix( + ctx => advanceStateWithCircuit( + ctx, + new CircuitDefinition(4, [new GateColumn([ + Gates.Controls.ZParityControl, + undefined, + Gates.Controls.ZParityControl, + Gates.HalfTurns.Z]) + ]), + false), + Matrix.generateDiagonal(1 << 4, i => Util.popcnt(i & 5) % 2 === 1 && ((i & 8) !== 0) ? -1 : 1)); +}); + +suite.testUsingWebGL('X-parity', () => { + assertThatCircuitUpdateActsLikeMatrix( + ctx => advanceStateWithCircuit( + ctx, + new CircuitDefinition(4, [ + new GateColumn([ + Gates.HalfTurns.H, + Gates.HalfTurns.H, + Gates.HalfTurns.H, + undefined, + ]), + new GateColumn([ + Gates.Controls.XParityControl, + Gates.Controls.XParityControl, + Gates.Controls.XParityControl, + Gates.HalfTurns.Z + ]), + new GateColumn([ + Gates.HalfTurns.H, + Gates.HalfTurns.H, + Gates.HalfTurns.H, + undefined, + ]), + ]), + false), + Matrix.generateDiagonal(1 << 4, i => Util.popcnt(i & 7) % 2 === 1 && ((i & 8) !== 0) ? -1 : 1)); +}); + +suite.testUsingWebGL('X-parity', () => { + assertThatCircuitUpdateActsLikeMatrix( + ctx => advanceStateWithCircuit( + ctx, + new CircuitDefinition(4, [ + new GateColumn([ + Gates.QuarterTurns.SqrtXBackward, + Gates.QuarterTurns.SqrtXBackward, + Gates.QuarterTurns.SqrtXBackward, + undefined, + ]), + new GateColumn([ + Gates.Controls.YParityControl, + Gates.Controls.YParityControl, + Gates.Controls.YParityControl, + Gates.HalfTurns.Z, + ]), + new GateColumn([ + Gates.QuarterTurns.SqrtXForward, + Gates.QuarterTurns.SqrtXForward, + Gates.QuarterTurns.SqrtXForward, + undefined, + ]), + ]), + false), + Matrix.generateDiagonal(1 << 4, i => Util.popcnt(i & 7) % 2 === 1 && ((i & 8) !== 0) ? -1 : 1)); +}); diff --git a/test/gates/Impossible_UniversalNotGate.test.js b/test/gates/Impossible_UniversalNotGate.test.js index d6df27b5..fbd359a9 100644 --- a/test/gates/Impossible_UniversalNotGate.test.js +++ b/test/gates/Impossible_UniversalNotGate.test.js @@ -38,6 +38,7 @@ suite.testUsingWebGL('universalNot', () => { 2, control, controlTex, + control, trader, new Map()); try { From ac4bd62486e74e8a22f8f142d29ae561b70d3750 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 19:45:11 -0300 Subject: [PATCH 08/19] Match post-selection labels to wire input labels --- src/gates/PostSelectionGates.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gates/PostSelectionGates.js b/src/gates/PostSelectionGates.js index 7ca534d0..e8061753 100644 --- a/src/gates/PostSelectionGates.js +++ b/src/gates/PostSelectionGates.js @@ -56,7 +56,7 @@ PostSelectionGates.PostSelectOn = new GateBuilder(). /** @type {!Gate} */ PostSelectionGates.PostSelectAntiX = new GateBuilder(). setSerializedId("|+⟩⟨+|"). // The +/- drawing convention was switched, but the serialized id must stay the same. - setSymbol("|⊖⟩⟨⊖|"). + setSymbol("|+⟩⟨+|"). setTitle("Postselect X-Off"). setBlurb("Keeps ON+OFF states, discards/retries ON-OFF states."). setDrawer(POST_SELECT_DRAWER). @@ -67,7 +67,7 @@ PostSelectionGates.PostSelectAntiX = new GateBuilder(). PostSelectionGates.PostSelectX = new GateBuilder(). setAlternate(PostSelectionGates.PostSelectAntiX). setSerializedId("|-⟩⟨-|"). // The +/- drawing convention was switched, but the serialized id must stay the same. - setSymbol("|⊕⟩⟨⊕|"). + setSymbol("|-⟩⟨-|"). setTitle("Postselect X-On"). setBlurb("Keeps ON-OFF states, discards/retries ON+OFF states."). setDrawer(POST_SELECT_DRAWER). @@ -77,7 +77,7 @@ PostSelectionGates.PostSelectX = new GateBuilder(). /** @type {!Gate} */ PostSelectionGates.PostSelectAntiY = new GateBuilder(). setSerializedId("|X⟩⟨X|"). // The cross/slash convention was switched, but the serialized id must stay the same. - setSymbol("|⊘⟩⟨⊘|"). + setSymbol("|i⟩⟨i|"). setTitle("Postselect Y-Off"). setBlurb("Keeps ON+iOFF states, discards ON-iOFF states."). setDrawer(POST_SELECT_DRAWER). @@ -88,7 +88,7 @@ PostSelectionGates.PostSelectAntiY = new GateBuilder(). PostSelectionGates.PostSelectY = new GateBuilder(). setAlternate(PostSelectionGates.PostSelectAntiY). setSerializedId("|/⟩⟨/|"). // The cross/slash convention was switched, but the serialized id must stay the same. - setSymbol("|⊗⟩⟨⊗|"). + setSymbol("|-i⟩⟨-i|"). setTitle("Postselect Y-On"). setBlurb("Keeps ON-iOFF states, discards/retries ON+iOFF states."). setDrawer(POST_SELECT_DRAWER). From 8e28c2a5b014615771afb9ab23630cffe0b0556b Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 20:04:02 -0300 Subject: [PATCH 09/19] Clear wire initial state when middle clicking it --- src/circuit/CircuitDefinition.js | 6 +++++- src/main.js | 23 ++++++++++++++++------- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/circuit/CircuitDefinition.js b/src/circuit/CircuitDefinition.js index 5bbe9d5a..fdfa7839 100644 --- a/src/circuit/CircuitDefinition.js +++ b/src/circuit/CircuitDefinition.js @@ -142,13 +142,17 @@ class CircuitDefinition { /** * @param {!int} wire + * @param {!int=} newStateIndex= * @returns {!CircuitDefinition} */ - withSwitchedInitialStateOn(wire) { + withSwitchedInitialStateOn(wire, newStateIndex=undefined) { let m = new Map([...this.customInitialValues.entries()]); let v = m.get(wire); let cycle = [...INITIAL_STATES_TO_GATES.keys()]; let newVal = cycle[(cycle.indexOf(v) + 1) % cycle.length]; + if (newStateIndex !== undefined) { + newVal = newStateIndex; + } if (newVal === undefined) { m.delete(wire); } else { diff --git a/src/main.js b/src/main.js index e34ae11c..ee1e6955 100644 --- a/src/main.js +++ b/src/main.js @@ -247,13 +247,22 @@ canvasDiv.addEventListener('mousedown', ev => { if (!isMiddleClicking(ev)) { return; } - let newHand = displayed.get().hand.withPos(eventPosRelativeTo(ev, canvas)); - let newInspector = syncArea(displayed.get()). - withHand(newHand). - afterGrabbing(false, false, true, false). // Grab the gate. - withHand(newHand). // Lose the gate. - afterTidyingUp(). - withJustEnoughWires(newHand, 0); + let cur = syncArea(displayed.get()); + let initOver = cur.tryGetHandOverButtonKey(); + let newHand = cur.hand.withPos(eventPosRelativeTo(ev, canvas)); + let newInspector; + if (initOver !== undefined && initOver.startsWith('wire-init-')) { + let newCircuit = cur.displayedCircuit.circuitDefinition.withSwitchedInitialStateOn( + parseInt(initOver.substr(10)), 0); + newInspector = cur.withCircuitDefinition(newCircuit).withHand(newHand).afterTidyingUp(); + } else { + newInspector = cur. + withHand(newHand). + afterGrabbing(false, false, true, false). // Grab the gate. + withHand(newHand). // Lose the gate. + afterTidyingUp(). + withJustEnoughWires(newHand, 0); + } if (!displayed.get().isEqualTo(newInspector)) { revision.commit(newInspector.snapshot()); ev.preventDefault(); From b7add27be94471806a3ac26068c1ae8cad24530e Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 12 Aug 2019 20:08:15 -0300 Subject: [PATCH 10/19] Fix transposition of displayed values in single qubit density matrix display --- src/gates/DensityMatrixDisplay.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gates/DensityMatrixDisplay.js b/src/gates/DensityMatrixDisplay.js index 9bba9214..78ffb2c3 100644 --- a/src/gates/DensityMatrixDisplay.js +++ b/src/gates/DensityMatrixDisplay.js @@ -171,7 +171,7 @@ function singleDensityMatrixDisplayMaker(builder) { markAsDrawerNeedsSingleQubitDensityStats(). setDrawer(GatePainting.makeDisplayDrawer(args => { let {col, row} = args.positionInCircuit; - let ρ = args.stats.qubitDensityMatrix(col, row); + let ρ = args.stats.qubitDensityMatrix(col, row).transpose(); MathPainter.paintDensityMatrix(args.painter, ρ, args.rect, args.focusPoints); })); } From f9aee08dde6b7d7030bbce166f47bb1cf994a8f3 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Tue, 13 Aug 2019 12:29:14 -0300 Subject: [PATCH 11/19] Work around user-reported error where getProgramInfoLog returned null --- src/webgl/WglShader.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webgl/WglShader.js b/src/webgl/WglShader.js index 4268ef4d..c68ae8eb 100644 --- a/src/webgl/WglShader.js +++ b/src/webgl/WglShader.js @@ -150,7 +150,9 @@ class WglCompiledShader { gl.attachShader(program, glFragmentShader); gl.linkProgram(program); - let warnings = gl.getProgramInfoLog(program).trim(); + // Note: MDN says the result of getProgramInfoLog is always a DOMString, but a user reported an + // error where it returned null. So now we fallback to the empty string when getting a falsy value. + let warnings = (gl.getProgramInfoLog(program) || '').trim(); if (warnings !== '' && warnings !== '\0' && // [happened in Ubuntu with NVIDIA GK107GL] Config.SUPPRESSED_GLSL_WARNING_PATTERNS.every(e => !e.test(warnings))) { From ef4c17c3e4c2c8042acebf1e0ed7901333cf5fc1 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Thu, 15 Aug 2019 17:40:27 -0300 Subject: [PATCH 12/19] Fix X and Y parity controls not being blocked on measured qubits --- src/circuit/Gate.js | 7 ++++--- src/gates/Controls.js | 2 -- test/gates/AllGates.test.js | 4 +--- test/gates/Controls.test.js | 22 ++++++++++++++++++++++ 4 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 67c5cff1..1413e942 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -828,10 +828,11 @@ class GateBuilder { } /** - * Indicates that the gate may temporarily, but not permanently, transform the system state. + * Indicates that the gate can be skipped over when computing the system state. * - * The gate may also be part of a pair that together permanently changes the state (e.g. an input gate with an - * addition gate), but the other gate in the pair will not be marked as having no effect. + * Note that Input gates and Z controls meet this definition, because their effects happen via other gates + * conditioning on them. X and Y controls don't meet the definition because they have to perform a temporary + * basis change which counts as a change. * * @returns {!GateBuilder} */ diff --git a/src/gates/Controls.js b/src/gates/Controls.js index 062f3dad..3c4539f0 100644 --- a/src/gates/Controls.js +++ b/src/gates/Controls.js @@ -236,7 +236,6 @@ Controls.XParityControl = new GateBuilder(). setActualEffectToUpdateFunc(() => {}). promiseEffectIsStable(). promiseEffectIsUnitary(). - promiseHasNoNetEffectOnStateVector(). markAsControlExpecting('parity'). setSetupCleanupEffectToUpdateFunc( ctx => { @@ -258,7 +257,6 @@ Controls.YParityControl = new GateBuilder(). setActualEffectToUpdateFunc(() => {}). promiseEffectIsStable(). promiseEffectIsUnitary(). - promiseHasNoNetEffectOnStateVector(). markAsControlExpecting('parity'). setSetupCleanupEffectToUpdateFunc( ctx => { diff --git a/test/gates/AllGates.test.js b/test/gates/AllGates.test.js index e6fb9563..265a8f4d 100644 --- a/test/gates/AllGates.test.js +++ b/test/gates/AllGates.test.js @@ -182,11 +182,9 @@ suite.test("knownDoNothingGateFamilies", () => { assertThat(doNothingFamilies).isEqualTo(new Set([ // Measurement technically does something, but internally it's deferred and handled special almost everywhere. 'Measure', - // Operation modifiers technically do things, but we assign the effects to the operation itself. + // Z basis operation modifiers technically do things, but we assign the effects to the operation itself. '•', '◦', - 'xpar', - 'ypar', 'zpar', 'inputA1', 'inputB1', diff --git a/test/gates/Controls.test.js b/test/gates/Controls.test.js index 1e0dac4e..3af76bad 100644 --- a/test/gates/Controls.test.js +++ b/test/gates/Controls.test.js @@ -190,3 +190,25 @@ suite.testUsingWebGL('X-parity', () => { false), Matrix.generateDiagonal(1 << 4, i => Util.popcnt(i & 7) % 2 === 1 && ((i & 8) !== 0) ? -1 : 1)); }); + +suite.test('xyParityControlsDisabledByMeasurement', () => { + let c = CircuitDefinition.fromTextDiagram( + new Map([ + ['M', Gates.Special.Measurement], + ['H', Gates.HalfTurns.H], + ['x', Gates.Controls.XParityControl], + ['y', Gates.Controls.YParityControl], + ['z', Gates.Controls.ZParityControl], + ['-', undefined], + ]), + `-x-M-x- + -y-M-y- + -z-M-z- + -H---H-`); + assertThat(c.gateAtLocIsDisabledReason(1, 0)).isEqualTo(undefined); + assertThat(c.gateAtLocIsDisabledReason(1, 1)).isEqualTo(undefined); + assertThat(c.gateAtLocIsDisabledReason(1, 2)).isEqualTo(undefined); + assertThat(c.gateAtLocIsDisabledReason(5, 0)).isNotEqualTo(undefined); + assertThat(c.gateAtLocIsDisabledReason(5, 1)).isNotEqualTo(undefined); + assertThat(c.gateAtLocIsDisabledReason(5, 2)).isEqualTo(undefined); +}); From 4f5453830956320c1263986540772b7d5501a4a0 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sun, 8 Sep 2019 23:28:53 -0700 Subject: [PATCH 13/19] Add customizable formula gates - Add Formulaic toolbox section - Add Complex.cos/sin/tan - Add Gate._withParamRecomputeFunc - Allow Gate.param to be non-integer - Add GateDrawParams.hand - GatePainting.paintGateSymbol has allowExponent parameter - Add X/Y/Z^f(t) and Rx/y/z(f(t)) gates - Tidy up after clicking gate button to edit inspector - Add hand.isHoldingSomething - Don't draw gate buttons when dragging --- src/circuit/Gate.js | 30 ++- src/circuit/Serializer.js | 3 - src/draw/GateDrawParams.js | 4 + src/draw/GatePainting.js | 35 ++- src/gates/AllGates.js | 18 +- src/gates/ExponentiatingGates.js | 2 +- src/gates/InputGates.js | 7 + src/gates/ParametrizedRotationGates.js | 215 +++++++++++++++++++ src/main.js | 10 +- src/math/Complex.js | 143 ++++++++---- src/ui/DisplayedCircuit.js | 1 + src/ui/DisplayedInspector.js | 14 +- src/ui/DisplayedToolbox.js | 15 +- src/ui/Hand.js | 7 + test/circuit/Serializer.test.js | 2 + test/gates/AllGates.test.js | 18 +- test/gates/ParametrizedRotationGates.test.js | 91 ++++++++ test/math/Complex.test.js | 6 + 18 files changed, 530 insertions(+), 91 deletions(-) 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))); +}); From 7f461a9bdec5640fcaec386e60247045f943ebf0 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sun, 8 Sep 2019 23:29:17 -0700 Subject: [PATCH 14/19] All hints hide when circuit contains any gates --- src/ui/DisplayedInspector.js | 74 +++++++----------------------------- 1 file changed, 13 insertions(+), 61 deletions(-) diff --git a/src/ui/DisplayedInspector.js b/src/ui/DisplayedInspector.js index b6449b4c..1fc15a22 100644 --- a/src/ui/DisplayedInspector.js +++ b/src/ui/DisplayedInspector.js @@ -321,33 +321,12 @@ class DisplayedInspector { this._drawHint_useControls(painter); } - /** - * @returns {!number} - * @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) { - return 0; - } - - let handPosY = this.hand.pos === undefined ? Infinity : this.hand.pos.y; - return gatesInCircuit === 0 ? (handPosY - 125)/25 : - gatesInPlay === 2 ? (150 - handPosY)/25 : - 1.0; - } - /** * @param {!Painter} painter * @private */ _drawHint_watchOutputsChange(painter) { - let visibilityFactor = this._watchOutputsChangeVisibility(); + let visibilityFactor = this._hintVisibility(); if (visibilityFactor <= 0) { return; } @@ -382,20 +361,22 @@ class DisplayedInspector { painter.ctx.restore(); } + _hintVisibility() { + if (this.displayedCircuit.circuitDefinition.columns.length > 0) { + return 0; + } + return this.hand.pos === undefined || !this.hand.isBusy() ? 1.0 : + this.hand.heldGate !== undefined && this.hand.heldGate.isControl() ? 1.0 : + (150-this.hand.pos.y)/50; + } + /** * @param {!Painter} painter * @private */ _drawHint_dragGatesOntoCircuit(painter) { - if (this.displayedCircuit.circuitDefinition.hasNonControlGates()) { - return; - } - - let visibilityFactor = - this.hand.pos === undefined || !this.hand.isBusy() ? 1.0 : - this.hand.heldGate !== undefined && this.hand.heldGate.isControl() ? 1.0 : - (150-this.hand.pos.y)/50; + let visibilityFactor = this._hintVisibility(); if (visibilityFactor <= 0) { return; } @@ -404,7 +385,7 @@ class DisplayedInspector { painter.ctx.globalAlpha *= Math.min(1, visibilityFactor); painter.ctx.save(); - painter.ctx.translate(70, 190); + painter.ctx.translate(130, 195); painter.ctx.rotate(Math.PI * 0.05); painter.ctx.fillStyle = 'red'; painter.ctx.font = '16px sans-serif'; @@ -428,41 +409,12 @@ class DisplayedInspector { painter.ctx.restore(); } - /** - * @returns {!number} - * @private - */ - _useControlsHintVisibility() { - let circ = this.displayedCircuit.circuitDefinition; - 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; - } - - if (gatesInCircuit === 1 && gatesInPlay === 1 && !this.displayedCircuit.isBeingEdited()) { - return 1; - } - - if (gatesInCircuit === 1 && gatesInPlay === 2 && this.displayedCircuit.isBeingEdited()) { - return (150-this.hand.pos.y)/50; - } - - return 0; - } - /** * @param {!Painter} painter * @private */ _drawHint_useControls(painter) { - let visibilityFactor = this._useControlsHintVisibility(); + let visibilityFactor = this._hintVisibility(); if (visibilityFactor <= 0) { return; } From 966318cbb6e8b90efd3ed3670e5ea276aa1fca83 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sun, 8 Sep 2019 23:44:43 -0700 Subject: [PATCH 15/19] Polish some phase text - Hidden phase -> Global phase - Phase r+i -> Phase degs --- html/forge.partial.html | 2 +- src/draw/WidgetPainter.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/html/forge.partial.html b/html/forge.partial.html index 6db612c3..6c9f7bf1 100644 --- a/html/forge.partial.html +++ b/html/forge.partial.html @@ -26,7 +26,7 @@ axis
° angle
- ° phase + ° global phase diff --git a/src/draw/WidgetPainter.js b/src/draw/WidgetPainter.js index 9dad67e4..ba587e4c 100644 --- a/src/draw/WidgetPainter.js +++ b/src/draw/WidgetPainter.js @@ -47,7 +47,8 @@ class WidgetPainter { return "doesn't affect " + inputDescription; } } else if (Seq.range(n).every(r => r === c || col[r].isEqualTo(0))) { - return "phases " + inputDescription + " by " + col[c].toString(format); + let degs = col[c].ln().imag * 180/Math.PI; + return "phases " + inputDescription + " by " + format.formatFloat(degs) + "°"; } let outputDescription = new Seq(col). mapWithIndex((e, c) => WidgetPainter.describeKet(b, c, e, format)). @@ -146,7 +147,7 @@ class WidgetPainter { `rotates: ${format.formatFloat(angle * 180 / Math.PI)}°`, `around: ${WidgetPainter.describeAxis(axis, format)}`, '', - `hidden phase: exp(${format.formatFloat(phase * 180 / Math.PI)}°i)`, + `global phase: exp(${format.formatFloat(phase * 180 / Math.PI)}°i)`, '' ]).join('\n'); pushRect(painter.printParagraph( From a4d1b99673b5e8a5f257ba4f980baf041417fd1c Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sun, 8 Sep 2019 23:54:07 -0700 Subject: [PATCH 16/19] Note decimal value in display tooltips - Drop Sampling display from toolbox --- src/draw/MathPainter.js | 4 ++-- src/gates/AllGates.js | 2 +- src/gates/AmplitudeDisplay.js | 4 ++-- src/gates/ProbabilityDisplay.js | 2 +- src/ui/DisplayedCircuit.js | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/draw/MathPainter.js b/src/draw/MathPainter.js index 26521b6a..54823876 100644 --- a/src/draw/MathPainter.js +++ b/src/draw/MathPainter.js @@ -622,8 +622,8 @@ class MathPainter { let n = Math.round(Math.log2(numRows)); MathPainter.paintMatrixTooltip(painter, matrix, drawArea, focusPoints, (c, r) => c === r ? - `Probability of |${Util.bin(c, n)}⟩` : - `Coupling of |${Util.bin(r, n)}⟩ to ⟨${Util.bin(c, n)}|`, + `Probability of |${Util.bin(c, n)}⟩ (decimal ${c})` : + `Coupling of |${Util.bin(r, n)}⟩ to ⟨${Util.bin(c, n)}| (decimal ${r} to ${c})`, (c, r, v) => c === r ? (matrix.cell(c, r).real*100).toFixed(4) + "%" : matrix.cell(c, r).toString(new Format(false, 0, 6, ", "))); diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 190b4b10..563758ca 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -198,7 +198,7 @@ Gates.TopToolboxGroups = [ { hint: "Displays", gates: [ - SampleDisplayFamily.ofSize(3), undefined, + undefined, undefined, DensityMatrixDisplayFamily.ofSize(1), BlochSphereDisplay, ProbabilityDisplayFamily.ofSize(1), AmplitudeDisplayFamily.ofSize(2) ] diff --git a/src/gates/AmplitudeDisplay.js b/src/gates/AmplitudeDisplay.js index 24a1c777..862166c9 100644 --- a/src/gates/AmplitudeDisplay.js +++ b/src/gates/AmplitudeDisplay.js @@ -261,12 +261,12 @@ const AMPLITUDE_DRAWER_FROM_CUSTOM_STATS = GatePainting.makeDisplayDrawer(args = let forceSign = v => (v >= 0 ? '+' : '') + v.toFixed(2); if (isIncoherent) { MathPainter.paintMatrixTooltip(args.painter, matrix, drawRect, args.focusPoints, - (c, r) => `Chance of |${Util.bin(r*matrix.width() + c, args.gate.height)}⟩ [amplitude not defined]`, + (c, r) => `Chance of |${Util.bin(r*matrix.width() + c, args.gate.height)}⟩ (decimal ${r*matrix.width() + c}) [amplitude not defined]`, (c, r, v) => `raw: ${(v.norm2()*100).toFixed(4)}%, log: ${(Math.log10(v.norm2())*10).toFixed(1)} dB`, (c, r, v) => '[entangled with other qubits]'); } else { MathPainter.paintMatrixTooltip(args.painter, matrix, drawRect, args.focusPoints, - (c, r) => `Amplitude of |${Util.bin(r*matrix.width() + c, args.gate.height)}⟩`, + (c, r) => `Amplitude of |${Util.bin(r*matrix.width() + c, args.gate.height)}⟩ (decimal ${r*matrix.width() + c})`, (c, r, v) => 'val:' + v.toString(new Format(false, 0, 5, ", ")), (c, r, v) => `mag²:${(v.norm2()*100).toFixed(4)}%, phase:${forceSign(v.phase() * 180 / Math.PI)}°`); if (phaseLockIndex !== undefined) { diff --git a/src/gates/ProbabilityDisplay.js b/src/gates/ProbabilityDisplay.js index 7866c771..cc47b9cb 100644 --- a/src/gates/ProbabilityDisplay.js +++ b/src/gates/ProbabilityDisplay.js @@ -207,7 +207,7 @@ function _paintMultiProbabilityDisplay_tooltips(args) { painter, x + w, y + k * d, - `Chance of |${Util.bin(k, args.gate.height)}⟩ if measured`, + `Chance of |${Util.bin(k, args.gate.height)}⟩ (decimal ${k}) if measured`, 'raw: ' + (p * 100).toFixed(4) + "%", 'log: ' + (Math.log10(p) * 10).toFixed(1) + " dB"); } diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index d643045a..5b3792fd 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -1341,7 +1341,7 @@ class DisplayedCircuit { Config.SUPERPOSITION_BACK_COLOR); let forceSign = v => (v >= 0 ? '+' : '') + v.toFixed(2); MathPainter.paintMatrixTooltip(painter, amplitudeGrid, gridRect, hand.hoverPoints(), - (c, r) => `Amplitude of |${Util.bin(r*amplitudeGrid.width() + c, numWire)}⟩`, + (c, r) => `Amplitude of |${Util.bin(r*amplitudeGrid.width() + c, numWire)}⟩ (decimal ${r*amplitudeGrid.width() + c})`, (c, r, v) => 'val:' + v.toString(new Format(false, 0, 5, ", ")), (c, r, v) => `mag²:${(v.norm2()*100).toFixed(4)}%, phase:${forceSign(v.phase() * 180 / Math.PI)}°`); From d584f02e2aa4d3540e4427d94b47207d4ad4b406 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 9 Sep 2019 00:00:22 -0700 Subject: [PATCH 17/19] Collapse -> sample --- src/gates/Detector.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gates/Detector.js b/src/gates/Detector.js index 855b0cc3..53fa70eb 100644 --- a/src/gates/Detector.js +++ b/src/gates/Detector.js @@ -368,7 +368,7 @@ 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⟩.`). + setBlurb(`Does a sampled ${axis}-axis measurement.\nControls operations with the result.\nResets the target to |0⟩.`). setDrawer(args => drawDetectClearReset(args, axis)). markAsControlExpecting(true, true). markAsReachingOtherWires(). @@ -403,7 +403,7 @@ function makeDetector(axis) { setSerializedIdAndSymbol(`${axis}Detector`). setTitle(`${axis} Axis Detector`). setBlurb( - `Collapsing ${axis}-axis measurement.\n` + + `Sampled ${axis}-axis measurement.\n` + `Shows *click* when the target qubit is ${state} and controls are satisfied.`). setDrawer(args => drawDetector(args, axis)). markAsReachingOtherWires(). From bba28295cd3db91bd59479b369910bdcbf424d7f Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Mon, 9 Sep 2019 22:41:41 -0700 Subject: [PATCH 18/19] Make column editing work on mac - Have metaKey act the same as ctrlKey --- src/main.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main.js b/src/main.js index 6fd93d86..f0d2e2ab 100644 --- a/src/main.js +++ b/src/main.js @@ -188,13 +188,13 @@ watchDrags(canvasDiv, let newHand = oldInspector.hand.withPos(pt); let newInspector = syncArea(oldInspector.withHand(newHand)); clickDownGateButtonKey = ( - ev.ctrlKey || ev.shiftKey || ev.altKey ? undefined : newInspector.tryGetHandOverButtonKey()); + ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey ? undefined : newInspector.tryGetHandOverButtonKey()); if (clickDownGateButtonKey !== undefined) { displayed.set(newInspector); return; } - newInspector = newInspector.afterGrabbing(ev.shiftKey, ev.ctrlKey); + newInspector = newInspector.afterGrabbing(ev.shiftKey, ev.ctrlKey || ev.metaKey); if (displayed.get().isEqualTo(newInspector) || !newInspector.hand.isBusy()) { return; } @@ -203,7 +203,7 @@ watchDrags(canvasDiv, revision.startedWorkingOnCommit(); displayed.set( syncArea(oldInspector.withHand(newHand).withJustEnoughWires(newInspector.hand, 1)). - afterGrabbing(ev.shiftKey, ev.ctrlKey, false, ev.altKey)); + afterGrabbing(ev.shiftKey, ev.ctrlKey || ev.metaKey, false, ev.altKey)); ev.preventDefault(); }, From 68aba932e7dfa335e91bb32711b560efdec17a37 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Wed, 11 Sep 2019 02:52:54 -0700 Subject: [PATCH 19/19] Bump to version 2.3 --- html/quirk.template.html | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/html/quirk.template.html b/html/quirk.template.html index d1756836..07068299 100644 --- a/html/quirk.template.html +++ b/html/quirk.template.html @@ -35,7 +35,7 @@       - Version 2.2 + Version 2.3 diff --git a/package.json b/package.json index 7f4d7fac..5593a570 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "title": "Quirk", "description": "A drag-and-drop toy for exploring and understanding small quantum circuits.", "license": "Apache-2.0", - "version": "2.2.0", + "version": "2.3.0", "homepage": "https://github.com/Strilanc/Quirk", "bugs": { "url": "https://github.com/Strilanc/Quirk/issues"