From 3ad0ce5a44cf103252a2f9b9ad9f2e90dca17ebf Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 23 Mar 2019 21:46:42 -0700 Subject: [PATCH] Rewrite AmplitudeDisplay to compute a quality metric instead of a pass/fail on incoherence - Pick largest vector component conditioned on other qubits, then dot product it onto all cases and add up the norms - Add Shaders.sumFoldFloatAdjacents - Add Shaders.floatData - Fix arrayPowerSizeOfTexture going below 0 causing all sim output to be lost - Fix cycleAllBitsFloat using vec2 instead of float when checking texture size --- src/circuit/GateShaders.js | 2 +- src/gates/AmplitudeDisplay.js | 405 +++++++++++----------------- src/webgl/ShaderCoders_Base.js | 2 +- src/webgl/Shaders.js | 19 ++ test/circuit/CircuitStats.test.js | 12 +- test/gates/AmplitudeDisplay.test.js | 340 ++++++++--------------- test/webgl/Shaders.test.js | 19 ++ 7 files changed, 303 insertions(+), 496 deletions(-) diff --git a/src/circuit/GateShaders.js b/src/circuit/GateShaders.js index f6444a1c..9835acf9 100644 --- a/src/circuit/GateShaders.js +++ b/src/circuit/GateShaders.js @@ -164,7 +164,7 @@ const CYCLE_ALL_SHADER_VEC2 = makePseudoShaderWithInputsAndOutputAndCode( * @returns {!WglConfiguredShader} */ GateShaders.cycleAllBitsFloat = (inputTexture, shiftAmount) => { - let size = currentShaderCoder().vec2.arrayPowerSizeOfTexture(inputTexture); + let size = currentShaderCoder().float.arrayPowerSizeOfTexture(inputTexture); return CYCLE_ALL_SHADER_FLOAT( inputTexture, WglArg.float("shiftAmount", 1 << Util.properMod(-shiftAmount, size))); diff --git a/src/gates/AmplitudeDisplay.js b/src/gates/AmplitudeDisplay.js index 048ca95c..5d301531 100644 --- a/src/gates/AmplitudeDisplay.js +++ b/src/gates/AmplitudeDisplay.js @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import {Complex} from "src/math/Complex.js" import {Config} from "src/Config.js" import {CircuitShaders} from "src/circuit/CircuitShaders.js" import {Gate} from "src/circuit/Gate.js" @@ -21,9 +20,10 @@ import {GateShaders} from "src/circuit/GateShaders.js" import {Format} from "src/base/Format.js" import {MathPainter} from "src/draw/MathPainter.js" import {Matrix, complexVectorToReadableJson, realVectorToReadableJson} from "src/math/Matrix.js" +import {probabilityStatTexture} from "src/gates/ProbabilityDisplay.js" import {Point} from "src/math/Point.js" import {Util} from "src/base/Util.js" -import {WglArg} from "src/webgl/WglArg.js" +import {Shaders} from "src/webgl/Shaders.js" import {WglConfiguredShader} from "src/webgl/WglConfiguredShader.js" import { Inputs, @@ -37,11 +37,14 @@ import {WglTextureTrader} from "src/webgl/WglTextureTrader.js" /** * @param {!WglTexture} stateKet * @param {!Controls} controls + * @param {!WglTexture} controlsTexture * @param {!int} rangeOffset * @param {!int} rangeLength * @returns {!Array.} */ -function amplitudeDisplayStatTextures(stateKet, controls, rangeOffset, rangeLength) { +function amplitudeDisplayStatTextures(stateKet, controls, controlsTexture, rangeOffset, rangeLength) { + let incoherentKet = probabilityStatTexture(stateKet, controlsTexture, rangeOffset, rangeLength); + let trader = new WglTextureTrader(stateKet); trader.dontDeallocCurrentTexture(); @@ -50,65 +53,103 @@ function amplitudeDisplayStatTextures(stateKet, controls, rangeOffset, rangeLeng let lostQubits = Util.numberOfSetBits(controls.inclusionMask); let lostHeadQubits = Util.numberOfSetBits(controls.inclusionMask & ((1< CircuitShaders.controlSelect(controls, tex), WglTexturePool.takeVec2Tex(involvedQubits)); trader.shadeAndTrade(tex => GateShaders.cycleAllBits(tex, lostHeadQubits-rangeOffset)); let ketJustAfterCycle = trader.dontDeallocCurrentTexture(); - // Look over all superposed values of the target qubits and pick the one with the most amplitude. - trader.shadeAndTrade(amplitudesToPolarKets, WglTexturePool.takeVec4Tex(involvedQubits)); - spreadLengthAcrossPolarKets(trader, rangeLength); - reduceToLongestPolarKet(trader, rangeLength); - trader.shadeAndTrade(convertAwayFromPolar); - let amps = trader.dontDeallocCurrentTexture(); + // Compute magnitude of each case's vector. + trader.shadeAndTrade(AMPS_TO_SQUARED_MAGS_SHADER, WglTexturePool.takeVecFloatTex(involvedQubits)); + for (let k = 0; k < rangeLength; k++) { + trader.shadeHalveAndTrade(Shaders.sumFoldFloatAdjacents); + } + + // Find the index of the case with the largest vector. + trader.shadeAndTrade(MAGS_TO_INDEXED_MAGS_SHADER, WglTexturePool.takeVec2Tex(broadcastQubits)); + for (let k = 0; k < broadcastQubits; k++) { + trader.shadeHalveAndTrade(FOLD_MAX_INDEXED_MAG_SHADER); + } - // Compare the chosen case against other cases. If they aren't multiples, we're not separable (i.e. incoherent). + // Lookup the components of the largest vector. trader.shadeAndTrade( - winningVectorKet => toRatiosVsRepresentative(ketJustAfterCycle, winningVectorKet), - WglTexturePool.takeVec4Tex(involvedQubits)); + indexed_mag => LOOKUP_KET_AT_INDEXED_MAG_SHADER(ketJustAfterCycle, indexed_mag), + WglTexturePool.takeVec2Tex(rangeLength)); + let rawKet = trader.dontDeallocCurrentTexture(); + + // Compute the dot product of the largest vector against every other vector. + trader.shadeAndTrade( + small_input => POINTWISE_CMUL_SHADER(small_input, ketJustAfterCycle), + WglTexturePool.takeVec2Tex(involvedQubits)); ketJustAfterCycle.deallocByDepositingInPool("ketJustAfterCycle in makeAmplitudeSpanPipeline"); - foldConsistentRatios(trader, rangeLength); - signallingSumAll(trader); + for (let k = 0; k < broadcastQubits; k++) { + trader.shadeHalveAndTrade(Shaders.sumFoldVec2); + } - return [amps, trader.currentTexture]; + // Sum up the magnitudes of the dot products to get a quality metric for how well the largest vector worked. + trader.shadeAndTrade(AMPS_TO_ABS_MAGS_SHADER, WglTexturePool.takeVecFloatTex(rangeLength)); + for (let k = 0; k < rangeLength; k++) { + trader.shadeHalveAndTrade(Shaders.sumFoldFloat); + } + + if (currentShaderCoder().float.needRearrangingToBeInVec4Format) { + trader.shadeHalveAndTrade(Shaders.packFloatIntoVec4); + } + let quality = trader.currentTexture; + + trader.currentTexture = rawKet; + if (currentShaderCoder().vec2.needRearrangingToBeInVec4Format) { + trader.shadeHalveAndTrade(Shaders.packVec2IntoVec4); + } + let ket = trader.currentTexture; + + return [ket, quality, incoherentKet]; } /** * @param {!int} span * @param {!Array.} pixelGroups * @param {!CircuitDefinition} circuitDefinition - * @returns {!{probabilities: undefined|!Float32Array, superposition: undefined|!Matrix, phaseLockIndex:undefined|!int}} + * @returns {!{quality: !float, ket: undefined|!Matrix, phaseLockIndex: undefined|!int}} */ function processOutputs(span, pixelGroups, circuitDefinition) { - let [ketPixels, consistentPixel] = pixelGroups; - let n = ketPixels.length >> 2; + let [ketPixels, qualityPixels, rawIncoherentKetPixels] = pixelGroups; + let quality = qualityPixels[0]; + let n = 1 << span; let w = n === 2 ? 2 : 1 << Math.floor(Math.round(Math.log2(n))/2); let h = n/w; - let isPure = !isNaN(consistentPixel[0]) && consistentPixel[0] !== -666.0; - let unity = ketPixels[2]; - if (!isPure) { - return _processOutputs_probabilities(w, h, n, unity, ketPixels); + // Rescale quantities. + let unity = 0; + for (let e of ketPixels) { + unity += e*e; + } + let incoherentKetPixels = new Float32Array(w * h * 2); + for (let i = 0; i < n; i++) { + incoherentKetPixels[i << 1] = Math.sqrt(rawIncoherentKetPixels[i]); } let phaseIndex = span === circuitDefinition.numWires ? undefined : _processOutputs_pickPhaseLockIndex(ketPixels); - let phase = phaseIndex === undefined ? 0 : Math.atan2(ketPixels[phaseIndex*4+1], ketPixels[phaseIndex*4]); + let phase = phaseIndex === undefined ? 0 : Math.atan2(ketPixels[phaseIndex*2+1], ketPixels[phaseIndex*2]); let c = Math.cos(phase); let s = -Math.sin(phase); let buf = new Float32Array(n*2); let sqrtUnity = Math.sqrt(unity); for (let i = 0; i < n; i++) { - let real = ketPixels[i*4]/sqrtUnity; - let imag = ketPixels[i*4+1]/sqrtUnity; + let real = ketPixels[i*2]/sqrtUnity; + let imag = ketPixels[i*2+1]/sqrtUnity; buf[i*2] = real*c + imag*-s; buf[i*2+1] = real*s + imag*c; } return { - probabilities: undefined, - superposition: new Matrix(w, h, buf), - phaseLockIndex: phaseIndex + quality, + ket: new Matrix(w, h, buf), + phaseLockIndex: phaseIndex, + incoherentKet: new Matrix(w, h, incoherentKetPixels), }; } @@ -120,230 +161,88 @@ function processOutputs(span, pixelGroups, circuitDefinition) { function _processOutputs_pickPhaseLockIndex(ketPixels) { let result = 0; let best = 0; - for (let k = 0; k < ketPixels.length; k += 4) { + for (let k = 0; k < ketPixels.length; k += 2) { let r = ketPixels[k]; let i = ketPixels[k+1]; let m = r*r + i*i; if (m > best*10000) { best = m; - result = k >> 2; + result = k >> 1; } } return result; } -function _processOutputs_probabilities(w, h, n, unity, ketPixels) { - let pBuf = new Float32Array(n*2); - for (let k = 0; k < n; k++) { - let r = ketPixels[k*4]; - let i = ketPixels[k*4+1]; - pBuf[k*2] = Math.sqrt((r*r + i*i)/unity); - } - return { - probabilities: new Matrix(w, h, pBuf), - superposition: undefined, - phaseLockIndex: undefined - }; -} - -/** - * @param {!WglTexture} input - * @returns {!WglConfiguredShader} - */ -function amplitudesToPolarKets(input) { - return AMPLITUDES_TO_POLAR_KETS_SHADER(input); -} -const AMPLITUDES_TO_POLAR_KETS_SHADER = makePseudoShaderWithInputsAndOutputAndCode( +const AMPS_TO_SQUARED_MAGS_SHADER = makePseudoShaderWithInputsAndOutputAndCode( [Inputs.vec2('input')], - Outputs.vec4(), - `vec4 outputFor(float k) { + Outputs.float(), + `float outputFor(float k) { vec2 ri = read_input(k); - float mag = dot(ri, ri); - float phase = mag == 0.0 ? 0.0 : atan(ri.y, ri.x); - return vec4(mag, phase, mag, 0.0); + return dot(ri, ri); }`); -/** - * Goes from (mag, angle, mag, 0) form to (mag, angle, total_vector_mag, 0) form. - * @param {!WglTextureTrader} textureTrader - * @param {!int} includedQubitCount - * @returns {void} - */ -function spreadLengthAcrossPolarKets(textureTrader, includedQubitCount) { - for (let bit = 0; bit < includedQubitCount; bit++) { - textureTrader.shadeAndTrade(inp => SPREAD_LENGTH_ACROSS_POLAR_KETS_SHADER( - inp, - WglArg.float('bit', 1 << bit))); - } -} -const SPREAD_LENGTH_ACROSS_POLAR_KETS_SHADER = makePseudoShaderWithInputsAndOutputAndCode( - [Inputs.vec4('input')], - Outputs.vec4(), - ` - uniform float bit; - - float xorBit(float v) { - float b = floor(mod(floor(v/bit) + 0.5, 2.0)); - float d = 1.0 - 2.0*b; - return v + bit*d; - } - - vec4 outputFor(float k) { - float partner = xorBit(k); - vec4 v = read_input(k); - vec4 p = read_input(partner); - return vec4(v.x, v.y, v.z + p.z, 0.0); +const AMPS_TO_ABS_MAGS_SHADER = makePseudoShaderWithInputsAndOutputAndCode( + [Inputs.vec2('input')], + Outputs.float(), + `float outputFor(float k) { + vec2 ri = read_input(k); + return sqrt(dot(ri, ri)); }`); -/** - * Reduces a list of vectors in (mag, angle, total_mag_of_vector, 0) form to the single highest-total-magnitude vector. - * @param {!WglTextureTrader} textureTrader - * @param {!int} includedQubitCount - * @returns {void} - */ -function reduceToLongestPolarKet(textureTrader, includedQubitCount) { - let curQubitCount = currentShaderCoder().vec4.arrayPowerSizeOfTexture(textureTrader.currentTexture); - while (curQubitCount > includedQubitCount) { - curQubitCount -= 1; - textureTrader.shadeHalveAndTrade( - inp => FOLD_REPRESENTATIVE_POLAR_KET_SHADER( - inp, - WglArg.float('offset', 1 << curQubitCount))); - } -} -const FOLD_REPRESENTATIVE_POLAR_KET_SHADER = makePseudoShaderWithInputsAndOutputAndCode( - [Inputs.vec4('input')], - Outputs.vec4(), - ` - uniform float offset; - - vec4 outputFor(float k) { - vec4 p = read_input(k); - vec4 q = read_input(k + offset); - return vec4( - p.x + q.x, - // Bias towards p1 is to keep the choice stable in the face of uniform superpositions and noise. - p.z*1.001 >= q.z ? p.y : q.y, - p.z + q.z, - 0.0); +const MAGS_TO_INDEXED_MAGS_SHADER = makePseudoShaderWithInputsAndOutputAndCode( + [Inputs.float('input')], + Outputs.vec2(), + `vec2 outputFor(float k) { + return vec2(float(k), read_input(k)); }`); -/** - * @param {!WglTexture} input - * @returns {!WglConfiguredShader} - */ -function convertAwayFromPolar(input) { - return CONVERT_AWAY_FROM_POLAR_SHADER(input); -} -const CONVERT_AWAY_FROM_POLAR_SHADER = makePseudoShaderWithInputsAndOutputAndCode( - [Inputs.vec4('input')], - Outputs.vec4(), - ` - vec4 outputFor(float k) { - vec4 polar = read_input(k); - float mag = sqrt(polar.x); - return vec4(mag * cos(polar.y), mag * sin(polar.y), polar.z, 0.0); +const FOLD_MAX_INDEXED_MAG_SHADER = makePseudoShaderWithInputsAndOutputAndCode( + [Inputs.vec2('input')], + Outputs.vec2(), + `vec2 outputFor(float k) { + vec2 a = read_input(k); + vec2 b = read_input(k + len_output()); + return a.y >= b.y ? a : b; }`); -/** - * @param {!WglTexture} ket - * @param {!WglTexture} rep - * @returns {!WglConfiguredShader} - */ -let toRatiosVsRepresentative = (ket, rep) => TO_RATIOS_VS_REPRESENTATIVE_SHADER(ket, rep); -const TO_RATIOS_VS_REPRESENTATIVE_SHADER = makePseudoShaderWithInputsAndOutputAndCode( - [ - Inputs.vec2('ket'), - Inputs.vec4('rep') - ], - Outputs.vec4(), - `vec4 outputFor(float k) { - return vec4(read_ket(k), read_rep(floor(mod(k + 0.5, len_rep()))).xy); +const LOOKUP_KET_AT_INDEXED_MAG_SHADER = makePseudoShaderWithInputsAndOutputAndCode( + [Inputs.vec2('input'), Inputs.vec2('indexed_mag')], + Outputs.vec2(), + `vec2 outputFor(float k) { + return read_input(k + read_indexed_mag(0.0).x * len_output()); }`); -/** - * @param {!WglTextureTrader} textureTrader - * @param {!int} includedQubitCount - * @returns {void} - */ -function foldConsistentRatios(textureTrader, includedQubitCount) { - let curQubitCount = currentShaderCoder().vec4.arrayPowerSizeOfTexture(textureTrader.currentTexture); - let remainingIncludedQubitCount = includedQubitCount; - while (remainingIncludedQubitCount > 0) { - remainingIncludedQubitCount -= 1; - curQubitCount -= 1; - textureTrader.shadeHalveAndTrade( - inp => FOLD_CONSISTENT_RATIOS_SHADER( - inp, - WglArg.float('bit', 1 << remainingIncludedQubitCount))); - } -} -const FOLD_CONSISTENT_RATIOS_SHADER = makePseudoShaderWithInputsAndOutputAndCode( - [Inputs.vec4('input')], - Outputs.vec4(), +const POINTWISE_CMUL_SHADER = makePseudoShaderWithInputsAndOutputAndCode( + [Inputs.vec2('small_input'), Inputs.vec2('large_input')], + Outputs.vec2(), ` - uniform float bit; - - vec2 mul(vec2 c1, vec2 c2) { - return vec2(c1.x*c2.x - c1.y*c2.y, c1.x*c2.y + c1.y*c2.x); + vec2 cmul(vec2 c1, vec2 c2) { + return mat2(c1.x, c1.y, -c1.y, c1.x) * c2; } - vec4 mergeRatios(vec4 a, vec4 b) { - vec2 c1 = mul(a.xy, b.zw); - vec2 c2 = mul(a.zw, b.xy); - vec2 d = c1 - c2; - float err = dot(d, d); - // The max up-scaling controls a tricky tradeoff between noisy false positives and blurry false negatives. - err /= max(0.00000000001, min(abs(dot(c1, c1)), abs(dot(c2,c2)))); - float m1 = dot(a, a); - float m2 = dot(b, b); - return a.x == -666.0 || b.x == -666.0 || err > 0.001 ? vec4(-666.0, -666.0, -666.0, -666.0) - : m1 >= m2 ? a - : b; + vec2 outputFor(float k) { + vec2 in1 = read_small_input(floor(mod(k + 0.5, len_small_input()))); + vec2 in2 = read_large_input(k); + return cmul(in1, in2); } - - vec4 outputFor(float k) { - float s1 = mod(k, bit) + floor(k/bit)*2.0*bit; - float s2 = s1 + bit; - vec4 v1 = read_input(s1); - vec4 v2 = read_input(s2); - - return mergeRatios(v1, v2); - }`); - -/** - * @param {!WglTextureTrader} textureTrader - */ -function signallingSumAll(textureTrader) { - let curQubitCount = currentShaderCoder().vec4.arrayPowerSizeOfTexture(textureTrader.currentTexture); - while (curQubitCount > 0) { - curQubitCount -= 1; - textureTrader.shadeHalveAndTrade(SIGNALLING_SUM_SHADER_VEC4); - } -} -const SIGNALLING_SUM_SHADER_VEC4 = makePseudoShaderWithInputsAndOutputAndCode( - [Inputs.vec4('input')], - Outputs.vec4(), - `vec4 outputFor(float k) { - vec4 a = read_input(k); - vec4 b = read_input(k + len_output()); - return a.x == -666.0 || b.x == -666.0 ? vec4(-666.0, -666.0, -666.0, -666.0) : a + b; - }`); + `); /** * @type {!function(!GateDrawParams)} */ const AMPLITUDE_DRAWER_FROM_CUSTOM_STATS = GatePainting.makeDisplayDrawer(args => { let n = args.gate.height; - let {probabilities, superposition, phaseLockIndex} = args.customStats || { - probabilities: undefined, - superposition: (n === 1 ? Matrix.zero(2, 1) : Matrix.zero(1 << Math.floor(n / 2), 1 << Math.ceil(n / 2))). - times(NaN), - phaseLockIndex: undefined + let {quality, ket, phaseLockIndex, incoherentKet} = args.customStats || { + ket: (n === 1 ? Matrix.zero(2, 1) : Matrix.zero(1 << Math.floor(n / 2), 1 << Math.ceil(n / 2))).times(NaN), + quality: 1, + phaseLockIndex: 0, + incoherentKet: undefined }; - let matrix = probabilities || superposition; - let isIncoherent = superposition === undefined; - let dw = args.rect.w - args.rect.h*matrix.width()/matrix.height(); + + let isIncoherent = quality < 0.99; + let matrix = isIncoherent ? incoherentKet : ket; + let dw = args.rect.w - args.rect.h*ket.width()/ket.height(); let drawRect = args.rect.skipLeft(dw/2).skipRight(dw/2); + let indicatorAlpha = Math.min(1, Math.max(0, (quality - 0.999) / 0.001)); MathPainter.paintMatrix( args.painter, matrix, @@ -352,7 +251,7 @@ const AMPLITUDE_DRAWER_FROM_CUSTOM_STATS = GatePainting.makeDisplayDrawer(args = 'black', Config.SUPERPOSITION_FORE_COLOR, Config.SUPERPOSITION_BACK_COLOR, - isIncoherent ? 'transparent' : 'black'); + `rgba(0, 0, 0, ${indicatorAlpha})`); let forceSign = v => (v >= 0 ? '+' : '') + v.toFixed(2); if (isIncoherent) { @@ -372,36 +271,40 @@ const AMPLITUDE_DRAWER_FROM_CUSTOM_STATS = GatePainting.makeDisplayDrawer(args = let r = Math.floor(phaseLockIndex / matrix.width()); let cx = drawRect.x + cw*(c+0.5); let cy = drawRect.y + rh*(r+0.5); - args.painter.strokeLine(new Point(cx, cy), new Point(cx + cw/2, cy), 'red', 2); + args.painter.strokeLine( + new Point(cx, cy), + new Point(cx + cw/2, cy), + `rgba(255,0,0,${indicatorAlpha})`, + 2); args.painter.print( 'fixed', cx + 0.5*cw, cy, 'right', 'bottom', - 'red', + `rgba(255,0,0,${indicatorAlpha})`, '12px monospace', cw*0.5, rh*0.5); } } - paintErrorIfPresent(args, isIncoherent); + paintErrorIfPresent(args, indicatorAlpha); }); /** * @param {!GateDrawParams} args - * @param {!boolean} isIncoherent + * @param {!number} indicatorAlpha */ -function paintErrorIfPresent(args, isIncoherent) { +function paintErrorIfPresent(args, indicatorAlpha) { /** @type {undefined|!string} */ let err = undefined; let {col, row} = args.positionInCircuit; let measured = ((args.stats.circuitDefinition.colIsMeasuredMask(col) >> row) & ((1 << args.gate.height) - 1)) !== 0; - if (isIncoherent) { - err = 'incoherent'; - } else if (measured) { + if (measured && indicatorAlpha > 0.9) { err = args.gate.width <= 2 ? '(w/ measure defer)' : '(assuming measurement deferred)'; + } else if (indicatorAlpha < 0.999) { + err = 'incoherent'; } if (err !== undefined) { args.painter.print( @@ -410,7 +313,7 @@ function paintErrorIfPresent(args, isIncoherent) { args.rect.y+args.rect.h, 'center', 'hanging', - 'red', + `rgba(255,0,0,${1-indicatorAlpha})`, '12px sans-serif', args.rect.w, args.rect.h, @@ -419,27 +322,17 @@ function paintErrorIfPresent(args, isIncoherent) { } /** - * @param customStats + * @param {!{quality: !float, ket: undefined|!Matrix, phaseLockIndex: undefined|!int}} customStats */ function customStatsToJsonData(customStats) { - let {probabilities, superposition, phaseLockIndex} = customStats; - let result = { - coherence_measure: superposition !== undefined ? 1 : 0, + let {quality, ket, phaseLockIndex, incoherentKet} = customStats; + let n = ket.width() * ket.height(); + return { + coherence_measure: quality, superposition_phase_locked_state_index: phaseLockIndex === undefined ? null : phaseLockIndex, - probabilities: null, - amplitudes: null, - + ket: complexVectorToReadableJson(new Matrix(1, n, ket.rawBuffer()).getColumn(0)), + incoherentKet: realVectorToReadableJson(new Matrix(1, n, incoherentKet.rawBuffer()).getColumn(0)) }; - if (probabilities !== undefined) { - let n = probabilities._width * probabilities._height; - result['probabilities'] = realVectorToReadableJson( - new Matrix(1, n, probabilities._buffer).getColumn(0).map(e => Math.pow(Complex.realPartOf(e), 2))); - } - if (superposition !== undefined) { - let n = superposition._width * superposition._height; - result['superposition'] = complexVectorToReadableJson(new Matrix(1, n, superposition._buffer).getColumn(0)); - } - return result; } let AmplitudeDisplayFamily = Gate.buildFamily(1, 16, (span, builder) => builder. @@ -451,19 +344,23 @@ let AmplitudeDisplayFamily = Gate.buildFamily(1, 16, (span, builder) => builder. promiseHasNoNetEffectOnStateVector(). setExtraDisableReasonFinder(args => args.isNested ? "can't\nnest\ndisplays\n(sorry)" : undefined). setStatTexturesMaker(ctx => - amplitudeDisplayStatTextures(ctx.stateTrader.currentTexture, ctx.controls, ctx.row, span)). + amplitudeDisplayStatTextures( + ctx.stateTrader.currentTexture, + ctx.controls, + ctx.controlsTexture, + ctx.row, + span)). setStatPixelDataPostProcessor((val, def) => processOutputs(span, val, def)). setProcessedStatsToJsonFunc(customStatsToJsonData). setDrawer(AMPLITUDE_DRAWER_FROM_CUSTOM_STATS)); export { AmplitudeDisplayFamily, - amplitudesToPolarKets, - convertAwayFromPolar, + AMPS_TO_SQUARED_MAGS_SHADER, + AMPS_TO_ABS_MAGS_SHADER, + MAGS_TO_INDEXED_MAGS_SHADER, + FOLD_MAX_INDEXED_MAG_SHADER, + LOOKUP_KET_AT_INDEXED_MAG_SHADER, + POINTWISE_CMUL_SHADER, amplitudeDisplayStatTextures, - reduceToLongestPolarKet, - foldConsistentRatios, - spreadLengthAcrossPolarKets, - signallingSumAll, - toRatiosVsRepresentative }; diff --git a/src/webgl/ShaderCoders_Base.js b/src/webgl/ShaderCoders_Base.js index 00fa8507..db9efd3c 100644 --- a/src/webgl/ShaderCoders_Base.js +++ b/src/webgl/ShaderCoders_Base.js @@ -84,7 +84,7 @@ class SingleTypeCoder { * @returns {!int} */ arrayPowerSizeOfTexture(tex) { - return tex.sizePower() - this.powerSizeOverhead; + return Math.max(0, tex.sizePower() - this.powerSizeOverhead); } } diff --git a/src/webgl/Shaders.js b/src/webgl/Shaders.js index a8c0b6ae..5e12c5b2 100644 --- a/src/webgl/Shaders.js +++ b/src/webgl/Shaders.js @@ -101,6 +101,13 @@ Shaders.data = rgbaData => new WglConfiguredShader(destinationTexture => { } }); +/** + * Returns a configured shader that overlays the destination texture with the given float data. + * @param {!Float32Array} floats + * @returns {!WglConfiguredShader} + */ +Shaders.floatData = floats => Shaders.data(currentShaderCoder().float.dataToPixels(floats)); + /** * Returns a configured shader that overlays the destination texture with the given vec2 data. * @param {!Float32Array} floats @@ -151,6 +158,18 @@ Shaders.sumFoldFloat = makePseudoShaderWithInputsAndOutputAndCode( return read_input(k) + read_input(k + len_output()); }`); +/** + * Adds the odd half of its input to the even half of its input. + * @param {!WglTexture} inp + * @returns {!WglConfiguredShader} + */ +Shaders.sumFoldFloatAdjacents = makePseudoShaderWithInputsAndOutputAndCode( + [Inputs.float('input')], + Outputs.float(), + `float outputFor(float k) { + return read_input(k*2.0) + read_input(k*2.0 + 1.0); + }`); + /** * Adds the second half of its input into the first half. * @param {!WglTexture} inp diff --git a/test/circuit/CircuitStats.test.js b/test/circuit/CircuitStats.test.js index b3dac2b5..08cf5e14 100644 --- a/test/circuit/CircuitStats.test.js +++ b/test/circuit/CircuitStats.test.js @@ -108,9 +108,10 @@ suite.testUsingWebGL('incoherent-amplitude-display', () => { assertThat(stats.qubitDensityMatrix(Infinity, 0)).isApproximatelyEqualTo(Matrix.square(0.5, 0, 0, 0.5)); assertThat(stats.qubitDensityMatrix(Infinity, 1)).isApproximatelyEqualTo(Matrix.square(0.5, 0, 0, 0.5)); assertThat(stats.customStatsForSlot(5, 0)).isApproximatelyEqualTo({ - probabilities: Matrix.row(1, 1).times(Math.sqrt(0.5)), - superposition: undefined, - phaseLockIndex: undefined + quality: 0.5, + ket: Matrix.row(1, 0), + phaseLockIndex: 0, + incoherentKet: Matrix.row(Math.sqrt(0.5), Math.sqrt(0.5)) }); }); @@ -123,8 +124,9 @@ suite.testUsingWebGL('coherent-amplitude-display', () => { assertThat(stats.qubitDensityMatrix(Infinity, 1)).isApproximatelyEqualTo(Matrix.square(0.5, 0, 0, 0.5)); assertThat(stats.qubitDensityMatrix(Infinity, 2)).isApproximatelyEqualTo(Matrix.square(0.5, 0.5, 0.5, 0.5)); assertThat(stats.customStatsForSlot(5, 0)).isApproximatelyEqualTo({ - probabilities: undefined, - superposition: Matrix.square(1, 0, 0, 1).times(Math.sqrt(0.5)), + quality: 1, + ket: Matrix.square(1, 0, 0, 1).times(Math.sqrt(0.5)), + incoherentKet: Matrix.square(1, 0, 0, 1).times(Math.sqrt(0.5)), phaseLockIndex: 0 }); }); diff --git a/test/gates/AmplitudeDisplay.test.js b/test/gates/AmplitudeDisplay.test.js index c5b403e3..c0e3c753 100644 --- a/test/gates/AmplitudeDisplay.test.js +++ b/test/gates/AmplitudeDisplay.test.js @@ -14,278 +14,148 @@ import {Suite, assertThat} from "test/TestUtil.js" import { - amplitudesToPolarKets, - convertAwayFromPolar, amplitudeDisplayStatTextures, - reduceToLongestPolarKet, - foldConsistentRatios, - spreadLengthAcrossPolarKets, - signallingSumAll, - toRatiosVsRepresentative + AMPS_TO_SQUARED_MAGS_SHADER, + AMPS_TO_ABS_MAGS_SHADER, + MAGS_TO_INDEXED_MAGS_SHADER, + FOLD_MAX_INDEXED_MAG_SHADER, + LOOKUP_KET_AT_INDEXED_MAG_SHADER, + POINTWISE_CMUL_SHADER, } from "src/gates/AmplitudeDisplay.js" import {Complex} from "src/math/Complex.js" import {Controls} from "src/circuit/Controls.js" +import {CircuitShaders} from "src/circuit/CircuitShaders.js" import {seq} from "src/base/Seq.js" import {Shaders} from "src/webgl/Shaders.js" import {currentShaderCoder} from "src/webgl/ShaderCoders.js" let suite = new Suite("AmplitudeDisplay"); -suite.testUsingWebGL("amplitudesToPolarKets", () => { +suite.testUsingWebGL("AMPS_TO_SQUARED_MAGS_SHADER", () => { let input = Shaders.vec2Data(new Float32Array([ 1,0, 3,4, -1,1, 0,0.5 ])).toVec2Texture(2); - assertThat(amplitudesToPolarKets(input).readVec4Outputs(2)).isApproximatelyEqualTo(new Float32Array([ - 1,0,1,0, - 25,new Complex(3,4).phase(),25,0, - 2,Math.PI*3/4,2,0, - 0.25,Math.PI/2,0.25,0 + assertThat(AMPS_TO_SQUARED_MAGS_SHADER(input).readVecFloatOutputs(2)).isApproximatelyEqualTo(new Float32Array([ + 1, + 25, + 2, + 0.25, ]), 0.001); input.deallocByDepositingInPool(); }); -suite.testUsingWebGL("amplitudesToPolarKets_zero", () => { +suite.testUsingWebGL("AMPS_TO_ABS_MAGS_SHADER", () => { let input = Shaders.vec2Data(new Float32Array([ - 0,0 - ])).toVec2Texture(0); - assertThat(amplitudesToPolarKets(input).readVec4Outputs(0)).isEqualTo(new Float32Array([ - 0,0,0,0 - ])); + 1,0, + 3,4, + -1,1, + 0,0.5 + ])).toVec2Texture(2); + assertThat(AMPS_TO_ABS_MAGS_SHADER(input).readVecFloatOutputs(2)).isApproximatelyEqualTo(new Float32Array([ + 1, + 5, + Math.sqrt(2), + 0.5, + ]), 0.001); input.deallocByDepositingInPool(); }); -suite.testUsingWebGL("spreadLengthAcrossPolarKets", () => { - let inp = Shaders.vec4Data(new Float32Array([ - 1, 2,100,0, - 3, 4,200,0, - 5, 6,400,0, - 7, 8,800,0, - 9,10,1600,0, - 11,12,3200,0, - 13,14,6400,0, - 15,16,12800,0 - ])).toVec4Texture(3); - let out = inp.tradeThrough(t => spreadLengthAcrossPolarKets(t, 1), true); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 1, 2,300,0, - 3, 4,300,0, - 5, 6,1200,0, - 7, 8,1200,0, - 9,10,4800,0, - 11,12,4800,0, - 13,14,19200,0, - 15,16,19200,0 - ])); - out.deallocByDepositingInPool(); - - out = inp.tradeThrough(t => spreadLengthAcrossPolarKets(t, 2), true); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 1, 2,1500,0, - 3, 4,1500,0, - 5, 6,1500,0, - 7, 8,1500,0, - 9,10,24000,0, - 11,12,24000,0, - 13,14,24000,0, - 15,16,24000,0 - ])); - out.deallocByDepositingInPool(); - - - out = inp.tradeThrough(t => spreadLengthAcrossPolarKets(t, 3)); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 1, 2,25500,0, - 3, 4,25500,0, - 5, 6,25500,0, - 7, 8,25500,0, - 9,10,25500,0, - 11,12,25500,0, - 13,14,25500,0, - 15,16,25500,0 - ])); - out.deallocByDepositingInPool(); -}); - -suite.testUsingWebGL("reduceToLongestPolarKet", () => { - let inp = Shaders.vec4Data(new Float32Array([ - 1, 2,300,0, - 3, 4,300,0, - 5, 6,1200,0, - 7, 8,1200,0, - 9,10,4800,0, - 11,12,4800,0, - 13,14,19200,0, - 15,16,19200,0 - ])).toVec4Texture(3); - let out; - - out = inp.tradeThrough(t => reduceToLongestPolarKet(t, 1)); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 1+5+9+13,14,25500,0, 3+7+11+15,16,25500,0 - ])); - out.deallocByDepositingInPool(); - - inp = Shaders.vec4Data(new Float32Array([ - 1, 2,300,0, - 3, 4,300,0, - 55,6,120000,0, - 7, 8,1200,0, - 9,10,4800,0, - 11,12,4800,0, - 13,14,19200,0, - 15,16,19200,0 - ])).toVec4Texture(3); - out = inp.tradeThrough(t => reduceToLongestPolarKet(t, 1)); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 1+55+9+13,6,144300,0, - 3+7+11+15,16,25500,0 - ])); - out.deallocByDepositingInPool(); +suite.testUsingWebGL("MAGS_TO_INDEXED_MAGS_SHADER", () => { + let input = Shaders.floatData(new Float32Array([ + 2, + 3, + 5, + 7, + ])).toVecFloatTexture(2); + assertThat(MAGS_TO_INDEXED_MAGS_SHADER(input).readVec2Outputs(2)).isApproximatelyEqualTo(new Float32Array([ + 0, 2, + 1, 3, + 2, 5, + 3, 7, + ]), 0.001); + input.deallocByDepositingInPool(); }); -suite.testUsingWebGL("convertAwayFromPolar", () => { - let input = Shaders.vec4Data(new Float32Array([ - 1,0,3,0, - 25,new Complex(3,4).phase(),9,0, - 2,Math.PI*3/4,10,0, - 0.25,Math.PI/2,20,0 - ])).toVec4Texture(2); - assertThat(convertAwayFromPolar(input).readVec4Outputs(2)).isApproximatelyEqualTo(new Float32Array([ - 1,0,3,0, - 3,4,9,0, - -1,1,10,0, - 0,0.5,20,0 - ]), 0.0001); - +suite.testUsingWebGL("FOLD_MAX_INDEXED_MAG_SHADER", () => { + let input = Shaders.vec2Data(new Float32Array([ + 0, 4.2, + 8, 2.1, + 13, 1.5, + 23, 3.3, + ])).toVec2Texture(2); + assertThat(FOLD_MAX_INDEXED_MAG_SHADER(input).readVec2Outputs(1)).isApproximatelyEqualTo(new Float32Array([ + 0, 4.2, + 23, 3.3, + ]), 0.001); input.deallocByDepositingInPool(); }); -suite.testUsingWebGL("toRatiosVsRepresentative", () => { - let c = (r, i=0) => new Complex(r, i); - let inp = Shaders.vec2Data(new Float32Array([ - 1,0, 1,0, 1,0, 1,0, - 2,0, 3,0, 4,0, 5,0, - 1,2, 3,4, 5,6, 7,8, - 0,0, 0,-1, 0,-2, 0,0 +suite.testUsingWebGL("LOOKUP_KET_AT_INDEXED_MAG_SHADER", () => { + let input = Shaders.vec2Data(new Float32Array([ + 0, 1, 2, 3, 4, 5, 6, 7, + 2, 3, 5, 7, 11, 13, 17, 19, + 0, 1, 4, 9, 16, 25, 36, 49, + 1, 2, 4, 8, 16, 32, 64, 128, ])).toVec2Texture(4); - let rep = Shaders.vec4Data(new Float32Array([ - 1,0,0,0, 3,4,0,0, -0.5,0,0,0, 0,0.5,0,0 - ])).toVec4Texture(2); - assertThat(toRatiosVsRepresentative(inp, rep).readVec4Outputs(4)).isApproximatelyEqualTo(seq([ - c(1),c(1), c(1),c(3,4), c(1),c(-0.5), c(1),c(0,0.5), - c(2),c(1), c(3),c(3,4), c(4),c(-0.5), c(5),c(0,0.5), - c(1,2),c(1), c(3,4),c(3,4), c(5,6),c(-0.5), c(7,8),c(0,0.5), - c(0),c(1), c(0,-1),c(3,4), c(0,-2),c(-0.5), c(0),c(0,0.5) - ]).flatMap(e => [e.real, e.imag]).toFloat32Array(), 0.0001); - - inp.deallocByDepositingInPool(); - rep.deallocByDepositingInPool(); + let index = Shaders.vec2Data(new Float32Array([ + 1, 5000.3, + ])).toVec2Texture(0); + assertThat(LOOKUP_KET_AT_INDEXED_MAG_SHADER(input, index).readVec2Outputs(2)).isApproximatelyEqualTo(new Float32Array([ + 2, 3, 5, 7, 11, 13, 17, 19, + ]), 0.001); + index.deallocByDepositingInPool(); + input.deallocByDepositingInPool(); }); -suite.testUsingWebGL("foldConsistentRatios", () => { - let inp = Shaders.vec4Data(new Float32Array([ - 1,0,0,0, 2,0,0,0, - 0,0,1,0, 0,0,2,3, - 1,0,1,0, 2,0,2,0, - 1,0,3,0, 2,0,6,0, - 1,0,0,1, 0,-1,1,0, - 1,2,3,4, 2,-1,4,-3, - 1,2,3,4, 1,2,3,5, - 1,0,2,0, 1,0,3,0 - ])).toVec4Texture(4); - let out; - - out = inp.tradeThrough(t => foldConsistentRatios(t, 1), true); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 2,0,0,0, - 0,0,2,3, - 2,0,2,0, - 2,0,6,0, - 1,0,0,1, - 1,2,3,4, - -666,-666,-666,-666, - -666,-666,-666,-666 - ])); - out.deallocByDepositingInPool(); - - out = inp.tradeThrough(t => foldConsistentRatios(t, 2)); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - -666,-666,-666,-666, - -666,-666,-666,-666, - -666,-666,-666,-666, - -666,-666,-666,-666 - ])); - out.deallocByDepositingInPool(); - - inp = Shaders.vec4Data(new Float32Array([ - 1,0,0,0, 20,0,0,0, 0,0,0,0, 3,1,0,0, - 1,0,0,0, 0,0,2,0, 0,0,0,0, 1,0,0,0, - 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0, - -666,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0 - ])).toVec4Texture(4); - out = inp.tradeThrough(t => foldConsistentRatios(t, 2)); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 20,0,0,0, - -666,-666,-666,-666, - 0,0,0,0, - -666,-666,-666,-666 - ])); - out.deallocByDepositingInPool(); +suite.testUsingWebGL("POINTWISE_CMUL_SHADER", () => { + let small_input = Shaders.vec2Data(new Float32Array([ + 1, 2, + ])).toVec2Texture(0); + let large_input = Shaders.vec2Data(new Float32Array([ + 0, 1, 2, 3, 4, 5, 6, 7, + 2, 3, 5, 7, 11, 13, 17, 19, + 0, 1, 4, 9, 16, 25, 36, 49, + 1, 2, 4, 8, 16, 32, 64, 128, + ])).toVec2Texture(4); + assertThat(POINTWISE_CMUL_SHADER(small_input, large_input).readVec2Outputs(4)).isApproximatelyEqualTo(new Float32Array([ + -2, 1, -4, 7, -6, 13, -8, 19, + -4, 7, -9, 17, -15, 35, -21, 53, + -2, 1, -14, 17, -34, 57, -62, 121, + -3, 4, -12, 16, -48, 64, -192, 256 + ]), 0.001); + small_input.deallocByDepositingInPool(); + large_input.deallocByDepositingInPool(); }); -suite.testUsingWebGL("pipelineToSumAll", () => { - let inp = Shaders.vec4Data(new Float32Array([ - 2,0,0,0, - 0,0,2,3, - 2,0,2,0, - 2,0,6,0, - 1,0,0,1, - 1,2,3,4, - 1,2,3,4, - 1,2,3,4 - ])).toVec4Texture(3); - let out; - - out = inp.tradeThrough(signallingSumAll); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - 10,6,19,16 +suite.testUsingWebGL("makeAmplitudeSpanPipeline_coherent", () => { + let inp = Shaders.vec2Data(new Float32Array([0.6,0.8, 0,0, 0,0, 0,0])). + toVec2Texture(2); + let controlTex = CircuitShaders.controlMask(Controls.NONE).toBoolTexture(2); + let [ketData, qualityData, incoherentKetData] = amplitudeDisplayStatTextures(inp, Controls.NONE, controlTex, 0, 2); + let ket = currentShaderCoder().vec4.pixelsToData(ketData.readPixels()); + let quality = currentShaderCoder().float.pixelsToData(qualityData.readPixels()); + let incoherentKet = currentShaderCoder().vec4.pixelsToData(incoherentKetData.readPixels()); + assertThat(ket).isApproximatelyEqualTo(new Float32Array([ + 0.6,0.8, + 0,0, + 0,0, + 0,0 ])); - out.deallocByDepositingInPool(); -}); - -suite.testUsingWebGL("pipelineToSumAll_signal", () => { - let inp = Shaders.vec4Data(new Float32Array([ - 2,0,0,0, - 0,0,2,3, - 2,0,2,0, - -666.0,-666.0,-666.0,-666.0, - -666.0,-666.0,-666.0,-666.0, - 1,2,3,4, - 1,2,3,4, - 1,2,3,4 - ])).toVec4Texture(3); - let out; - - out = inp.tradeThrough(signallingSumAll); - assertThat(currentShaderCoder().vec4.pixelsToData(out.readPixels())).isEqualTo(new Float32Array([ - -666.0,-666.0,-666.0,-666.0 + assertThat(incoherentKet).isApproximatelyEqualTo(new Float32Array([ + 1, + 0, + 0, + 0, ])); - out.deallocByDepositingInPool(); -}); - -suite.testUsingWebGL("makeAmplitudeSpanPipeline_OffOff", () => { - let inp = Shaders.vec2Data(new Float32Array([1,0, 0,0, 0,0, 0,0])). - toVec2Texture(2); - let [afterPolar, final] = amplitudeDisplayStatTextures(inp, Controls.NONE, 0, 2); - assertThat(currentShaderCoder().vec4.pixelsToData(afterPolar.readPixels())).isEqualTo( - new Float32Array([1,0,1,0, 0,0,1,0, 0,0,1,0, 0,0,1,0])); - assertThat(currentShaderCoder().vec4.pixelsToData(final.readPixels())).isEqualTo(new Float32Array([1,0,1,0])); + assertThat(quality).isApproximatelyEqualTo(new Float32Array([1])); inp.deallocByDepositingInPool(); - afterPolar.deallocByDepositingInPool(); - final.deallocByDepositingInPool(); + controlTex.deallocByDepositingInPool(); + ketData.deallocByDepositingInPool(); + qualityData.deallocByDepositingInPool(); + incoherentKetData.deallocByDepositingInPool(); }); diff --git a/test/webgl/Shaders.test.js b/test/webgl/Shaders.test.js index f2df3d28..2f8f729c 100644 --- a/test/webgl/Shaders.test.js +++ b/test/webgl/Shaders.test.js @@ -90,6 +90,25 @@ suite.testUsingWebGL("data-bytes", () => { }); suite.testUsingWebGL("sumFold", () => { + let raws = makePseudoShaderWithInputsAndOutputAndCode([], Outputs.float(), ` + float outputFor(float k) { + return k*k; + } + `)().toVecFloatTexture(3); + assertThat(Shaders.sumFoldFloat(raws).readVecFloatOutputs(2)).isEqualTo(new Float32Array([ + 16, + 1+25, + 4+36, + 9+49 + ])); + assertThat(Shaders.sumFoldFloatAdjacents(raws).readVecFloatOutputs(2)).isEqualTo(new Float32Array([ + 1, + 4+9, + 16+25, + 36+49 + ])); + raws.deallocByDepositingInPool(); + let coords = makePseudoShaderWithInputsAndOutputAndCode([], Outputs.vec2(), ` vec2 outputFor(float k) { return vec2(mod(k, 2.0), floor(k/2.0));