From e492a9e4a7a85e24801cb64a74250e512ae28528 Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sun, 6 Aug 2017 16:01:21 -0700 Subject: [PATCH 1/3] Iterating on 2-qubit entangled display design - Added six temporary prototypes to the toolbox - Iter 1: svd into separable/entangled, draw separable as bloch vectors and entangled as rotation axes - Iter 2: Drop seperable part of iter 1, label axes better - Iter 3: Point cloud of where qubit #1 is for various projections of qubit #2 - Iter 4: Scaling iter 3 points by success probability of projections - Iter 5: Scaling iter 3 points by purity-increase of projections - Iter 6: Polished iter 3 with axis-labelled points and control/target asymmetry --- src/circuit/Serializer.js | 2 +- src/gates/AllGates.js | 13 +- src/gates/DensityMatrixDisplay.js | 2 +- src/gates/EntanglementDisplays.js | 414 ++++++++++++++++++++++++++++++ 4 files changed, 428 insertions(+), 3 deletions(-) create mode 100644 src/gates/EntanglementDisplays.js diff --git a/src/circuit/Serializer.js b/src/circuit/Serializer.js index effc09f9..2d6596a7 100644 --- a/src/circuit/Serializer.js +++ b/src/circuit/Serializer.js @@ -143,7 +143,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/gates/AllGates.js b/src/gates/AllGates.js index 444a9781..42a89142 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -7,6 +7,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" @@ -70,6 +71,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; @@ -122,6 +124,7 @@ Gates.KnownToSerializer = [ ...ComparisonGates.all, ...CountingGates.all, ...CycleBitsGates.all, + ...EntanglementDisplays.all, ...ExponentiatingGates.all, ...FourierTransformGates.all, ...HalfTurnGates.all, @@ -229,7 +232,15 @@ Gates.TopToolboxGroups = [ NeGate, undefined, SpacerGate, undefined ] - } + }, + { + hint: 'Test', + gates: [ + EntanglementDisplays.Iteration1, EntanglementDisplays.Iteration2, + EntanglementDisplays.Iteration3, EntanglementDisplays.Iteration4, + EntanglementDisplays.Iteration5, EntanglementDisplays.Iteration6, + ] + }, ]; /** @type {!Array}>} */ diff --git a/src/gates/DensityMatrixDisplay.js b/src/gates/DensityMatrixDisplay.js index 61f30e65..40c9f2ab 100644 --- a/src/gates/DensityMatrixDisplay.js +++ b/src/gates/DensityMatrixDisplay.js @@ -190,4 +190,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..ff22c050 --- /dev/null +++ b/src/gates/EntanglementDisplays.js @@ -0,0 +1,414 @@ +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 {densityDisplayStatTexture, densityPixelsToMatrix} from "src/gates/DensityMatrixDisplay.js" + +let EntanglementDisplays = {}; + +/** + * @param {!Painter} painter + * @param {!number} x + * @param {!number} y + * @param {!number} z + * @param {!Rect} drawArea + * @param {!string=} fillColor + */ +function _paintBlochSphereDisplay_indicator( + painter, + [x, y, z], + drawArea, + fillColor) { + let c = drawArea.center(); + let u = Math.min(drawArea.w, drawArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + + let p = c.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); + let r = 3.8 / (1 + drawArea.w/32 * x / 6); + + // Draw state indicators (in not-quite-correct 3d). + painter.strokeLine(c, p, 'black', 1.5); + painter.fillCircle(p, r, fillColor); + painter.strokeCircle(p, r, 'black'); + + // Show depth by lerping the line from overlaying to being overlayd by the ball. + painter.ctx.save(); + painter.ctx.globalAlpha *= Math.min(1, Math.max(0, 0.5+x*5)); + painter.strokeLine(c, p, 'black', 2); + painter.ctx.restore(); +} + +/** + * @param {!Painter} painter + * @param {!Matrix} xyz + * @param {!Rect} drawArea + * @param {!string} fillColor + * @param {!string} label + * @param scale + * @param no_scale + */ +function _paintBlochSphereDisplay_indicator2( + painter, + xyz, + drawArea, + fillColor, + label, + scale=1, + no_scale=false) { + let x = xyz.rawBuffer()[0]; + let y = xyz.rawBuffer()[2]; + let z = xyz.rawBuffer()[4]; + let c = drawArea.center(); + let u = Math.min(drawArea.w, drawArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + + let p = c.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); + let r = 7 / (1 + drawArea.w/32 * x / 6); + r *= (no_scale ? 1 : Math.sqrt(Math.sqrt(x*x + y*y + z*z))) * 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'); + + // Show depth by lerping the line from overlaying to being overlayd by the ball. + painter.ctx.save(); + painter.ctx.globalAlpha *= Math.min(1, Math.max(0, 0.5 + x * 5)); + painter.strokeLine(c, p, 'black', 2); + painter.ctx.restore(); + + painter.print(label, p.x, p.y, 'center', 'middle', 'black', '36pt monospace', r*3, r*3); +} +/** + * @param {!Painter} painter + * @param {!Matrix} point + * @param {!Rect} drawArea + * @param {!string=} fillColor + */ +function _paintPoint3D(painter, point, drawArea, fillColor) { + let x = point.cell(0, 0).real; + let y = point.cell(0, 1).real; + let z = point.cell(0, 2).real; + let c = drawArea.center(); + let u = Math.min(drawArea.w, drawArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + let p = c.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); + let r = 8 / (1 + drawArea.w/32 * x / 8); + r *= 0.2; + painter.fillCircle(p, r, fillColor); +} + +/** + * @param {!Painter} painter + * @param {!Rect} drawArea + */ +function _paintBackground(painter, drawArea) { + let c = drawArea.center(); + let u = Math.min(drawArea.w, drawArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + + // Draw sphere and axis lines (in not-quite-proper 3d). + painter.fillCircle(c, u, '#EFF'); + 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'); +} + +/** + * @param {!Matrix} two_qubit_density + * @returns {!Matrix} + */ +function density_qubit_2(two_qubit_density) { + return Matrix.square( + two_qubit_density.cell(0, 0).plus(two_qubit_density.cell(2, 2)), + two_qubit_density.cell(1, 0).plus(two_qubit_density.cell(3, 2)), + two_qubit_density.cell(0, 1).plus(two_qubit_density.cell(2, 3)), + two_qubit_density.cell(1, 1).plus(two_qubit_density.cell(3, 3))); +} + +/** + * @param {!Matrix} two_qubit_density + * @returns {!Matrix} + */ +function density_qubit_1(two_qubit_density) { + return Matrix.square( + two_qubit_density.cell(0, 0).plus(two_qubit_density.cell(1, 1)), + two_qubit_density.cell(2, 0).plus(two_qubit_density.cell(3, 1)), + two_qubit_density.cell(0, 2).plus(two_qubit_density.cell(1, 3)), + two_qubit_density.cell(2, 2).plus(two_qubit_density.cell(3, 3))); +} + +/** + * @param {!Matrix} two_qubit_density + * @returns {!Matrix} + */ +function bloch_vector_of_2nd_qubit(two_qubit_density) { + return Matrix.col(...density_qubit_2(two_qubit_density).qubitDensityMatrixToBlochVector()); +} + +/** + * @param {!Matrix} two_qubit_density + * @param {!Matrix} required_1st_qubit_amps + * @returns {!{p: !number, ρ: !Matrix}} + */ +function density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps) { + // Normalize input. + let u = required_1st_qubit_amps.times(1 / Math.sqrt(required_1st_qubit_amps.norm2())); + + // Project. + let expanded_state = u.tensorProduct(Matrix.identity(2)); + let projector = expanded_state.times(expanded_state.adjoint()); + let projected_density = two_qubit_density.times(projector); + + // Normalize output. + let p = projected_density.trace().abs(); + if (p < 0.0001) { + return {p, ρ: Matrix.generateDiagonal(4, _ => 0.25)}; + } + + // A blatant violation of proper naming practices. + return {p, ρ: projected_density.times(1 / p)}; +} + +function second_qubit_point_given_first(two_qubit_density, required_1st_qubit_amps) { + let density_given_q1 = density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps).ρ; + return bloch_vector_of_2nd_qubit(density_given_q1); +} + +function second_qubit_point_given_first_purity_scaled(two_qubit_density, required_1st_qubit_amps) { + let projected_density = density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps).ρ; + let v_pre = bloch_vector_of_2nd_qubit(two_qubit_density); + let v_post = bloch_vector_of_2nd_qubit(projected_density); + + let purity_pre = Math.sqrt(v_pre.norm2()); + let purity_post = Math.sqrt(v_post.norm2()); + let purity_increase = purity_post - purity_pre; + + return v_post.times(purity_increase); +} + +function second_qubit_point_given_first_trace_scaled(two_qubit_density, required_1st_qubit_amps) { + let {p, ρ} = density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps); + let v_pre = bloch_vector_of_2nd_qubit(two_qubit_density); + let v_post = bloch_vector_of_2nd_qubit(ρ); + return v_post.times(p).times(2).minus(v_pre); +} + +function eigenDecompose(hermitian) { + let results = []; + while (results.length < 4 && hermitian.norm2() > 0.001) { + let v = Matrix.generate(1, 4, () => new Complex(Math.random(), Math.random())); + let m = hermitian; + for (let k = 0; k < 4; k++) { + m = m.times(m); + } + for (let k = 0; k < 20; k++) { + v = m.times(v); + v = v.times(1 / Math.sqrt(v.norm2())); + } + let w = hermitian.times(v).norm2(); + results.push({vec: v, val: w}); + hermitian = hermitian.minus(v.times(v.adjoint()).times(w)); + } + return results; +} + +function paintAxes1(painter, density, drawArea) { + let amps_matrix = new Matrix(2, 2, eigenDecompose(density)[0].vec.rawBuffer()); + let {U, S, V} = amps_matrix.singularValueDecomposition(); + let s1 = S.cell(1, 1).real; + let factored_state_1 = density_qubit_1(density); + let factored_state_2 = density_qubit_2(density); + + let weight_entangled = 2*s1*s1; + let entangled_state = U.times(V); + + _paintBlochSphereDisplay_indicator(painter, factored_state_1.qubitDensityMatrixToBlochVector(), drawArea, 'black'); + _paintBlochSphereDisplay_indicator(painter, factored_state_2.qubitDensityMatrixToBlochVector(), drawArea, 'white'); + + let rr = drawArea.scaledOutwardBy(Math.sqrt(weight_entangled)); + let xyz = entangled_state.times(Matrix.square(1, 0, 0, 0)).times(entangled_state.adjoint()).qubitDensityMatrixToBlochVector(); + _paintBlochSphereDisplay_indicator(painter, xyz, rr, 'red'); + xyz = entangled_state.times(Matrix.square(0.5, 0.5, 0.5, 0.5)).times(entangled_state.adjoint()).qubitDensityMatrixToBlochVector(); + _paintBlochSphereDisplay_indicator(painter, xyz, rr, 'green'); + xyz = entangled_state.times(Matrix.square(0.5, new Complex(0, -0.5), new Complex(0, 0.5), 0.5)).times(entangled_state.adjoint()).qubitDensityMatrixToBlochVector(); + _paintBlochSphereDisplay_indicator(painter, xyz, rr, 'blue'); +} + +function paintAxes2(painter, density, drawArea) { + let vx = Matrix.col(0, 0, 0); + let vy = Matrix.col(0, 0, 0); + let vz = Matrix.col(0, 0, 0); + + for (let eigen of eigenDecompose(density)) { + let M = new Matrix(2, 2, eigen.vec.rawBuffer()); + let {U, S, V} = M.singularValueDecomposition(); + let s = S.cell(1, 1).real; + let weight = 2*s*s; + let E = U.times(V); + let state_to_xyz_contribution = (...vec_raw) => { + let vec = Matrix.col(...vec_raw); + let unit_vec = vec.times(1 / Math.sqrt(vec.norm2())); + let mat = unit_vec.times(unit_vec.adjoint()); + let mat2 = E.times(mat).times(E.adjoint()); + let xyz = mat2.qubitDensityMatrixToBlochVector(); + return Matrix.col(...xyz).times(weight * eigen.val); + }; + vx = vx.plus(state_to_xyz_contribution(1, 1)); + vy = vy.plus(state_to_xyz_contribution(1, Complex.I)); + vz = vz.plus(state_to_xyz_contribution(1, 0)); + } + + _paintBlochSphereDisplay_indicator2(painter, vy, drawArea, '#AFA', 'y'); + _paintBlochSphereDisplay_indicator2(painter, vz, drawArea, '#AAF', 'z'); + _paintBlochSphereDisplay_indicator2(painter, vx, drawArea, '#FAA', 'x'); +} + +/** + * @param {!Painter} painter + * @param {!Matrix} stateDensityMatrix + * @param {!Rect} drawArea + * @param {!function(!Matrix, !Matrix): !Matrix} pointPicker + */ +function paintStatePointCloud(painter, stateDensityMatrix, drawArea, pointPicker) { + for (let pitch = 0.01; pitch < Math.PI / 2; pitch += 0.15) { + let amp0 = Math.cos(pitch); + let amp1_abs = Math.sin(pitch); + let c2 = ''+Math.floor(pitch / Math.PI * 2 * 80 + 10); + let dr = 0.15 / Math.sin(pitch * 2); + for (let yaw = 0.0001; yaw < Math.PI * 2; yaw += dr) { + let c1 = ''+Math.floor(yaw / Math.PI / 2 * 80 + 10); + let color = '#' + c2 + c1 + '00'; + let amp1 = Complex.polar(amp1_abs, yaw); + let pt = pointPicker(stateDensityMatrix, Matrix.col(amp0, amp1)); + _paintPoint3D(painter, pt, drawArea, color); + } + } +} + +function make_density_drawer(id, drawer) { + return new GateBuilder(). + setSerializedIdAndSymbol(id). + setWidth(2). + setHeight(2). + promiseHasNoNetEffectOnStateVector(). + setDrawer(GatePainting.makeDisplayDrawer(args => { + let ρ = args.customStats || Matrix.zero(4, 4).times(NaN); + if (ρ.hasNaN()) { + MathPainter.paintDensityMatrix(args.painter, ρ, args.rect, args.focusPoints); + return; + } + _paintBackground(args.painter, args.rect); + drawer(args, ρ); + })). + setStatTexturesMaker(ctx => densityDisplayStatTexture( + ctx.stateTrader.currentTexture, ctx.wireCount, ctx.controls, ctx.row, 2)). + setStatPixelDataPostProcessor(densityPixelsToMatrix). + gate; +} + +EntanglementDisplays.Iteration1 = make_density_drawer("iter1", (args, ρ) => + paintAxes1(args.painter, ρ, args.rect)); + +EntanglementDisplays.Iteration2 = make_density_drawer("iter2", (args, ρ) => + paintAxes2(args.painter, ρ, args.rect)); + +EntanglementDisplays.Iteration3 = make_density_drawer("iter3", (args, ρ) => + paintStatePointCloud(args.painter, ρ, args.rect, second_qubit_point_given_first)); + +EntanglementDisplays.Iteration4 = make_density_drawer("iter4", (args, ρ) => + paintStatePointCloud(args.painter, ρ, args.rect, second_qubit_point_given_first_trace_scaled)); + +EntanglementDisplays.Iteration5 = make_density_drawer("iter5", (args, ρ) => + paintStatePointCloud(args.painter, ρ, args.rect, second_qubit_point_given_first_purity_scaled)); + +EntanglementDisplays.Iteration6 = new GateBuilder(). + setSerializedIdAndSymbol('iter6'). + setWidth(1). + setHeight(2). + promiseHasNoNetEffectOnStateVector(). + setDrawer(GatePainting.makeDisplayDrawer(args => { + let ρ = args.customStats || Matrix.zero(4, 4).times(NaN); + if (ρ.hasNaN()) { + MathPainter.paintDensityMatrix(args.painter, ρ, args.rect, args.focusPoints); + return; + } + + 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.topHalf(); + let c = topArea.center(); + let u = Math.min(topArea.w, topArea.h) / 2; + let {dx, dy, dz} = MathPainter.coordinateSystem(u); + + // Draw 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'); + + let vc = args.rect.bottomHalf().center(); + args.painter.fillCircle(vc, 10, '#EFF'); + args.painter.strokeCircle(vc, 10, 'black'); + args.painter.print('∀', vc.x, vc.y, 'center', 'middle', 'black', '16pt monospace', 20, 20); + + paintStatePointCloud(args.painter, ρ, topArea, second_qubit_point_given_first); + + let pts = []; + for (let [yaw, pitch, label, color, scale] of [ + [Math.PI/2, -Math.PI/2, '', '#AFA', 0.3], + [0, -Math.PI/2, '', '#FAA', 0.3], + [0, Math.PI, '', '#AAF', 0.3], + [Math.PI/2, Math.PI/2, 'Y', '#AFA', 0.7], + [0, Math.PI/2, 'X', '#FAA', 0.7], + [0, 0, 'Z', '#AAF', 0.7], + ]) { + let amp0 = Math.cos(pitch/2); + let amp1_abs = Math.sin(pitch/2); + let amp1 = Complex.polar(amp1_abs, yaw); + let xyz = second_qubit_point_given_first(ρ, Matrix.col(amp0, amp1)); + if (xyz.norm2() > 0) { + pts.push(xyz); + _paintBlochSphereDisplay_indicator2(args.painter, xyz, topArea, color, label, scale); + } + } + let [x,y,z] = density_qubit_2(ρ).qubitDensityMatrixToBlochVector(); + let m = 0; + for (let e1 of pts) { + for (let e2 of pts) { + m += e1.minus(e2).norm2(); + } + } + + args.painter.ctx.save(); + args.painter.ctx.globalAlpha *= Math.max(0, 1 - m*10); + _paintBlochSphereDisplay_indicator2(args.painter, Matrix.col(x, y, z), topArea, 'white', '', 0.7, true); + args.painter.ctx.restore(); + })). + setStatTexturesMaker(ctx => densityDisplayStatTexture( + ctx.stateTrader.currentTexture, ctx.wireCount, ctx.controls, ctx.row, 2)). + setStatPixelDataPostProcessor(densityPixelsToMatrix). + gate; + +EntanglementDisplays.all = [ + EntanglementDisplays.Iteration1, + EntanglementDisplays.Iteration2, + EntanglementDisplays.Iteration3, + EntanglementDisplays.Iteration4, + EntanglementDisplays.Iteration5, + EntanglementDisplays.Iteration6, +]; + +export {EntanglementDisplays} From 77a938eca3120d4536094d4191e2cc687aaa3d5a Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Tue, 12 Sep 2017 22:37:45 -0700 Subject: [PATCH 2/3] Polishing iter 6 - Move axis-origin to center point - Nearly double size of ball by moving wires out of the way --- src/draw/GatePainting.js | 32 ++++++++++++++++++++++++ src/gates/EntanglementDisplays.js | 41 ++++++++++++++++++------------- 2 files changed, 56 insertions(+), 17 deletions(-) diff --git a/src/draw/GatePainting.js b/src/draw/GatePainting.js index 32ee7509..889301a6 100644 --- a/src/draw/GatePainting.js +++ b/src/draw/GatePainting.js @@ -494,4 +494,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/EntanglementDisplays.js b/src/gates/EntanglementDisplays.js index ff22c050..ebfdf23b 100644 --- a/src/gates/EntanglementDisplays.js +++ b/src/gates/EntanglementDisplays.js @@ -47,6 +47,7 @@ function _paintBlochSphereDisplay_indicator( * @param {!string} label * @param scale * @param no_scale + * @param {!Matrix} xyz_center */ function _paintBlochSphereDisplay_indicator2( painter, @@ -55,15 +56,20 @@ function _paintBlochSphereDisplay_indicator2( fillColor, label, scale=1, - no_scale=false) { + no_scale=false, + xyz_center=Matrix.col(0, 0, 0)) { let x = xyz.rawBuffer()[0]; let y = xyz.rawBuffer()[2]; let z = xyz.rawBuffer()[4]; - let c = drawArea.center(); + 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); - let p = c.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); + 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 = 7 / (1 + drawArea.w/32 * x / 6); r *= (no_scale ? 1 : Math.sqrt(Math.sqrt(x*x + y*y + z*z))) * scale; @@ -330,7 +336,7 @@ EntanglementDisplays.Iteration5 = make_density_drawer("iter5", (args, ρ) => EntanglementDisplays.Iteration6 = new GateBuilder(). setSerializedIdAndSymbol('iter6'). - setWidth(1). + setWidth(2). setHeight(2). promiseHasNoNetEffectOnStateVector(). setDrawer(GatePainting.makeDisplayDrawer(args => { @@ -343,11 +349,18 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). 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.topHalf(); + 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); + 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 sphere and axis lines (in not-quite-proper 3d). args.painter.fillCircle(c, u, '#EFF'); args.painter.trace(trace => { @@ -359,14 +372,11 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). } }).thenStroke('#BBB'); - let vc = args.rect.bottomHalf().center(); - args.painter.fillCircle(vc, 10, '#EFF'); - args.painter.strokeCircle(vc, 10, 'black'); - args.painter.print('∀', vc.x, vc.y, 'center', 'middle', 'black', '16pt monospace', 20, 20); - paintStatePointCloud(args.painter, ρ, topArea, second_qubit_point_given_first); let pts = []; + let [x,y,z] = density_qubit_2(ρ).qubitDensityMatrixToBlochVector(); + let cen = Matrix.col(x, y, z); for (let [yaw, pitch, label, color, scale] of [ [Math.PI/2, -Math.PI/2, '', '#AFA', 0.3], [0, -Math.PI/2, '', '#FAA', 0.3], @@ -379,12 +389,9 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). let amp1_abs = Math.sin(pitch/2); let amp1 = Complex.polar(amp1_abs, yaw); let xyz = second_qubit_point_given_first(ρ, Matrix.col(amp0, amp1)); - if (xyz.norm2() > 0) { - pts.push(xyz); - _paintBlochSphereDisplay_indicator2(args.painter, xyz, topArea, color, label, scale); - } + pts.push(xyz); + _paintBlochSphereDisplay_indicator2(args.painter, xyz, topArea, color, label, scale, false, cen); } - let [x,y,z] = density_qubit_2(ρ).qubitDensityMatrixToBlochVector(); let m = 0; for (let e1 of pts) { for (let e2 of pts) { @@ -393,8 +400,8 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). } args.painter.ctx.save(); - args.painter.ctx.globalAlpha *= Math.max(0, 1 - m*10); - _paintBlochSphereDisplay_indicator2(args.painter, Matrix.col(x, y, z), topArea, 'white', '', 0.7, true); + args.painter.ctx.globalAlpha *= 0.5; + _paintBlochSphereDisplay_indicator2(args.painter, Matrix.col(x, y, z), topArea, 'black', '', 0.7, true); args.painter.ctx.restore(); })). setStatTexturesMaker(ctx => densityDisplayStatTexture( From 2c7e3fe7215e1795b95c8ec646ccc1819a8c265d Mon Sep 17 00:00:00 2001 From: Craig Gidney Date: Sat, 2 Nov 2019 03:59:18 -0700 Subject: [PATCH 3/3] Polish look and feel of entanglement display - Drop all iterations except one - Cut down to just seven bloch points (unconditioned, +-XYZ) - Replace X/Y/Z labels with state labels (0/1/+/-/i/-i) - Adjust text sizing and positioning to make it easier to read labels - Drop unused code --- src/gates/AllGates.js | 2 +- src/gates/EntanglementDisplays.js | 396 +++++++----------------------- test/circuit/Serializer.test.js | 1 + test/gates/AllGates.test.js | 1 + 4 files changed, 97 insertions(+), 303 deletions(-) diff --git a/src/gates/AllGates.js b/src/gates/AllGates.js index 4766a14c..f8b8a5d7 100644 --- a/src/gates/AllGates.js +++ b/src/gates/AllGates.js @@ -201,7 +201,7 @@ Gates.TopToolboxGroups = [ { hint: "Displays", gates: [ - EntanglementDisplays.Iteration1, undefined, + EntanglementDisplays.EntanglementDisplay, undefined, DensityMatrixDisplayFamily.ofSize(1), BlochSphereDisplay, ProbabilityDisplayFamily.ofSize(1), AmplitudeDisplayFamily.ofSize(2) ] diff --git a/src/gates/EntanglementDisplays.js b/src/gates/EntanglementDisplays.js index ebfdf23b..ef5127ee 100644 --- a/src/gates/EntanglementDisplays.js +++ b/src/gates/EntanglementDisplays.js @@ -3,42 +3,11 @@ 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 {!number} x - * @param {!number} y - * @param {!number} z - * @param {!Rect} drawArea - * @param {!string=} fillColor - */ -function _paintBlochSphereDisplay_indicator( - painter, - [x, y, z], - drawArea, - fillColor) { - let c = drawArea.center(); - let u = Math.min(drawArea.w, drawArea.h) / 2; - let {dx, dy, dz} = MathPainter.coordinateSystem(u); - - let p = c.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); - let r = 3.8 / (1 + drawArea.w/32 * x / 6); - - // Draw state indicators (in not-quite-correct 3d). - painter.strokeLine(c, p, 'black', 1.5); - painter.fillCircle(p, r, fillColor); - painter.strokeCircle(p, r, 'black'); - - // Show depth by lerping the line from overlaying to being overlayd by the ball. - painter.ctx.save(); - painter.ctx.globalAlpha *= Math.min(1, Math.max(0, 0.5+x*5)); - painter.strokeLine(c, p, 'black', 2); - painter.ctx.restore(); -} - /** * @param {!Painter} painter * @param {!Matrix} xyz @@ -46,17 +15,15 @@ function _paintBlochSphereDisplay_indicator( * @param {!string} fillColor * @param {!string} label * @param scale - * @param no_scale * @param {!Matrix} xyz_center */ -function _paintBlochSphereDisplay_indicator2( +function _paintBlochIndicator( painter, xyz, drawArea, fillColor, label, scale=1, - no_scale=false, xyz_center=Matrix.col(0, 0, 0)) { let x = xyz.rawBuffer()[0]; let y = xyz.rawBuffer()[2]; @@ -67,284 +34,97 @@ function _paintBlochSphereDisplay_indicator2( 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 = 7 / (1 + drawArea.w/32 * x / 6); - r *= (no_scale ? 1 : Math.sqrt(Math.sqrt(x*x + y*y + z*z))) * scale; + 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'); - // Show depth by lerping the line from overlaying to being overlayd by the ball. - painter.ctx.save(); - painter.ctx.globalAlpha *= Math.min(1, Math.max(0, 0.5 + x * 5)); - painter.strokeLine(c, p, 'black', 2); - painter.ctx.restore(); - - painter.print(label, p.x, p.y, 'center', 'middle', 'black', '36pt monospace', r*3, r*3); -} -/** - * @param {!Painter} painter - * @param {!Matrix} point - * @param {!Rect} drawArea - * @param {!string=} fillColor - */ -function _paintPoint3D(painter, point, drawArea, fillColor) { - let x = point.cell(0, 0).real; - let y = point.cell(0, 1).real; - let z = point.cell(0, 2).real; - let c = drawArea.center(); - let u = Math.min(drawArea.w, drawArea.h) / 2; - let {dx, dy, dz} = MathPainter.coordinateSystem(u); - let p = c.plus(dx.times(x)).plus(dy.times(y)).plus(dz.times(z)); - let r = 8 / (1 + drawArea.w/32 * x / 8); - r *= 0.2; - painter.fillCircle(p, r, fillColor); -} - -/** - * @param {!Painter} painter - * @param {!Rect} drawArea - */ -function _paintBackground(painter, drawArea) { - let c = drawArea.center(); - let u = Math.min(drawArea.w, drawArea.h) / 2; - let {dx, dy, dz} = MathPainter.coordinateSystem(u); - - // Draw sphere and axis lines (in not-quite-proper 3d). - painter.fillCircle(c, u, '#EFF'); - 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'); -} - -/** - * @param {!Matrix} two_qubit_density - * @returns {!Matrix} - */ -function density_qubit_2(two_qubit_density) { - return Matrix.square( - two_qubit_density.cell(0, 0).plus(two_qubit_density.cell(2, 2)), - two_qubit_density.cell(1, 0).plus(two_qubit_density.cell(3, 2)), - two_qubit_density.cell(0, 1).plus(two_qubit_density.cell(2, 3)), - two_qubit_density.cell(1, 1).plus(two_qubit_density.cell(3, 3))); + painter.print( + label, + p.x, + p.y + 1, + 'center', + 'middle', + 'black', + '36pt sans-serif', + r*2.5, + r*2.5); } /** - * @param {!Matrix} two_qubit_density + * @param {!Matrix} twoQubitDensityMatrix * @returns {!Matrix} */ -function density_qubit_1(two_qubit_density) { +function traceOutSecondQubit(twoQubitDensityMatrix) { return Matrix.square( - two_qubit_density.cell(0, 0).plus(two_qubit_density.cell(1, 1)), - two_qubit_density.cell(2, 0).plus(two_qubit_density.cell(3, 1)), - two_qubit_density.cell(0, 2).plus(two_qubit_density.cell(1, 3)), - two_qubit_density.cell(2, 2).plus(two_qubit_density.cell(3, 3))); + 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} two_qubit_density + * @param {!Matrix} twoQubitDensityMatrix * @returns {!Matrix} */ -function bloch_vector_of_2nd_qubit(two_qubit_density) { - return Matrix.col(...density_qubit_2(two_qubit_density).qubitDensityMatrixToBlochVector()); +function blochVectorAfterTracingOutSecondQubit(twoQubitDensityMatrix) { + return Matrix.col(...traceOutSecondQubit(twoQubitDensityMatrix).qubitDensityMatrixToBlochVector()); } /** - * @param {!Matrix} two_qubit_density - * @param {!Matrix} required_1st_qubit_amps - * @returns {!{p: !number, ρ: !Matrix}} + * @param {!Matrix} twoQubitDensityMatrix + * @param {!Matrix} desiredFirstQubitColVector + * @returns {!{successProbability: !number, densityMatrix: !Matrix}} */ -function density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps) { +function projectSecondQubit(twoQubitDensityMatrix, desiredFirstQubitColVector) { // Normalize input. - let u = required_1st_qubit_amps.times(1 / Math.sqrt(required_1st_qubit_amps.norm2())); + 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 = two_qubit_density.times(projector); + let projected_density = twoQubitDensityMatrix.times(projector); // Normalize output. - let p = projected_density.trace().abs(); - if (p < 0.0001) { - return {p, ρ: Matrix.generateDiagonal(4, _ => 0.25)}; + 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 {p, ρ: projected_density.times(1 / p)}; -} - -function second_qubit_point_given_first(two_qubit_density, required_1st_qubit_amps) { - let density_given_q1 = density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps).ρ; - return bloch_vector_of_2nd_qubit(density_given_q1); -} - -function second_qubit_point_given_first_purity_scaled(two_qubit_density, required_1st_qubit_amps) { - let projected_density = density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps).ρ; - let v_pre = bloch_vector_of_2nd_qubit(two_qubit_density); - let v_post = bloch_vector_of_2nd_qubit(projected_density); - - let purity_pre = Math.sqrt(v_pre.norm2()); - let purity_post = Math.sqrt(v_post.norm2()); - let purity_increase = purity_post - purity_pre; - - return v_post.times(purity_increase); -} - -function second_qubit_point_given_first_trace_scaled(two_qubit_density, required_1st_qubit_amps) { - let {p, ρ} = density_given_1st_qubit(two_qubit_density, required_1st_qubit_amps); - let v_pre = bloch_vector_of_2nd_qubit(two_qubit_density); - let v_post = bloch_vector_of_2nd_qubit(ρ); - return v_post.times(p).times(2).minus(v_pre); -} - -function eigenDecompose(hermitian) { - let results = []; - while (results.length < 4 && hermitian.norm2() > 0.001) { - let v = Matrix.generate(1, 4, () => new Complex(Math.random(), Math.random())); - let m = hermitian; - for (let k = 0; k < 4; k++) { - m = m.times(m); - } - for (let k = 0; k < 20; k++) { - v = m.times(v); - v = v.times(1 / Math.sqrt(v.norm2())); - } - let w = hermitian.times(v).norm2(); - results.push({vec: v, val: w}); - hermitian = hermitian.minus(v.times(v.adjoint()).times(w)); - } - return results; -} - -function paintAxes1(painter, density, drawArea) { - let amps_matrix = new Matrix(2, 2, eigenDecompose(density)[0].vec.rawBuffer()); - let {U, S, V} = amps_matrix.singularValueDecomposition(); - let s1 = S.cell(1, 1).real; - let factored_state_1 = density_qubit_1(density); - let factored_state_2 = density_qubit_2(density); - - let weight_entangled = 2*s1*s1; - let entangled_state = U.times(V); - - _paintBlochSphereDisplay_indicator(painter, factored_state_1.qubitDensityMatrixToBlochVector(), drawArea, 'black'); - _paintBlochSphereDisplay_indicator(painter, factored_state_2.qubitDensityMatrixToBlochVector(), drawArea, 'white'); - - let rr = drawArea.scaledOutwardBy(Math.sqrt(weight_entangled)); - let xyz = entangled_state.times(Matrix.square(1, 0, 0, 0)).times(entangled_state.adjoint()).qubitDensityMatrixToBlochVector(); - _paintBlochSphereDisplay_indicator(painter, xyz, rr, 'red'); - xyz = entangled_state.times(Matrix.square(0.5, 0.5, 0.5, 0.5)).times(entangled_state.adjoint()).qubitDensityMatrixToBlochVector(); - _paintBlochSphereDisplay_indicator(painter, xyz, rr, 'green'); - xyz = entangled_state.times(Matrix.square(0.5, new Complex(0, -0.5), new Complex(0, 0.5), 0.5)).times(entangled_state.adjoint()).qubitDensityMatrixToBlochVector(); - _paintBlochSphereDisplay_indicator(painter, xyz, rr, 'blue'); -} - -function paintAxes2(painter, density, drawArea) { - let vx = Matrix.col(0, 0, 0); - let vy = Matrix.col(0, 0, 0); - let vz = Matrix.col(0, 0, 0); - - for (let eigen of eigenDecompose(density)) { - let M = new Matrix(2, 2, eigen.vec.rawBuffer()); - let {U, S, V} = M.singularValueDecomposition(); - let s = S.cell(1, 1).real; - let weight = 2*s*s; - let E = U.times(V); - let state_to_xyz_contribution = (...vec_raw) => { - let vec = Matrix.col(...vec_raw); - let unit_vec = vec.times(1 / Math.sqrt(vec.norm2())); - let mat = unit_vec.times(unit_vec.adjoint()); - let mat2 = E.times(mat).times(E.adjoint()); - let xyz = mat2.qubitDensityMatrixToBlochVector(); - return Matrix.col(...xyz).times(weight * eigen.val); - }; - vx = vx.plus(state_to_xyz_contribution(1, 1)); - vy = vy.plus(state_to_xyz_contribution(1, Complex.I)); - vz = vz.plus(state_to_xyz_contribution(1, 0)); - } - - _paintBlochSphereDisplay_indicator2(painter, vy, drawArea, '#AFA', 'y'); - _paintBlochSphereDisplay_indicator2(painter, vz, drawArea, '#AAF', 'z'); - _paintBlochSphereDisplay_indicator2(painter, vx, drawArea, '#FAA', 'x'); + return {successProbability, densityMatrix: projected_density.times(1 / successProbability)}; } /** - * @param {!Painter} painter - * @param {!Matrix} stateDensityMatrix - * @param {!Rect} drawArea - * @param {!function(!Matrix, !Matrix): !Matrix} pointPicker + * @param {!Matrix} twoQubitDensityMatrix + * @param {!Matrix} desiredFirstQubitColVector + * @returns {!Matrix} */ -function paintStatePointCloud(painter, stateDensityMatrix, drawArea, pointPicker) { - for (let pitch = 0.01; pitch < Math.PI / 2; pitch += 0.15) { - let amp0 = Math.cos(pitch); - let amp1_abs = Math.sin(pitch); - let c2 = ''+Math.floor(pitch / Math.PI * 2 * 80 + 10); - let dr = 0.15 / Math.sin(pitch * 2); - for (let yaw = 0.0001; yaw < Math.PI * 2; yaw += dr) { - let c1 = ''+Math.floor(yaw / Math.PI / 2 * 80 + 10); - let color = '#' + c2 + c1 + '00'; - let amp1 = Complex.polar(amp1_abs, yaw); - let pt = pointPicker(stateDensityMatrix, Matrix.col(amp0, amp1)); - _paintPoint3D(painter, pt, drawArea, color); - } +function secondQubitBlochVectorConditioningOnFirstQubitStateVector(twoQubitDensityMatrix, desiredFirstQubitColVector) { + let {successProbability, densityMatrix} = projectSecondQubit(twoQubitDensityMatrix, desiredFirstQubitColVector); + if (successProbability < 0.0001) { + return undefined; } + return blochVectorAfterTracingOutSecondQubit(densityMatrix); } -function make_density_drawer(id, drawer) { - return new GateBuilder(). - setSerializedIdAndSymbol(id). - setWidth(2). - setHeight(2). - promiseHasNoNetEffectOnStateVector(). - setDrawer(GatePainting.makeDisplayDrawer(args => { - let ρ = args.customStats || Matrix.zero(4, 4).times(NaN); - if (ρ.hasNaN()) { - MathPainter.paintDensityMatrix(args.painter, ρ, args.rect, args.focusPoints); - return; - } - _paintBackground(args.painter, args.rect); - drawer(args, ρ); - })). - setStatTexturesMaker(ctx => densityDisplayStatTexture( - ctx.stateTrader.currentTexture, ctx.wireCount, ctx.controls, ctx.row, 2)). - setStatPixelDataPostProcessor(densityPixelsToMatrix). - gate; -} - -EntanglementDisplays.Iteration1 = make_density_drawer("iter1", (args, ρ) => - paintAxes1(args.painter, ρ, args.rect)); - -EntanglementDisplays.Iteration2 = make_density_drawer("iter2", (args, ρ) => - paintAxes2(args.painter, ρ, args.rect)); - -EntanglementDisplays.Iteration3 = make_density_drawer("iter3", (args, ρ) => - paintStatePointCloud(args.painter, ρ, args.rect, second_qubit_point_given_first)); - -EntanglementDisplays.Iteration4 = make_density_drawer("iter4", (args, ρ) => - paintStatePointCloud(args.painter, ρ, args.rect, second_qubit_point_given_first_trace_scaled)); - -EntanglementDisplays.Iteration5 = make_density_drawer("iter5", (args, ρ) => - paintStatePointCloud(args.painter, ρ, args.rect, second_qubit_point_given_first_purity_scaled)); - -EntanglementDisplays.Iteration6 = new GateBuilder(). - setSerializedIdAndSymbol('iter6'). +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 ρ = args.customStats || Matrix.zero(4, 4).times(NaN); - if (ρ.hasNaN()) { - MathPainter.paintDensityMatrix(args.painter, ρ, args.rect, args.focusPoints); - return; - } + 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)); @@ -354,6 +134,7 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). 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'); @@ -361,7 +142,7 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). args.painter.strokeCircle(vc, 6, 'black'); args.painter.print('∀', vc.x, vc.y, 'center', 'middle', 'black', '16pt monospace', 20, 20); - // Draw sphere and axis lines (in not-quite-proper 3d). + // 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); @@ -372,37 +153,53 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). } }).thenStroke('#BBB'); - paintStatePointCloud(args.painter, ρ, topArea, second_qubit_point_given_first); + 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 pts = []; - let [x,y,z] = density_qubit_2(ρ).qubitDensityMatrixToBlochVector(); + let [x, y, z] = traceOutSecondQubit(twoQubitDensityMatrix).qubitDensityMatrixToBlochVector(); let cen = Matrix.col(x, y, z); - for (let [yaw, pitch, label, color, scale] of [ - [Math.PI/2, -Math.PI/2, '', '#AFA', 0.3], - [0, -Math.PI/2, '', '#FAA', 0.3], - [0, Math.PI, '', '#AAF', 0.3], - [Math.PI/2, Math.PI/2, 'Y', '#AFA', 0.7], - [0, Math.PI/2, 'X', '#FAA', 0.7], - [0, 0, 'Z', '#AAF', 0.7], - ]) { - let amp0 = Math.cos(pitch/2); - let amp1_abs = Math.sin(pitch/2); - let amp1 = Complex.polar(amp1_abs, yaw); - let xyz = second_qubit_point_given_first(ρ, Matrix.col(amp0, amp1)); - pts.push(xyz); - _paintBlochSphereDisplay_indicator2(args.painter, xyz, topArea, color, label, scale, false, cen); - } - let m = 0; - for (let e1 of pts) { - for (let e2 of pts) { - m += e1.minus(e2).norm2(); + 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(); } - - args.painter.ctx.save(); - args.painter.ctx.globalAlpha *= 0.5; - _paintBlochSphereDisplay_indicator2(args.painter, Matrix.col(x, y, z), topArea, 'black', '', 0.7, true); - args.painter.ctx.restore(); })). setStatTexturesMaker(ctx => densityDisplayStatTexture( ctx.stateTrader.currentTexture, ctx.wireCount, ctx.controls, ctx.row, 2)). @@ -410,12 +207,7 @@ EntanglementDisplays.Iteration6 = new GateBuilder(). gate; EntanglementDisplays.all = [ - EntanglementDisplays.Iteration1, - EntanglementDisplays.Iteration2, - EntanglementDisplays.Iteration3, - EntanglementDisplays.Iteration4, - EntanglementDisplays.Iteration5, - EntanglementDisplays.Iteration6, + 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. '…' ]));