diff --git a/src/circuit/Serializer.js b/src/circuit/Serializer.js index e9d0e3f5..ca6cffeb 100644 --- a/src/circuit/Serializer.js +++ b/src/circuit/Serializer.js @@ -157,7 +157,7 @@ let toJson_Gate = (gate, context=new CustomGateSet()) => { } else if (gate.knownCircuit !== undefined) { result.circuit = toJson_CircuitDefinition(gate.knownCircuit, context); } else { - throw new DetailedError("Don't known how to serialize gate's function.", {gate}); + throw new DetailedError("Don't know how to serialize gate's function.", {gate}); } return result; diff --git a/src/draw/GatePainting.js b/src/draw/GatePainting.js index 9f1d07cd..a0c8df93 100644 --- a/src/draw/GatePainting.js +++ b/src/draw/GatePainting.js @@ -527,4 +527,36 @@ GatePainting.PERMUTATION_DRAWER = args => { } }; +/** + * Draws the gate as a re-arrangement of wires. + * @param {!GateDrawParams} args + * @param {!Array.} offsets + */ +GatePainting.DrawPerturbedWires = (args, offsets) => { + _eraseWiresForPermutation(args); + + // Draw wires. + let x1 = args.rect.x; + let x2 = args.rect.x + 3; + let x3 = args.rect.right() - 3; + let x4 = args.rect.right(); + args.painter.ctx.strokeStyle = 'black'; + for (let i = 0; i < args.gate.height; i++) { + let pt = new Point(args.positionInCircuit.col, args.positionInCircuit.row + i); + let isMeasured = args.stats.circuitDefinition.locIsMeasured(pt); + let y = _wireY(args, i); + let yt = _wireY(args, i + offsets[i]); + args.painter.ctx.beginPath(); + for (let [dx, dy] of isMeasured ? [[-1, -1], [0, +1]] : [[0, 0]]) { + args.painter.ctx.moveTo(Math.min(x1, x1 + dx), y + dy); + args.painter.ctx.lineTo(x1 + dx, y + dy); + args.painter.ctx.lineTo(x2 + dx, yt + dy); + args.painter.ctx.lineTo(x3 + dx, yt + dy); + args.painter.ctx.lineTo(x4 + dx, y + dy); + args.painter.ctx.lineTo(Math.max(x4, x4 + dx), y + dy); + } + args.painter.ctx.stroke(); + } +}; + export {GatePainting} diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 563758ca..f8b8a5d7 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -21,6 +21,7 @@ import {Controls} from "src/gates/Controls.js" import {CountingGates} from "src/gates/CountingGates.js" import {CycleBitsGates} from "src/gates/CycleBitsGates.js" import {DensityMatrixDisplayFamily} from "src/gates/DensityMatrixDisplay.js" +import {EntanglementDisplays} from "src/gates/EntanglementDisplays.js" import {ErrorInjectionGate} from "src/gates/Debug_ErrorInjectionGate.js" import {ExponentiatingGates} from "src/gates/ExponentiatingGates.js" import {FourierTransformGates} from "src/gates/FourierTransformGates.js" @@ -89,6 +90,7 @@ Gates.CycleBitsGates = CycleBitsGates; Gates.Displays.DensityMatrixDisplay = DensityMatrixDisplayFamily.ofSize(1); Gates.Displays.DensityMatrixDisplay2 = DensityMatrixDisplayFamily.ofSize(2); Gates.Displays.ChanceDisplay = Gates.Displays.ProbabilityDisplayFamily.ofSize(1); +Gates.EntanglementDisplays = EntanglementDisplays; Gates.ErrorInjection = ErrorInjectionGate; Gates.Exponentiating = ExponentiatingGates; Gates.FourierTransformGates = FourierTransformGates; @@ -150,6 +152,7 @@ Gates.KnownToSerializer = [ ...ComparisonGates.all, ...CountingGates.all, ...CycleBitsGates.all, + ...EntanglementDisplays.all, ...Detectors.all, ...ExponentiatingGates.all, ...FourierTransformGates.all, @@ -198,7 +201,7 @@ Gates.TopToolboxGroups = [ { hint: "Displays", gates: [ - undefined, undefined, + EntanglementDisplays.EntanglementDisplay, undefined, DensityMatrixDisplayFamily.ofSize(1), BlochSphereDisplay, ProbabilityDisplayFamily.ofSize(1), AmplitudeDisplayFamily.ofSize(2) ] diff --git a/src/gates/DensityMatrixDisplay.js b/src/gates/DensityMatrixDisplay.js index 78ffb2c3..621148b2 100644 --- a/src/gates/DensityMatrixDisplay.js +++ b/src/gates/DensityMatrixDisplay.js @@ -207,4 +207,4 @@ let DensityMatrixDisplayFamily = Gate.buildFamily(1, 8, (span, builder) => span === 1 ? singleDensityMatrixDisplayMaker(builder) : largeDensityMatrixDisplayMaker(span, builder)); -export {DensityMatrixDisplayFamily, amplitudesToCouplings} +export {DensityMatrixDisplayFamily, amplitudesToCouplings, densityDisplayStatTexture, densityPixelsToMatrix} diff --git a/src/gates/EntanglementDisplays.js b/src/gates/EntanglementDisplays.js new file mode 100644 index 00000000..ef5127ee --- /dev/null +++ b/src/gates/EntanglementDisplays.js @@ -0,0 +1,213 @@ +import {Complex} from "src/math/Complex.js" +import {GateBuilder} from "src/circuit/Gate.js" +import {GatePainting} from "src/draw/GatePainting.js" +import {MathPainter} from "src/draw/MathPainter.js" +import {Matrix} from "src/math/Matrix.js" +import {seq} from "src/base/Seq.js" +import {densityDisplayStatTexture, densityPixelsToMatrix} from "src/gates/DensityMatrixDisplay.js" + +let EntanglementDisplays = {}; + +/** + * @param {!Painter} painter + * @param {!Matrix} xyz + * @param {!Rect} drawArea + * @param {!string} fillColor + * @param {!string} label + * @param scale + * @param {!Matrix} xyz_center + */ +function _paintBlochIndicator( + painter, + xyz, + drawArea, + fillColor, + label, + scale=1, + xyz_center=Matrix.col(0, 0, 0)) { + let x = xyz.rawBuffer()[0]; + let y = xyz.rawBuffer()[2]; + let z = xyz.rawBuffer()[4]; + let cx = xyz_center.rawBuffer()[0]; + let cy = xyz_center.rawBuffer()[2]; + let cz = xyz_center.rawBuffer()[4]; + let origin = drawArea.center(); + let u = Math.min(drawArea.w, drawArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + dy = dy.times(-1); + + let c = origin.plus(dx.times(cx)).plus(dy.times(cy)).plus(dz.times(cz)); + let p = origin.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); + let r = 18 / (2 + drawArea.w/32 * x / 6); + r *= scale; + + // Draw state indicators (in not-quite-correct 3d). + painter.strokeLine(c, p, 'black', 2); + painter.fillCircle(p, r, fillColor); + painter.strokeCircle(p, r, 'black'); + + painter.print( + label, + p.x, + p.y + 1, + 'center', + 'middle', + 'black', + '36pt sans-serif', + r*2.5, + r*2.5); +} + +/** + * @param {!Matrix} twoQubitDensityMatrix + * @returns {!Matrix} + */ +function traceOutSecondQubit(twoQubitDensityMatrix) { + return Matrix.square( + twoQubitDensityMatrix.cell(0, 0).plus(twoQubitDensityMatrix.cell(2, 2)), + twoQubitDensityMatrix.cell(1, 0).plus(twoQubitDensityMatrix.cell(3, 2)), + twoQubitDensityMatrix.cell(0, 1).plus(twoQubitDensityMatrix.cell(2, 3)), + twoQubitDensityMatrix.cell(1, 1).plus(twoQubitDensityMatrix.cell(3, 3))); +} + +/** + * @param {!Matrix} twoQubitDensityMatrix + * @returns {!Matrix} + */ +function blochVectorAfterTracingOutSecondQubit(twoQubitDensityMatrix) { + return Matrix.col(...traceOutSecondQubit(twoQubitDensityMatrix).qubitDensityMatrixToBlochVector()); +} + +/** + * @param {!Matrix} twoQubitDensityMatrix + * @param {!Matrix} desiredFirstQubitColVector + * @returns {!{successProbability: !number, densityMatrix: !Matrix}} + */ +function projectSecondQubit(twoQubitDensityMatrix, desiredFirstQubitColVector) { + // Normalize input. + let u = desiredFirstQubitColVector.times(1 / Math.sqrt(desiredFirstQubitColVector.norm2())); + + // Project. + let expanded_state = u.tensorProduct(Matrix.identity(2)); + let projector = expanded_state.times(expanded_state.adjoint()); + let projected_density = twoQubitDensityMatrix.times(projector); + + // Normalize output. + let successProbability = projected_density.trace().abs(); + if (successProbability < 0.0001) { + return {successProbability, densityMatrix: Matrix.generateDiagonal(4, _ => 0.25)}; + } + + // A blatant violation of proper naming practices. + return {successProbability, densityMatrix: projected_density.times(1 / successProbability)}; +} + +/** + * @param {!Matrix} twoQubitDensityMatrix + * @param {!Matrix} desiredFirstQubitColVector + * @returns {!Matrix} + */ +function secondQubitBlochVectorConditioningOnFirstQubitStateVector(twoQubitDensityMatrix, desiredFirstQubitColVector) { + let {successProbability, densityMatrix} = projectSecondQubit(twoQubitDensityMatrix, desiredFirstQubitColVector); + if (successProbability < 0.0001) { + return undefined; + } + return blochVectorAfterTracingOutSecondQubit(densityMatrix); +} + +EntanglementDisplays.EntanglementDisplay = new GateBuilder(). + setSerializedId('entdisp'). + setSymbol('Entang'). + setTitle('Entanglement Display'). + setBlurb('Shows the Bloch vectors of one qubit after respectively conditioning on the |0⟩, |1⟩, |+⟩, |-⟩, |i⟩, and |-i⟩ states of another.'). + setWidth(2). + setHeight(2). + promiseHasNoNetEffectOnStateVector(). + setDrawer(GatePainting.makeDisplayDrawer(args => { + let twoQubitDensityMatrix = args.customStats || Matrix.zero(4, 4).times(NaN); + + for (let dx of [0]) { + args.painter.strokeLine(args.rect.topHalf().center().offsetBy(dx, 0), args.rect.bottomHalf().center().offsetBy(dx, 0)); + } + let topArea = args.rect.takeTopProportion(0.95); + let c = topArea.center(); + let u = Math.min(topArea.w, topArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + + // Draw redirected wires and for-all control. + GatePainting.DrawPerturbedWires(args, [0.48, 0.4]); + let vc = args.rect.takeBottomProportion(0.01).takeLeftProportion(0.25).center(); + args.painter.strokeLine(vc, c, 'black'); + args.painter.fillCircle(vc, 6, '#EFF'); + args.painter.strokeCircle(vc, 6, 'black'); + args.painter.print('∀', vc.x, vc.y, 'center', 'middle', 'black', '16pt monospace', 20, 20); + + // Draw big blue sphere and axis lines (in not-quite-proper 3d). + args.painter.fillCircle(c, u, '#EFF'); + args.painter.trace(trace => { + trace.circle(c.x, c.y, u); + trace.ellipse(c.x, c.y, dy.x, dx.y); + trace.ellipse(c.x, c.y, dx.x, dz.y); + for (let d of [dx, dy, dz]) { + trace.line(c.x - d.x, c.y - d.y, c.x + d.x, c.y + d.y); + } + }).thenStroke('#BBB'); + + if (twoQubitDensityMatrix.hasNaN()) { + args.painter.print( + 'NaN', + args.rect.x + args.rect.w/2, + args.rect.y + args.rect.h/2, + 'center', + 'middle', + 'red', + '16px sans-serif', + args.rect.w, + args.rect.h); + return; + } + + let [x, y, z] = traceOutSecondQubit(twoQubitDensityMatrix).qubitDensityMatrixToBlochVector(); + let cen = Matrix.col(x, y, z); + let s = Math.sqrt(0.5); + let draws = []; + let kets = [ + [Matrix.col(s, s), '+', '#FAA', 0.7], + [Matrix.col(s, -s), '-', '#FAA', 0.7], + [Matrix.col(s, new Complex(0, s)), 'i', '#AFA', 0.7], + [Matrix.col(s, new Complex(0, -s)), '-i', '#AFA', 0.7], + [Matrix.col(1, 0), '0', '#AAF', 0.7], + [Matrix.col(0, 1), '1', '#AAF', 0.7], + [undefined, '', '#FFF', 0.5], + ]; + let kk = 0; + for (let [ket, label, color, scale] of kets) { + kk += 1; + let xyz = ket !== undefined ? secondQubitBlochVectorConditioningOnFirstQubitStateVector(twoQubitDensityMatrix, ket) : Matrix.col(x, y, z); + if (xyz === undefined) { + continue; + } + draws.push([ + -xyz.cell(0, 0).real + kk/1000, () => _paintBlochIndicator( + args.painter, + xyz, + topArea, + color, + label, + scale, + ket === undefined ? undefined : cen)]); + } + for (let [_, f] of seq(draws).sortedBy(e => e[0])) { + f(); + } + })). + setStatTexturesMaker(ctx => densityDisplayStatTexture( + ctx.stateTrader.currentTexture, ctx.wireCount, ctx.controls, ctx.row, 2)). + setStatPixelDataPostProcessor(densityPixelsToMatrix). + gate; + +EntanglementDisplays.all = [ + EntanglementDisplays.EntanglementDisplay, +]; + +export {EntanglementDisplays} diff --git a/test/circuit/Serializer.test.js b/test/circuit/Serializer.test.js index ba4b7902..bfe632c0 100644 --- a/test/circuit/Serializer.test.js +++ b/test/circuit/Serializer.test.js @@ -211,6 +211,7 @@ const IDS_THAT_SHOULD_BE_KNOWN = [ "Chance", "Chance2", "Chance3", "Chance4", "Chance5", "Chance6", "Chance7", "Chance8", "Chance9", "Chance10", "Chance11", "Chance12", "Chance13", "Chance14", "Chance15", "Chance16", "Sample1", "Sample2", "Sample3", "Sample4", "Sample5", "Sample6", "Sample7", "Sample8", "Sample9", "Sample10", "Sample11", "Sample12", "Sample13", "Sample14", "Sample15", "Sample16", "Density", "Density2", "Density3", "Density4", "Density5", "Density6", "Density7", "Density8", + "entdisp", "Bloch", "inc1", "inc2", "inc3", "inc4", "inc5", "inc6", "inc7", "inc8", "inc9", "inc10", "inc11", "inc12", "inc13", "inc14", "inc15", "inc16", "dec1", "dec2", "dec3", "dec4", "dec5", "dec6", "dec7", "dec8", "dec9", "dec10", "dec11", "dec12", "dec13", "dec14", "dec15", "dec16", diff --git a/test/gates/AllGates.test.js b/test/gates/AllGates.test.js index 0c8c9861..ec12afa1 100644 --- a/test/gates/AllGates.test.js +++ b/test/gates/AllGates.test.js @@ -200,6 +200,7 @@ suite.test("knownDoNothingGateFamilies", () => { 'Sample1', 'Density', 'Bloch', + 'entdisp', // Spacer gate. '…' ]));