From afbc6a099b27bcaaa49ac7e1b817083cb25a515e Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Fri, 24 Nov 2017 20:42:02 -0800 Subject: [PATCH] Implement Detector gate - It's like "real" measurement in that it picks a result and projects the state. - Fix checking for customStatPostProcessor instead of customStateTexturesMaker when marking circuit locations as having a custom stat - Add promiseEffectIsDiagonal to GateBuilder - Add Detector to the "unmeasure" special cases - Fix the return function type of ketShader expecting WglArg named values but not input-order argument values - Add 'alsoStroke' param to print - Defined 'drawMeasurementGate' outside of fluent builder lines. - Changed post-selection column note labels from 'fails' to 'omits' (and 'gain' to 'gains') - Changed end-of-circuit 'Survival rate' label to 'kept' - Added parameter documentation to SingleTypeCoder - Added method documentation to WglTexturePool and WglTextureTrader methods - WglTexture.toString now special-cases float/byte pixel types --- src/circuit/CircuitComputeUtil.js | 2 - src/circuit/CircuitDefinition.js | 2 +- src/circuit/CircuitEvalContext.js | 1 - src/circuit/Gate.js | 12 ++ src/circuit/GateColumn.js | 3 +- src/circuit/KetShaderUtil.js | 10 +- src/draw/Painter.js | 7 +- src/gates/AllGates.js | 3 + src/gates/Detector.js | 185 +++++++++++++++++++++++++ src/gates/InterleaveBitsGates.js | 6 +- src/gates/MeasurementGate.js | 55 ++++---- src/ui/DisplayedCircuit.js | 8 +- src/webgl/ShaderCoders_Base.js | 20 ++- src/webgl/WglTexture.js | 4 +- src/webgl/WglTexturePool.js | 23 ++- src/webgl/WglTextureTrader.js | 6 + test/circuit/CircuitDefinition.test.js | 9 +- test/circuit/Serializer.test.js | 2 +- test/gates/AllGates.test.js | 5 +- test/gates/Detector.test.js | 90 ++++++++++++ 20 files changed, 398 insertions(+), 55 deletions(-) create mode 100644 src/gates/Detector.js create mode 100644 test/gates/Detector.test.js diff --git a/src/circuit/CircuitComputeUtil.js b/src/circuit/CircuitComputeUtil.js index d9f47d7f..d56449cc 100644 --- a/src/circuit/CircuitComputeUtil.js +++ b/src/circuit/CircuitComputeUtil.js @@ -78,9 +78,7 @@ function advanceStateWithCircuit(ctx, circuitDefinition, collectStats) { colQubitDensities.push(qubitDensities); colNorms.push(norm); for (let {row, stat} of customGateStats) { - //noinspection JSUnusedAssignment customStatsMap.push({col, row, out: customStats.length}); - //noinspection JSUnusedAssignment customStats.push(stat); } }; diff --git a/src/circuit/CircuitDefinition.js b/src/circuit/CircuitDefinition.js index 2c3960f3..7f48d02b 100644 --- a/src/circuit/CircuitDefinition.js +++ b/src/circuit/CircuitDefinition.js @@ -926,7 +926,7 @@ class CircuitDefinition { let result = []; for (let row = 0; row < col.gates.length; row++) { if (col.gates[row] !== undefined && - col.gates[row].customStatPostProcesser !== undefined && + col.gates[row].customStatTexturesMaker !== undefined && this.gateAtLocIsDisabledReason(colIndex, row) === undefined) { result.push(row); } diff --git a/src/circuit/CircuitEvalContext.js b/src/circuit/CircuitEvalContext.js index 7bcb81a1..357738d5 100644 --- a/src/circuit/CircuitEvalContext.js +++ b/src/circuit/CircuitEvalContext.js @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {Util} from "src/base/Util.js" import {WglConfiguredShader} from "src/webgl/WglConfiguredShader.js" /** diff --git a/src/circuit/Gate.js b/src/circuit/Gate.js index 67dcdfb3..ad751710 100644 --- a/src/circuit/Gate.js +++ b/src/circuit/Gate.js @@ -718,6 +718,18 @@ class GateBuilder { return this; } + /** + * Sets meta-properties to indicate the gate is equivalent to a matrix with diagonal entries, such as post-selection + * in the computational basis. + * + * @returns {!GateBuilder} + */ + promiseEffectIsDiagonal() { + this.gate._hasNoEffect = false; + this.gate._effectPermutesStates = false; + this.gate._effectCreatesSuperpositions = false; + return this; + } /** * Sets meta-properties to indicate the gate is safe for classical, quantum, and mixed use. diff --git a/src/circuit/GateColumn.js b/src/circuit/GateColumn.js index 95272f23..93897773 100644 --- a/src/circuit/GateColumn.js +++ b/src/circuit/GateColumn.js @@ -392,7 +392,8 @@ class GateColumn { // Post-selection gates un-measure (in that the simulator can then do coherent operations on the qubit // without getting the wrong answer, at least). let hasSingleResult = gate === Gates.PostSelectionGates.PostSelectOn - || gate === Gates.PostSelectionGates.PostSelectOff; + || gate === Gates.PostSelectionGates.PostSelectOff + || gate === Gates.Detector; if (!this.hasControl() && hasSingleResult) { state.measureMask &= ~(1<} inputs - * @return {!{withArgs: !function(args: ...!WglArg) : !WglConfiguredShader}} A function that, when given the args - * returned by ketArgs when given your input texture and also a WglArg for each custom uniform you defined, returns - * a WglConfiguredShader that can be used to renderTo a destination texture. + * @return {!{withArgs: !function(args: ...!WglArg|!WglTexture) : !WglConfiguredShader}} A function that, when given the + * args returned by ketArgs when given your input texture and also a WglArg for each custom uniform you defined, + * returns a WglConfiguredShader that can be used to renderTo a destination texture. */ const ketShader = (head, body, span=null, inputs=[]) => ({withArgs: makePseudoShaderWithInputsAndOutputAndCode( [ @@ -78,7 +78,7 @@ const ketShader = (head, body, span=null, inputs=[]) => ({withArgs: makePseudoSh * @param {!String} head * @param {!String} body * @param {null|!int=null} span - * @return {!{withArgs: !function(args: ...!WglArg) : !WglConfiguredShader}} + * @return {!{withArgs: !function(args: ...!WglArg|!WglTexture) : !WglConfiguredShader}} */ const ketShaderPermute = (head, body, span=null) => ketShader( head + `float _ketgen_input_for(float out_id) { ${body} }`, @@ -91,7 +91,7 @@ const ketShaderPermute = (head, body, span=null) => ketShader( * @param {!String} head Header code defining shader methods, uniforms, etc. * @param {!String} body The body of a shader method returning the number of radians to phase by. * @param {null|!int=null} span The number of qubits this operation applies to, if known ahead of time. - * @return {!{withArgs: !function(args: ...!WglArg) : !WglConfiguredShader}} + * @return {!{withArgs: !function(args: ...!WglArg|!WglTexture) : !WglConfiguredShader}} */ const ketShaderPhase = (head, body, span=null) => ketShader( `${head} diff --git a/src/draw/Painter.js b/src/draw/Painter.js index cf1112ac..f70f93b6 100644 --- a/src/draw/Painter.js +++ b/src/draw/Painter.js @@ -190,6 +190,7 @@ class Painter { * @param {!string} fillStyle Text color. * @param {!string} font * @param {!function(!number, !number) : void} afterMeasureBeforeDraw + * @param {!boolean} alsoStroke */ print(text, x, @@ -200,7 +201,8 @@ class Painter { font, boundingWidth, boundingHeight, - afterMeasureBeforeDraw = undefined) { + afterMeasureBeforeDraw = undefined, + alsoStroke = false) { this.ctx.font = font; let naiveWidth = this.ctx.measureText(text).width; @@ -218,6 +220,9 @@ class Painter { this.ctx.fillStyle = fillStyle; this.ctx.translate(x, y); this.ctx.scale(scale, scale); + if (alsoStroke) { + this.ctx.strokeText(text, 0, 0); + } this.ctx.fillText(text, 0, 0); this.ctx.restore(); } diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 06da00c2..ed4104ae 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -45,6 +45,7 @@ import {ProbabilityDisplayFamily} from "src/gates/ProbabilityDisplay.js" import {QuarterTurnGates} from "src/gates/QuarterTurnGates.js" import {ReverseBitsGateFamily} from "src/gates/ReverseBitsGate.js" import {SampleDisplayFamily} from "src/gates/SampleDisplay.js" +import {Detector} from "src/gates/Detector.js" import {SpacerGate} from "src/gates/SpacerGate.js" import {SwapGateHalf} from "src/gates/SwapGateHalf.js" import {UniversalNotGate} from "src/gates/Impossible_UniversalNotGate.js" @@ -108,6 +109,7 @@ Gates.PostSelectionGates = PostSelectionGates; Gates.Powering = PoweringGates; Gates.QuarterTurns = QuarterTurnGates; Gates.ReverseBitsGateFamily = ReverseBitsGateFamily; +Gates.Detector = Detector; Gates.SpacerGate = SpacerGate; Gates.UniversalNot = UniversalNotGate; Gates.XorGates = XorGates; @@ -118,6 +120,7 @@ Gates.KnownToSerializer = [ ...Controls.all, ...InputGates.all, MeasurementGate, + Detector, SwapGateHalf, SpacerGate, UniversalNotGate, diff --git a/src/gates/Detector.js b/src/gates/Detector.js new file mode 100644 index 00000000..cb4b5991 --- /dev/null +++ b/src/gates/Detector.js @@ -0,0 +1,185 @@ +// Copyright 2017 Google Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import {GateBuilder} from 'src/circuit/Gate.js' +import {amplitudesToProbabilities} from 'src/gates/ProbabilityDisplay.js' +import {WglTexturePool} from 'src/webgl/WglTexturePool.js'; +import {WglTextureTrader} from 'src/webgl/WglTextureTrader.js'; +import {Shaders} from 'src/webgl/Shaders.js' +import {currentShaderCoder, Inputs} from 'src/webgl/ShaderCoders.js'; +import {CircuitShaders} from 'src/circuit/CircuitShaders.js' +import {Controls} from 'src/circuit/Controls.js'; +import {WglArg} from 'src/webgl/WglArg.js'; +import {Config} from 'src/Config.js' +import {GatePainting} from 'src/draw/GatePainting.js' +import {makePseudoShaderWithInputsAndOutputAndCode, Outputs} from 'src/webgl/ShaderCoders.js'; + +/** + * @param {!CircuitEvalContext} ctx + * @param {!Controls} controls + * @returns {!WglTexture} + */ +function controlMaskTex(ctx, controls) { + let powerSize = currentShaderCoder().vec2.arrayPowerSizeOfTexture(ctx.stateTrader.currentTexture); + return CircuitShaders.controlMask(controls).toBoolTexture(powerSize); +} + +/** + * Prepares a 1x1 texture containing the total squared-magnitude of states matching the given controls. + * @param {!WglTexture} ketTexture + * @param {!WglTexture} controlMaskTex + * @param {!boolean} forStats + * @returns {!WglTexture} + */ +function textureWithTotalWeightMatchingGivenControls(ketTexture, controlMaskTex, forStats=false) { + let powerSize = currentShaderCoder().vec2.arrayPowerSizeOfTexture(ketTexture); + + // Convert the matching amplitudes to probabilities (and the non-matching ones to 0). + let trader = new WglTextureTrader(ketTexture); + trader.dontDeallocCurrentTexture(); + trader.shadeAndTrade( + tex => amplitudesToProbabilities(tex, controlMaskTex), + WglTexturePool.takeVecFloatTex(powerSize)); + + // Sum the probabilities. + let n = currentShaderCoder().vec2.arrayPowerSizeOfTexture(ketTexture); + while (n > 0) { + n -= 1; + trader.shadeHalveAndTrade(Shaders.sumFoldFloat); + } + + trader.shadeAndTrade(Shaders.packFloatIntoVec4, WglTexturePool.takeVec4Tex(0)); + return trader.currentTexture; +} + +/** + * @param {!CircuitEvalContext} ctx + * @returns {!WglTexture} + */ +function detectorStatTexture(ctx) { + let mask = controlMaskTex(ctx, ctx.controls.and(Controls.bit(ctx.row, true))); + try { + return textureWithTotalWeightMatchingGivenControls(ctx.stateTrader.currentTexture, mask, true); + } finally { + mask.deallocByDepositingInPool('textureWithTotalWeightMatchingPositiveMeasurement:mask') + } +} + +/** + * Discards states that don't meet the detection result. + */ +let detectorShader = makePseudoShaderWithInputsAndOutputAndCode( + [ + Inputs.float('total_weight'), + Inputs.float('detection_weight'), + Inputs.bool('classification'), + Inputs.vec2('ket'), + ], + Outputs.vec2(), + ` + uniform float rnd; + + vec2 outputFor(float k) { + float detectChance = read_detection_weight(0.0) / read_total_weight(0.0); + float detection_type = float(rnd < detectChance); + float own_type = read_classification(k); + return read_ket(k) * float(detection_type == own_type); + } + `); + +/** + * Applies a sample measurement operation to the state. + * @param {!CircuitEvalContext} ctx + */ +function sampleMeasure(ctx) { + let maskAll = controlMaskTex(ctx, Controls.NONE); + let maskMatch = controlMaskTex(ctx, ctx.controls.and(Controls.bit(ctx.row, true))); + let weightAll = textureWithTotalWeightMatchingGivenControls(ctx.stateTrader.currentTexture, maskAll); + let weightMatch = textureWithTotalWeightMatchingGivenControls(ctx.stateTrader.currentTexture, maskMatch); + + ctx.applyOperation(detectorShader( + weightAll, + weightMatch, + maskMatch, + ctx.stateTrader.currentTexture, + WglArg.float('rnd', Math.random()))); + + weightMatch.deallocByDepositingInPool(); + weightAll.deallocByDepositingInPool(); + maskMatch.deallocByDepositingInPool(); + maskAll.deallocByDepositingInPool(); +} + +/** + * @param {!GateDrawParams} args + */ +function drawDetector(args) { + // Draw framing. + if (args.isHighlighted || args.isInToolbox) { + args.painter.fillRect( + args.rect, + args.isHighlighted ? Config.HIGHLIGHTED_GATE_FILL_COLOR : Config.TIME_DEPENDENT_HIGHLIGHT_COLOR); + GatePainting.paintOutline(args); + } + + // Draw semi-circle wedge. + const τ = Math.PI * 2; + let r = args.rect.w*0.4; + let {x, y} = args.rect.center(); + x -= r*0.5; + x += 0.5; + y += 0.5; + args.painter.trace(trace => { + trace.ctx.arc(x, y, r, τ*3/4, τ/4); + trace.ctx.lineTo(x, y - r - 1); + }).thenStroke('black', 2).thenFill(Config.TIME_DEPENDENT_HIGHLIGHT_COLOR); + + // Draw tilted "*click*" text. + let clicked = args.customStats; + if (clicked) { + args.painter.ctx.save(); + args.painter.ctx.translate(args.rect.center().x, args.rect.center().y); + args.painter.ctx.rotate(τ/8); + args.painter.ctx.strokeStyle = 'white'; + args.painter.ctx.lineWidth = 2; + args.painter.print( + '*click*', + 0, + 0, + 'center', + 'middle', + 'black', + 'bold 16px sans-serif', + args.rect.w*1.4, + args.rect.h*1.4, + undefined, + true); + args.painter.ctx.restore(); + } +} + +let Detector = new GateBuilder(). + setSerializedIdAndSymbol('Detector'). + setTitle('Detector'). + setBlurb('Makes a *click* when the target qubit is ON and all controls are satisfied.\n' + + 'Measures whether to click, samples the result, then post-selects on it.'). + setDrawer(drawDetector). + promiseEffectIsDiagonal(). + markAsReachingOtherWires(). + setActualEffectToUpdateFunc(sampleMeasure). + setStatTexturesMaker(detectorStatTexture). + setStatPixelDataPostProcessor((pixels, circuit, row, col) => pixels[0] > 0). + gate; + +export {Detector} diff --git a/src/gates/InterleaveBitsGates.js b/src/gates/InterleaveBitsGates.js index 92823143..3734c7f5 100644 --- a/src/gates/InterleaveBitsGates.js +++ b/src/gates/InterleaveBitsGates.js @@ -51,7 +51,7 @@ function deinterleaveBit(bit, len) { * Constructs a shader that permutes bits based on the given function. * @param {!int} span * @param {!function(bit: !int, len: !int) : !int} bitPermutation - * @return {!{withArgs: !function(args: ...!WglArg) : !WglConfiguredShader}} + * @return {!{withArgs: !function(args: ...!WglArg|!WglTexture) : !WglConfiguredShader}} */ function shaderFromBitPermutation(span, bitPermutation) { let bitMoveLines = []; @@ -71,14 +71,14 @@ function shaderFromBitPermutation(span, bitPermutation) { } /** - * @type {!Map.} + * @type {!Map.} */ let _interleaveShadersForSize = Seq.range(Config.MAX_WIRE_COUNT + 1). skip(2). toMap(k => k, k => shaderFromBitPermutation(k, interleaveBit)); /** - * @type {!Map.} + * @type {!Map.} */ let _deinterleaveShadersForSize = Seq.range(Config.MAX_WIRE_COUNT + 1). skip(2). diff --git a/src/gates/MeasurementGate.js b/src/gates/MeasurementGate.js index 7f4030ea..318aa016 100644 --- a/src/gates/MeasurementGate.js +++ b/src/gates/MeasurementGate.js @@ -16,35 +16,40 @@ import {Config} from "src/Config.js" import {GateBuilder} from "src/circuit/Gate.js" import {GatePainting} from "src/draw/GatePainting.js" +/** + * @param {!GateDrawParams} args + */ +function drawMeasurementGate(args) { + let backColor = Config.GATE_FILL_COLOR; + if (args.isHighlighted) { + backColor = Config.HIGHLIGHTED_GATE_FILL_COLOR; + } + args.painter.fillRect(args.rect, backColor); + GatePainting.paintOutline(args); + + const τ = Math.PI * 2; + let r = args.rect.w*0.4; + let {x, y} = args.rect.center(); + y += r*0.6; + let a = -τ/6; + let [c, s] = [Math.cos(a)*r*1.5, Math.sin(a)*r*1.5]; + let [p, q] = [x + c, y + s]; + + // Draw the dial and shaft. + args.painter.trace(trace => { + trace.ctx.arc(x, y, r, τ/2, τ); + trace.line(x, y, p, q); + }).thenStroke('black'); + // Draw the indicator head. + args.painter.trace(trace => trace.arrowHead(p, q, r*0.3, a, τ/4)).thenFill('black'); +} + let MeasurementGate = new GateBuilder(). setSerializedIdAndSymbol("Measure"). setTitle("Measurement Gate"). - setBlurb("Measures a wire's qubit along the Z axis."). + setBlurb("Measures whether a qubit is ON or OFF, without conditioning on the result."). promiseHasNoNetEffectOnStateVector(). // Because in the simulation we defer measurement by preventing operations. - setDrawer(args => { - let backColor = Config.GATE_FILL_COLOR; - if (args.isHighlighted) { - backColor = Config.HIGHLIGHTED_GATE_FILL_COLOR; - } - args.painter.fillRect(args.rect, backColor); - GatePainting.paintOutline(args); - - const τ = Math.PI * 2; - let r = args.rect.w*0.4; - let {x, y} = args.rect.center(); - y += r*0.6; - let a = -τ/6; - let [c, s] = [Math.cos(a)*r*1.5, Math.sin(a)*r*1.5]; - let [p, q] = [x + c, y + s]; - - // Draw the dial and shaft. - args.painter.trace(trace => { - trace.ctx.arc(x, y, r, τ/2, τ); - trace.line(x, y, p, q); - }).thenStroke('black'); - // Draw the indicator head. - args.painter.trace(trace => trace.arrowHead(p, q, r*0.3, a, τ/4)).thenFill('black'); - }). + setDrawer(drawMeasurementGate). setExtraDisableReasonFinder(args => { if (args.isNested) { return "can't\nnest\nmeasure\n(sorry)"; diff --git a/src/ui/DisplayedCircuit.js b/src/ui/DisplayedCircuit.js index 970b952a..33a623e0 100644 --- a/src/ui/DisplayedCircuit.js +++ b/src/ui/DisplayedCircuit.js @@ -562,11 +562,11 @@ class DisplayedCircuit { let rate = Math.round(-marginalRate * 100); let rateDesc = marginalRate === -1 ? "100" : rate < 100 ? rate : ">99"; descAmount = `${rateDesc}%`; - descCategory = 'fails'; + descCategory = 'omits'; } else { let factor = Math.round(marginalRate * 100 + 100); descAmount = `${factor}%`; - descCategory = 'gain'; + descCategory = 'gains'; } let pt = this.opRect(col).bottomCenter(); @@ -1241,10 +1241,10 @@ class DisplayedCircuit { let rateDesc = survivalRate === 0 ? "0" : rate > 0 ? rate : "<1"; - desc = `Survival rate: ${rateDesc}%`; + desc = `kept: ${rateDesc}%`; } else { let factor = Math.round(survivalRate * 100); - desc = `Over-unity: ${factor}%`; + desc = `over-unity: ${factor}%`; } painter.print( desc, diff --git a/src/webgl/ShaderCoders_Base.js b/src/webgl/ShaderCoders_Base.js index 816a735d..00fa8507 100644 --- a/src/webgl/ShaderCoders_Base.js +++ b/src/webgl/ShaderCoders_Base.js @@ -42,13 +42,19 @@ class ShaderPart { */ class SingleTypeCoder { /** - * @param {!function(name: !string) : !ShaderPart} inputPartGetter - * @param {!ShaderPart} outputPart - * @param {!int} powerSizeOverhead - * @param {!int} pixelType - * @param {!function(*) : !Float32Array|!Uint8Array} dataToPixels - * @param {!function(!Float32Array|!Uint8Array) : *} pixelsToData - * @param {!boolean} needRearrangingToBeInVec4Format + * @param {!function(name: !string) : !ShaderPart} inputPartGetter Determines how values are decoded from the + * various given input textures while rendering. + * @param {!ShaderPart} outputPart Determines how computed values are encoded into the next texture while rendering. + * @param {!int} powerSizeOverhead This value is the k in the 2^k * N texture area (pixels) it takes to encode N + * values. + * @param {!int} pixelType Determines whether encoding goes into float or byte textures. + * @param {!function(*) : !Float32Array|!Uint8Array} dataToPixels Converts from tightly packed data into the data + * that would be returned by readPixels (or that should be written into a texture encoding the data). + * @param {!function(!Float32Array|!Uint8Array) : *} pixelsToData Converts from raw pixel data returned by + * readPixels into tightly packed data. + * @param {!boolean} needRearrangingToBeInVec4Format Determines if the encoded values will be spread out instead of + * packed together tightly when readPixels is called. Code trying to minimize the time spent blocked on + * readPixels uses this as a hint to rearrange the data before reading it. */ constructor(inputPartGetter, outputPart, diff --git a/src/webgl/WglTexture.js b/src/webgl/WglTexture.js index 81bb7c43..972aac08 100644 --- a/src/webgl/WglTexture.js +++ b/src/webgl/WglTexture.js @@ -95,7 +95,9 @@ class WglTexture { toString() { return 'Texture(' + [ this.width + 'x' + this.height, - this.pixelType, + this.pixelType === WebGLRenderingContext.FLOAT ? 'FLOAT' : + this.pixelType === WebGLRenderingContext.UNSIGNED_BYTE ? 'UNSIGNED_BYTE' : + this.pixelType, this._hasBeenRenderedTo ? 'rendered' : 'not rendered'].join(', ') + ')'; } diff --git a/src/webgl/WglTexturePool.js b/src/webgl/WglTexturePool.js index 94f35eb3..e5970385 100644 --- a/src/webgl/WglTexturePool.js +++ b/src/webgl/WglTexturePool.js @@ -23,7 +23,7 @@ const BYTE_POOL = []; let unreturnedTextureCount = 0; /** - * Utilities related to storing and operation on superpositions and other circuit information in WebGL textures. + * Keeps track of a collection of textures of various sizes and types, so they can be used and returned and re-used. */ class WglTexturePool { /** @@ -45,6 +45,10 @@ class WglTexturePool { } /** + * Allocates a texture with the given pixel type with a total area of 1< { suite.test("colIsMeasuredMask", () => { let assertAbout = (diagram, ...extraGates) => { - let c = circuit(diagram, ...extraGates); + let c = circuit(diagram, ['D', Gates.Detector], ...extraGates); return assertThat(Seq.range(c.columns.length + 3).map(i => c.colIsMeasuredMask(i-1)).toArray()); }; @@ -419,6 +419,13 @@ suite.test("colIsMeasuredMask", () => { assertAbout('MM').isEqualTo([0, 0, 1, 1, 1]); // Post-selection clears assertAbout(`M!`).isEqualTo([0, 0, 1, 0, 0]); + // Detector clears. + assertAbout(`MD`).isEqualTo([0, 0, 1, 0, 0]); + // Controlled detector doesn't clear. + assertAbout(`MD + -●`).isEqualTo([0, 0, 1, 1, 1]); + assertAbout(`MD + M●`).isEqualTo([0, 0, 3, 3, 3]); // Swap moves measured assertAbout(`---s= -M=s-`).isEqualTo([0, 0, 0, 2, 2, 1, 1, 1]); diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index 85fe2d75..56a9a7e6 100644 --- a/test/circuit/Serializer.test.js +++ b/test/circuit/Serializer.test.js @@ -179,7 +179,7 @@ suite.test("roundTrip_circuitDefinition", () => { const IDS_THAT_SHOULD_BE_KNOWN = [ "•", "◦", "⊕", "⊖", "⊗", "(/)", "|0⟩⟨0|", "|1⟩⟨1|", "|+⟩⟨+|", "|-⟩⟨-|", "|X⟩⟨X|", "|/⟩⟨/|", - "Measure", + "Measure", "Detector", "Swap", "…", "inputA1", "inputA2", "inputA3", "inputA4", "inputA5", "inputA6", "inputA7", "inputA8", "inputA9", "inputA10", "inputA11", "inputA12", "inputA13", "inputA14", "inputA15", "inputA16", diff --git a/test/gates/AllGates.test.js b/test/gates/AllGates.test.js index 2ffa8946..b6a752ff 100644 --- a/test/gates/AllGates.test.js +++ b/test/gates/AllGates.test.js @@ -162,7 +162,8 @@ suite.testUsingWebGL("knownNonUnitaryGates", () => { '|+⟩⟨+|', '|-⟩⟨-|', '|X⟩⟨X|', - '|/⟩⟨/|' + '|/⟩⟨/|', + 'Detector', ])); }); @@ -222,7 +223,9 @@ suite.test("knownDynamicGateFamilies", () => { '< CircuitDefinition.fromTextDiagram(new Map([ + ...extras, + ['-', undefined], + ['●', Gates.Controls.Control], + ['D', Gates.Detector], + ['X', Gates.HalfTurns.X], + ['Z', Gates.HalfTurns.Z], + ['H', Gates.HalfTurns.H], +]), diagram); + +suite.testUsingWebGL("guaranteed-clicks", () => { + let c = CircuitStats.fromCircuitAtTime(circuit(` + -X-D- + ---D- + `), 0); + assertThat(c.customStatsForSlot(3, 0)).isEqualTo(true); + assertThat(c.customStatsForSlot(3, 1)).isEqualTo(false); +}); + +suite.testUsingWebGL("collapse-clicks", () => { + let c = CircuitStats.fromCircuitAtTime(circuit(` + -H-D- + -H-D- + `), 0); + let a = c.customStatsForSlot(3, 0); + let b = c.customStatsForSlot(3, 1); + assertThat(c.qubitDensityMatrix(Infinity, 0).cell(0, 0)).isEqualTo(a ? 0 : 1); + assertThat(c.qubitDensityMatrix(Infinity, 1).cell(0, 0)).isEqualTo(b ? 0 : 1); +}); + +suite.testUsingWebGL("guaranteed-agreement", () => { + let c = CircuitStats.fromCircuitAtTime(circuit(` + -H-●-D- + ---X-D- + `), 0); + let a = c.customStatsForSlot(5, 0); + let b = c.customStatsForSlot(5, 1); + assertThat(a).isEqualTo(b); + // And it collapsed the state. + assertThat(c.qubitDensityMatrix(Infinity, 0).cell(0, 0)).isEqualTo(a ? 0 : 1); + assertThat(c.qubitDensityMatrix(Infinity, 1).cell(0, 0)).isEqualTo(b ? 0 : 1); +}); + +suite.testUsingWebGL("guaranteed-control-clicks", () => { + let c = CircuitStats.fromCircuitAtTime(circuit(` + ---●--- + -X-D-D- + -X---●- + `), 0); + assertThat(c.customStatsForSlot(3, 1)).isEqualTo(false); + assertThat(c.customStatsForSlot(5, 1)).isEqualTo(true); +}); + +suite.testUsingWebGL("collapsed-control-clicks", () => { + for (let i = 0; i < 10; i++) { + let c = CircuitStats.fromCircuitAtTime(circuit(` + -H-●- + -H-D- + `), 0); + let a = c.customStatsForSlot(3, 1); + if (a) { + assertThat(c.finalState).isApproximatelyEqualTo(Matrix.col(0, 0, 0, 1)); + } else { + let s = Math.sqrt(1 / 3); + assertThat(c.finalState).isApproximatelyEqualTo(Matrix.col(s, s, s, 0)); + } + } +});