Skip to content

Commit

Permalink
Implement Detector gate
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
Strilanc committed Nov 25, 2017
1 parent eaad86f commit afbc6a0
Show file tree
Hide file tree
Showing 20 changed files with 398 additions and 55 deletions.
2 changes: 0 additions & 2 deletions src/circuit/CircuitComputeUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/circuit/CircuitDefinition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
1 change: 0 additions & 1 deletion src/circuit/CircuitEvalContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"

/**
Expand Down
12 changes: 12 additions & 0 deletions src/circuit/Gate.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/circuit/GateColumn.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<<row);
return;
Expand Down
10 changes: 5 additions & 5 deletions src/circuit/KetShaderUtil.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ import {makePseudoShaderWithInputsAndOutputAndCode, Inputs, Outputs} from "src/w
* @param {!String} body Code that goes inside the output-computing function.
* @param {null|!int=null} span The height of the gate; the number of qubits it spans.
* @param {!Array.<!ShaderPartDescription>} 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(
[
Expand Down Expand Up @@ -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} }`,
Expand All @@ -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}
Expand Down
7 changes: 6 additions & 1 deletion src/draw/Painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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();
}
Expand Down
3 changes: 3 additions & 0 deletions src/gates/AllGates.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
Expand All @@ -118,6 +120,7 @@ Gates.KnownToSerializer = [
...Controls.all,
...InputGates.all,
MeasurementGate,
Detector,
SwapGateHalf,
SpacerGate,
UniversalNotGate,
Expand Down
185 changes: 185 additions & 0 deletions src/gates/Detector.js
Original file line number Diff line number Diff line change
@@ -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}
6 changes: 3 additions & 3 deletions src/gates/InterleaveBitsGates.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [];
Expand All @@ -71,14 +71,14 @@ function shaderFromBitPermutation(span, bitPermutation) {
}

/**
* @type {!Map.<!int, !{withArgs: !function(args: ...!WglArg) : !WglConfiguredShader}>}
* @type {!Map.<!int, !{withArgs: !function(args: ...!WglArg|!WglTexture) : !WglConfiguredShader}>}
*/
let _interleaveShadersForSize = Seq.range(Config.MAX_WIRE_COUNT + 1).
skip(2).
toMap(k => k, k => shaderFromBitPermutation(k, interleaveBit));

/**
* @type {!Map.<!int, !{withArgs: !function(args: ...!WglArg) : !WglConfiguredShader}>}
* @type {!Map.<!int, !{withArgs: !function(args: ...!WglArg|!WglTexture) : !WglConfiguredShader}>}
*/
let _deinterleaveShadersForSize = Seq.range(Config.MAX_WIRE_COUNT + 1).
skip(2).
Expand Down
Loading

0 comments on commit afbc6a0

Please sign in to comment.