UNPKG

@qbead/bloch-sphere

Version:

A 3D Bloch Sphere visualisation built with Three.js and TypeScript.

1,752 lines (1,708 loc) 46.1 kB
var __defProp = Object.defineProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; // src/colors.ts import { Color } from "three"; var dark = new Color(1114112); var darkBlue = new Color(2501700); var lightBlue = new Color(5129949); var greyBlue = new Color(5003915); var babyBlue = new Color(4956899); var green = new Color(5617541); var lightGreen = new Color(9751106); var magenta = new Color(12792181); var yellow = new Color(14791998); var beige = new Color(14474984); var red = new Color(15744557); var defaultColors = { text: beige, background: darkBlue, blochSphereSkin: greyBlue, grid: darkBlue, axisXPlus: red, axisXMinus: babyBlue, axisYPlus: lightGreen, axisYMinus: magenta, axisZPlus: lightBlue, axisZMinus: yellow, operator: yellow, operatorPath: beige, path: beige, region: green }; // src/styles.ts var defaultText = defaultColors.text.getStyle(); var styles_default = ` <style> .label, .axis-label, .angle-label { line-height: 1; display: inline-block; color: var(--label-color, ${defaultText}); text-align: center; font-size: 1em; font-family: monospace; text-shadow: 0 0 2px black; } .axis-label { font-size: 1.6em; } .axis-label::before { content: ''; border: solid var(--label-color, ${defaultText}); border-width: 0 0 0 3px; display: inline-block; padding: 0.4em; transform: scale(0.6, 1.55) translate(0.6em, 0.036em); } .axis-label::after { content: ''; border: solid var(--label-color, ${defaultText}); border-width: 0 3px 3px 0; display: inline-block; padding: 0.4em; transform: scaleX(0.6) translate(-0.4em, 0.1em) rotate(-45deg); } </style> `; // src/bloch-sphere.ts import * as THREE4 from "three"; import { CSS2DRenderer } from "three/examples/jsm/renderers/CSS2DRenderer.js"; import { OrbitControls } from "three/examples/jsm/Addons.js"; // src/bloch-sphere-scene.ts import * as THREE3 from "three"; // src/components/label.ts import * as THREE2 from "three"; import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer.js"; // src/components/component.ts import * as THREE from "three"; var BaseComponent = class extends THREE.Object3D { _color; constructor(name) { super(); this.userData.component = this; this._color = new THREE.Color(16777215); if (name) { this.name = name; } } /** * Get color of the component */ get color() { return this._color; } /** * Set color of the component */ set color(color) { this._color.set(color); this.traverse((child) => { if (child instanceof THREE.Mesh) { child.material.color.set(this._color); } }); } }; // src/components/label.ts var Label = class extends BaseComponent { htmlobj; /** * Create a new label * @param text The text to display * @param type The type of label, corresponding to the html class (default: 'label') */ constructor(text, type = "label") { super("label"); const el = document.createElement("label"); el.className = type; el.textContent = text; el.setAttribute("style", `--label-color: ${defaultColors.text.getStyle()}`); this.htmlobj = new CSS2DObject(el); this.htmlobj.position.set(0, 0, 0); this.htmlobj.userData.component = this; this.add(this.htmlobj); } get text() { return this.htmlobj.element.textContent || ""; } set text(text) { this.htmlobj.element.textContent = text; if (!text) { this.visible = false; } } get fontSize() { return parseInt(this.htmlobj.element.style.fontSize || "18"); } set fontSize(size) { this.htmlobj.element.style.fontSize = `${size}em`; } get color() { return this._color; } set color(color) { this._color.set(color); this.htmlobj.element.style.setProperty( "--label-color", `${this._color.getStyle(THREE2.LinearSRGBColorSpace)}` ); } /** * Cleanup tasks */ destroy() { this.htmlobj.element.remove(); } }; // src/bloch-sphere-scene.ts var BlockSphereSceneOptions = { backgroundColor: defaultColors.background, gridColor: defaultColors.grid, gridDivisions: 36 / 3, sphereSkinColor: defaultColors.blochSphereSkin, sphereSkinOpacity: 0.55 }; var BlochSphereScene = class extends THREE3.Scene { sphere; grids; axes; labels = {}; plotStage = new THREE3.Group(); constructor(options) { options = Object.assign( {}, BlockSphereSceneOptions, options ); super(); this.background = new THREE3.Color(options.backgroundColor); this.fog = new THREE3.Fog(options.backgroundColor, 14.5, 17); const light = new THREE3.DirectionalLight(16777215, 1); light.position.set(1, 1, 1); this.add(light); this.sphere = new THREE3.Group(); this.grids = new THREE3.Group(); this.sphere.add(this.grids); const edges = new THREE3.EdgesGeometry( new THREE3.SphereGeometry(1, options.gridDivisions, options.gridDivisions), 0.5 ); const grid = new THREE3.LineSegments( edges, new THREE3.LineBasicMaterial({ // color: 0xdd9900, color: options.gridColor, transparent: true, opacity: 0.35, linewidth: 1 }) ); grid.rotation.x = Math.PI / 2; grid.name = "grid"; this.grids.add(grid); const polarGrid = new THREE3.PolarGridHelper( 0.98, options.gridDivisions, 2, 64, options.gridColor, options.gridColor ); polarGrid.rotation.x = Math.PI / 2; polarGrid.position.z = 1e-3; polarGrid.name = "polar-grid"; this.grids.add(polarGrid); const disc = new THREE3.Mesh( new THREE3.CircleGeometry(0.98, 64), new THREE3.MeshBasicMaterial({ color: options.sphereSkinColor, side: THREE3.DoubleSide, transparent: true, opacity: options.sphereSkinOpacity }) ); this.sphere.add(disc); const sphereSkin = new THREE3.Mesh( new THREE3.SphereGeometry(0.995, 32, 32), new THREE3.MeshBasicMaterial({ color: options.sphereSkinColor, transparent: true, opacity: options.sphereSkinOpacity, side: THREE3.BackSide }) ); sphereSkin.rotation.x = Math.PI / 2; this.sphere.add(sphereSkin); this.axes = new THREE3.Group(); this.sphere.add(this.axes); const axes = new THREE3.AxesHelper(1.25); axes.position.set(0, 0, 1e-3); axes.setColors( defaultColors.axisXPlus, defaultColors.axisYPlus, defaultColors.axisZPlus ); axes.material.depthFunc = THREE3.AlwaysDepth; this.axes.add(axes); const inverseAxes = new THREE3.AxesHelper(1.25); inverseAxes.setColors( defaultColors.axisXMinus, defaultColors.axisYMinus, defaultColors.axisZMinus ); inverseAxes.position.set(0, 0, -1e-3); inverseAxes.scale.set(-1, -1, -1); inverseAxes.material.depthFunc = THREE3.AlwaysDepth; this.axes.add(inverseAxes); this.sphere.add(this.plotStage); this.add(this.sphere); this.initLabels(); this.backgroundColor = options.backgroundColor; } get backgroundColor() { return this.background; } set backgroundColor(color) { this.background = new THREE3.Color(color); this.fog.color = new THREE3.Color(color); } initLabels() { const labels = [ { id: "zero", text: "0", position: new THREE3.Vector3(0, 0, 1), // color: new THREE.Color(0x0000ff), color: new THREE3.Color(defaultColors.axisZPlus), type: "axis-label" }, { id: "one", text: "1", position: new THREE3.Vector3(0, 0, -1), // color: new THREE.Color(0xffff00), color: new THREE3.Color(defaultColors.axisZMinus), type: "axis-label" }, { id: "plus", text: "+", position: new THREE3.Vector3(1, 0, 0), // color: new THREE.Color(0xff0000), color: new THREE3.Color(defaultColors.axisXPlus), type: "axis-label" }, { id: "minus", text: "-", position: new THREE3.Vector3(-1, 0, 0), // color: new THREE.Color(0x00ffff), color: new THREE3.Color(defaultColors.axisXMinus), type: "axis-label" }, { id: "i", text: "+i", position: new THREE3.Vector3(0, 1, 0), // color: new THREE.Color(0x00ff00), color: new THREE3.Color(defaultColors.axisYPlus), type: "axis-label" }, { id: "minus-i", text: "-i", position: new THREE3.Vector3(0, -1, 0), // color: new THREE.Color(0xff00ff), color: new THREE3.Color(defaultColors.axisYMinus), type: "axis-label" } ]; labels.forEach((label) => { const l = new Label(label.text, label.type); const color = label.color; l.position.copy(label.position).multiplyScalar(1.35); l.color = color; this.labels[label.id] = l; this.axes.add(l); }); } clearPlot() { this.plotStage.traverse((child) => { if (child instanceof Label) { child.destroy(); } }); this.plotStage.clear(); } }; // src/bloch-sphere.ts var BlochSphere = class { renderer; cssRenderer; el; scene; camera; controls; constructor(options) { this.initRenderer(); this.camera = new THREE4.OrthographicCamera(-2, 2, 2, -2, 0.1, 50); this.camera.position.set(10, 10, 5); this.camera.up.set(0, 0, 1); this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.scene = new BlochSphereScene(options); this.setOptions(options); } setOptions(options) { if (!options) return; if (options.fontSize) { this.el.style.fontSize = `${options.fontSize}em`; } if (options.showGrid !== void 0) { this.showGrid = options.showGrid; } } initRenderer() { this.el = document.createElement("div"); this.el.className = "bloch-sphere-widget-container"; this.el.innerHTML = styles_default; this.renderer = new THREE4.WebGLRenderer({ antialias: true }); this.renderer.setPixelRatio(window.devicePixelRatio); this.renderer.setSize(200, 200); this.el.appendChild(this.renderer.domElement); this.cssRenderer = new CSS2DRenderer(); this.cssRenderer.setSize(200, 200); this.cssRenderer.domElement.style.position = "absolute"; this.cssRenderer.domElement.style.top = "0"; this.cssRenderer.domElement.style.pointerEvents = "none"; this.cssRenderer.domElement.style.zIndex = "1"; this.el.appendChild(this.cssRenderer.domElement); } get showGrid() { return this.scene.grids.visible; } set showGrid(value) { this.scene.grids.visible = value; } add(item) { this.scene.plotStage.add(item); } remove(item) { this.scene.plotStage.remove(item); } /** * Removes all objects from the plot * * This will not remove the grid or the sphere. */ clearPlot() { this.scene.clearPlot(); } /** * Rescales the sphere */ scale(size) { this.scene.sphere.scale.set(size, size, size); } /** * Attaches the widget to a parent element * * Must be called to make the widget visible. */ attach(parent) { parent = parent ?? document.body; parent.appendChild(this.el); this.resize(); this.start(); } /** * Resizes the widget to fit the parent element * * Optionally, you can specify the width and height to resize to. */ resize(width, height) { width = width ?? this.el.parentElement?.clientWidth ?? 200; height = height ?? this.el.parentElement?.clientHeight ?? 200; let aspect = height / width; this.renderer.setSize(width, height); this.cssRenderer.setSize(width, height); this.camera.top = 2 * aspect; this.camera.bottom = -2 * aspect; this.camera.updateProjectionMatrix(); } /** * Renders the scene * * This is called automatically in the animation loop unless that * loop is stopped. */ render() { this.renderer.render(this.scene, this.camera); this.cssRenderer.render(this.scene, this.camera); this.controls.update(); } /** * Starts the animation loop * * Automatically started when the widget is attached to a parent element. * * This will call the render method automatically. */ start() { this.renderer.setAnimationLoop(() => { this.render(); }); } /** * Stops the animation loop * * This will stop the render loop */ stop() { this.renderer.setAnimationLoop(null); } /** * Performs cleanup and disposes everything contained in the widget */ dispose() { this.stop(); this.clearPlot(); this.renderer.dispose(); this.el.remove(); this.scene.traverse((child) => { if (child instanceof THREE4.Mesh) { child.geometry.dispose(); child.material.dispose(); } }); } }; // src/components/qubit-arrow.ts import * as THREE5 from "three"; // src/format.ts function formatVector(v, precision = 2) { const xyz = [v.x, v.y, v.z].map((n) => n.toFixed(precision)); return `(${xyz.join(", ")})`; } function formatDegrees(radians, precision = 2) { return `${(radians * 180 / Math.PI).toFixed(precision)}\xB0`; } function formatRadians(radians, precision = 2) { return `${radians.toFixed(precision)} rad`; } // src/components/qubit-arrow.ts var QubitArrow = class extends BaseComponent { arrowHelper; label; constructor() { super("qubit-arrow"); const arrow = new THREE5.ArrowHelper( new THREE5.Vector3(0, 0, 1), new THREE5.Vector3(0, 0, 0), 1, 16777215, 0.1, 0.05 ); this.arrowHelper = arrow; this.label = new Label("(0, 0, 0)"); this.label.position.set(0, 1.1, 0); this.arrowHelper.add(this.label); this.arrowHelper.userData.component = this; this.add(this.arrowHelper); } set color(color) { this._color.set(color); this.arrowHelper.setColor(new THREE5.Color(color)); } follow(v) { this.arrowHelper.setDirection(v); this.label.text = formatVector(v); } }; // src/components/angle-indicators.ts import * as THREE6 from "three"; // src/math/units.ts function isRadiansUnits(units) { return ["rad", "RAD", "radians"].includes(units); } // src/components/angle-indicators.ts var yAxis = new THREE6.Vector3(0, 1, 0); var AngleIndicators = class extends BaseComponent { units = "deg"; phiWedge; phiLabel; thetaLabelContainer; thetaWedge; thetaLabel; phiLabelContainer; _phiColor = new THREE6.Color(defaultColors.text); _thetaColor = new THREE6.Color(defaultColors.text); /** * Creates a new AngleIndicators component * * @param scale - The scale of the angle indicators (default is 0.25) */ constructor(scale = 0.25) { super("angle-indicators"); this.phiWedge = new THREE6.Mesh( new THREE6.RingGeometry(0, 1, 16, 1, 0, Math.PI), new THREE6.MeshBasicMaterial({ color: this._phiColor, transparent: true, opacity: 0.35, side: THREE6.DoubleSide }) ); this.phiWedge.material.depthTest = false; this.phiWedge.renderOrder = 2; this.phiLabel = new Label("0", "label angle-label"); this.phiLabel.position.set(1, 0, 0); this.phiLabelContainer = new THREE6.Object3D(); this.phiLabelContainer.add(this.phiLabel); this.phiWedge.add(this.phiLabelContainer); this.thetaWedge = new THREE6.Mesh( new THREE6.RingGeometry(0, 1, 16, 1, 0, Math.PI / 2), new THREE6.MeshBasicMaterial({ color: this._thetaColor, transparent: true, opacity: 0.35, side: THREE6.DoubleSide }) ); this.thetaWedge.material.depthTest = false; this.thetaWedge.renderOrder = 3; this.thetaWedge.rotation.set(Math.PI / 2, Math.PI / 2, 0); this.thetaLabel = new Label("0", "label angle-label"); this.thetaLabel.position.set(0, 1, 0); this.thetaLabelContainer = new THREE6.Object3D(); this.thetaLabelContainer.add(this.thetaLabel); this.thetaWedge.add(this.thetaLabelContainer); this.phiWedge.add(this.thetaWedge); this.add(this.phiWedge, this.thetaWedge); this.scale.set(scale, scale, scale); this.phiColor = this._phiColor; this.thetaColor = this._thetaColor; this.labelRadius = 1.1; this.opacity = 0.2; } /** * Update the angle indicators for the given Bloch vector */ update(v) { const { phi, theta } = v; this.phiWedge.geometry.dispose(); this.thetaWedge.geometry.dispose(); this.phiWedge.geometry = new THREE6.RingGeometry(0, 1, 16, 1, 0, phi); this.thetaWedge.geometry = new THREE6.RingGeometry( 0, 1, 16, 1, Math.PI / 2, theta ); this.thetaWedge.rotation.set(Math.PI / 2, Math.PI / 2, 0); this.thetaWedge.rotateOnAxis(yAxis, Math.PI / 2 + phi); this.thetaLabelContainer.rotation.set(0, 0, Math.min(theta, 0.5)); this.phiLabelContainer.rotation.set(0, 0, phi / 2); if (isRadiansUnits(this.units)) { this.phiLabel.text = formatRadians(phi); this.thetaLabel.text = formatRadians(theta); } else { this.phiLabel.text = formatDegrees(phi); this.thetaLabel.text = formatDegrees(theta); } } get opacity() { return this.phiWedge.material.opacity; } set opacity(opacity) { this.phiWedge.material.opacity = opacity; this.thetaWedge.material.opacity = opacity; } /** * The distance of the labels from the center of the sphere */ get labelRadius() { return this.phiLabel.position.length(); } set labelRadius(radius2) { this.phiLabel.position.set(radius2, 0, 0); this.thetaLabel.position.set(0, radius2, 0); } set color(color) { this.phiColor = color; this.thetaColor = color; } get phiColor() { return this._phiColor; } set phiColor(color) { this._phiColor.set(color); this.phiWedge.material.color = this._phiColor; this.phiLabel.color = this._phiColor; } get thetaColor() { return this._thetaColor; } set thetaColor(color) { this._thetaColor.set(color); this.thetaWedge.material.color = this._thetaColor; this.thetaLabel.color = this._thetaColor; } }; // src/components/wedge.ts import * as THREE7 from "three"; var vertices = []; var radius = 1; var angle = Math.PI / 2; var segments = 32; for (let i = 0; i <= segments; i++) { const x2 = radius * Math.cos(i * angle / segments); const y2 = radius * Math.sin(i * angle / segments); vertices.push(x2, y2, 0); } vertices.push(0, 0, 0); vertices.push(radius, 0, 0); var geometry = new THREE7.BufferGeometry(); geometry.setAttribute("position", new THREE7.Float32BufferAttribute(vertices, 3)); geometry.dispose = () => { }; var Wedge = class extends BaseComponent { constructor() { super("wedge"); const material = new THREE7.LineBasicMaterial({}); material.depthFunc = THREE7.AlwaysDepth; const line = new THREE7.Line(geometry, material); line.rotation.set(Math.PI / 2, 0, 0); this.add(line); } }; // src/components/qubit-proj-wedge.ts var QubitProjWedge = class extends Wedge { constructor() { super(); } follow(v) { const { theta, phi } = v; if (theta > Math.PI / 2) { this.rotation.set(0, Math.PI, Math.PI - phi); } else { this.rotation.set(0, 0, phi); } } }; // src/math/complex.ts var Complex = class _Complex { real; imag; static get ZERO() { return new _Complex(0, 0); } static get ONE() { return new _Complex(1, 0); } static get I() { return new _Complex(0, 1); } constructor(real, imag = 0) { this.real = real; this.imag = imag; } static from(value, imag) { if (typeof value === "number") { return new _Complex(value, imag); } if (Array.isArray(value)) { return new _Complex(value[0], value[1]); } return new _Complex(value.real, value.imag); } static random() { return _Complex.from(Math.random(), Math.random()); } static unitRandom() { const theta = Math.random() * 2 * Math.PI; return _Complex.from(Math.cos(theta), Math.sin(theta)); } static fromPolar(magnitude, phase2) { return _Complex.from( magnitude * Math.cos(phase2), magnitude * Math.sin(phase2) ); } plus(other) { const { real, imag } = _Complex.from(other); return _Complex.from(this.real + real, this.imag + imag); } minus(other) { const { real, imag } = _Complex.from(other); return _Complex.from(this.real - real, this.imag - imag); } times(other) { const { real, imag } = _Complex.from(other); return _Complex.from( this.real * real - this.imag * imag, this.real * imag + this.imag * real ); } dividedBy(other) { const { real, imag } = _Complex.from(other); const denominator = real * real + imag * imag; return _Complex.from( (this.real * real + this.imag * imag) / denominator, (this.imag * real - this.real * imag) / denominator ); } get magnitude() { return Math.sqrt(this.real * this.real + this.imag * this.imag); } get phase() { return Math.atan2(this.imag, this.real); } conjugate() { return _Complex.from(this.real, -this.imag); } toString() { return `${this.real} + ${this.imag}i`; } }; // src/math/bloch-vector.ts import { Vector3 as Vector34 } from "three"; // src/math/interpolation.ts function lerp(a, b, t) { return a + t * (b - a); } // src/math/bloch-vector.ts var BlochVector = class _BlochVector extends Vector34 { /** * A bloch vector representing the zero state */ static get ZERO() { return new _BlochVector(0, 0, 1); } /** * A bloch vector representing the one state */ static get ONE() { return new _BlochVector(0, 0, -1); } /** * A bloch vector representing the plus state (|+>) or (|0> + |1>)/sqrt(2) */ static get PLUS() { return new _BlochVector(1, 0, 0); } /** * A bloch vector representing the minus state (|->) or (|0> - |1>)/sqrt(2) */ static get MINUS() { return new _BlochVector(-1, 0, 0); } /** * A bloch vector representing the imaginary state (|i>) or (|0> + i|1>)/sqrt(2) */ static get I() { return new _BlochVector(0, 1, 0); } /** * A bloch vector representing the minus imaginary state (|-i>) or (|0> - i|1>)/sqrt(2) */ static get MINUS_I() { return new _BlochVector(0, -1, 0); } /** * Generate a random Bloch vector with magnitude 1 */ static random() { const theta = Math.random() * Math.PI; const phi = Math.random() * 2 * Math.PI; return _BlochVector.fromAngles(theta, phi); } /** * Create a zero state Bloch vector */ static zero() { return _BlochVector.from(_BlochVector.ZERO); } static from(x2, y2 = 0, z2 = 0) { if (Array.isArray(x2)) { return new _BlochVector(x2[0], x2[1], x2[2]); } if (x2 instanceof _BlochVector) { return x2.clone(); } if (x2 instanceof Vector34) { return new _BlochVector(x2.x, x2.y, x2.z); } return new _BlochVector(x2, y2, z2); } /** * Create a Bloch vector from angles (theta, phi) */ static fromAngles(theta, phi) { return _BlochVector.zero().setAngles([theta, phi]); } /** The angle between the BlochVector and the z-axis */ get theta() { return Math.acos(this.z); } /** The angle between the projection of the BlochVector on the xy-plane and the x-axis */ get phi() { return Math.atan2(this.y, this.x); } /** The amplitude of the Bloch vector */ get amplitude() { return Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2); } /** The density matrix representation of the Bloch vector */ get rho() { return this.densityMatrix(); } /** The density matrix representation of the Bloch vector */ densityMatrix() { const x2 = Complex.from(this.x); const y2 = Complex.from(this.y); const z2 = Complex.from(this.z); const half = Complex.from(0.5); const i = Complex.from(0, 1); return [ [half.times(z2.plus(1)), half.times(x2.minus(i.times(y2)))], [half.times(x2.plus(i.times(y2))), half.times(Complex.ONE.minus(z2))] ]; } /** * Create a Bloch vector from a density matrix * * @param rho - The density matrix to create the Bloch vector from */ static fromDensityMatrix(rho) { const x2 = rho[0][1].real * 2; const y2 = rho[1][0].imag * 2; const z2 = rho[0][0].minus(rho[1][1]).real; return new _BlochVector(x2, y2, z2); } /** * Apply an operator to the Bloch vector returning a new Bloch vector * * @param op - The operator to apply * @returns The new Bloch vector */ applyOperator(op) { return _BlochVector.fromDensityMatrix(op.applyTo(this.rho)); } /** * Get both angles of the Bloch vector as an array `[theta, phi]` */ angles() { return [this.theta, this.phi]; } /** * Set the Bloch vector from angles `[theta, phi]` * * @param angles - The angles to set the Bloch vector to */ setAngles(angles) { const [theta, phi] = angles; this.set( Math.sin(theta) * Math.cos(phi), Math.sin(theta) * Math.sin(phi), Math.cos(theta) ); return this; } toString() { return `(${this.x}, ${this.y}, ${this.z})`; } /** * Spherical linear interpolation of this Bloch vector to another Bloch vector * * @param other - The other Bloch vector to interpolate to * @param t - The interpolation factor (0 <= t <= 1) * @returns The interpolated Bloch vector */ slerpTo(other, t) { const theta = lerp(this.theta, other.theta, t); const phi = lerp(this.phi, other.phi, t); return _BlochVector.fromAngles(theta, phi); } }; // src/animation.ts import { Parsers } from "intween"; function animate(callback, duration = 1e3, easing = "linear", loop = false) { const easeFn = Parsers.parseEasing(easing); let start = performance.now(); let cancelled = false; const step = () => { if (cancelled) return; const time = performance.now(); const progress = Math.min((time - start) / duration, 1); const k = easeFn(progress); callback(k); if (progress >= 1) { if (loop) { start = time; } else { return; } } requestAnimationFrame(step); }; requestAnimationFrame(step); return () => { cancelled = true; }; } // src/components/qubit-display.ts var QubitDisplay = class extends BaseComponent { arrow; wedge; angleIndicators; state; _anim = null; constructor(q) { super("qubit-display"); this.arrow = new QubitArrow(); this.add(this.arrow); this.wedge = new QubitProjWedge(); this.angleIndicators = new AngleIndicators(); this.add(this.angleIndicators); this.state = BlochVector.zero(); if (q) { this.set(q); } } set color(color) { super.color = color; this.arrow.color = color; } /** * Set the bloch vector state of the display * * Can also be used to animate the state of the qubit. * * @param q - The new Bloch vector state to set. * @param duration - The duration of the animation (default is 0). * @param easing - The easing function to use for the animation (default is 'quadInOut'). */ set(q, duration = 0, easing = "quadInOut") { if (duration > 0) { let start = this.state.angles(); let end = q.angles(); this._anim?.call(); this._anim = animate( (k) => { this.state.setAngles([ lerp(start[0], end[0], k), lerp(start[1], end[1], k) ]); this.set(this.state); }, duration, easing ); return this._anim; } this.arrow.follow(q); this.wedge.follow(q); this.angleIndicators.update(q); } }; // src/components/path-display.ts import * as THREE8 from "three"; var BlochSpherePath = class extends THREE8.Curve { from; to; constructor(from, to) { super(); this.from = from; this.to = to; } getPoint(t, optionalTarget = new THREE8.Vector3()) { optionalTarget.copy(this.from.slerpTo(this.to, t)); return optionalTarget; } }; function* pairs(arr) { for (let i = 0; i < arr.length - 1; i++) { yield [arr[i], arr[i + 1]]; } } function tubePath(vertices2, material) { const curves = Array.from(pairs(vertices2)).map(([v1, v2]) => new BlochSpherePath(v1, v2)).reduce((curvePath, curve) => { curvePath.add(curve); return curvePath; }, new THREE8.CurvePath()); const tube = new THREE8.TubeGeometry(curves, 256, 5e-3, 6, false); return new THREE8.Mesh(tube, material); } var PathDisplay = class extends BaseComponent { constructor(path) { super("path-display"); if (path) { this.set(path); } } /// Set the path set(vertices2) { this.clear(); const material = new THREE8.MeshBasicMaterial({ color: defaultColors.path, side: THREE8.DoubleSide, transparent: true, opacity: 0.8 }); material.depthTest = false; const mesh = tubePath(vertices2, material); mesh.renderOrder = 10; this.add(mesh); } }; // src/components/region-display.ts import * as THREE10 from "three"; // src/materials/spherical-polygon.ts import * as THREE9 from "three"; var MAX_POINTS = 100; function sortRegionPoints(regionPoints) { let avgNormal = new THREE9.Vector3(); regionPoints.forEach((p) => avgNormal.add(p)); avgNormal.normalize(); let ref = new THREE9.Vector3(1, 0, 0); if (Math.abs(avgNormal.dot(ref)) > 0.9) ref.set(0, 1, 0); let tangentX = new THREE9.Vector3().crossVectors(avgNormal, ref).normalize(); let tangentY = new THREE9.Vector3().crossVectors(avgNormal, tangentX).normalize(); regionPoints.sort((a, b) => { let angleA = Math.atan2(a.dot(tangentY), a.dot(tangentX)); let angleB = Math.atan2(b.dot(tangentY), b.dot(tangentX)); return angleA - angleB; }); return regionPoints; } function getRegionPoints(region) { const points = []; for (const v of region) { points.push(v); } const sortedPoints = sortRegionPoints(points); for (let i = region.length; i < MAX_POINTS; i++) { sortedPoints.push(new THREE9.Vector3(0, 0, 0)); } return sortedPoints; } var SphericalPolygonMaterial = class extends THREE9.ShaderMaterial { region; constructor(region = []) { super({ uniforms: { regionPoints: { value: getRegionPoints(region) }, numPoints: { value: region.length }, highlightColor: { value: new THREE9.Color(16711680).convertLinearToSRGB() } }, vertexShader: ` varying vec3 vPosition; void main() { vec4 worldPosition = modelMatrix * vec4(position, 1.0); vPosition = normalize(worldPosition.xyz); // Ensure it's on the sphere gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 regionPoints[${MAX_POINTS}]; uniform int numPoints; uniform vec3 highlightColor; varying vec3 vPosition; // Checks if point p is inside the spherical polygon defined by regionPoints bool insideRegion(vec3 p) { int winding = 0; vec3 averageNormal = vec3(0.0); for (int i = 0; i < numPoints; i++) { vec3 a = normalize(regionPoints[i]); vec3 b = normalize(regionPoints[(i + 1) % numPoints]); // Compute cross product and accumulate for average normal vec3 edgeNormal = cross(a, b); averageNormal += edgeNormal; if (dot(edgeNormal, p) > 0.0) { winding += 1; } else { winding -= 1; } } // Normalize to get the true average normal averageNormal = normalize(averageNormal); // Ensure point p is in the same hemisphere as averageNormal bool isCorrectHemisphere = dot(averageNormal, p) > 0.0; return abs(winding) == numPoints && isCorrectHemisphere; } void main() { if (insideRegion(vPosition)) { gl_FragColor = vec4(highlightColor, 1.0); } else { discard; } } `, transparent: true, side: THREE9.DoubleSide }); this.region = region; } get highlightColor() { return this.uniforms.highlightColor.value; } set highlightColor(color) { this.uniforms.highlightColor.value = new THREE9.Color( color ).convertLinearToSRGB(); } setRegion(region) { this.region = region; this.uniforms.regionPoints.value = getRegionPoints(region); this.uniforms.numPoints.value = region.length; } }; // src/components/region-display.ts var RegionDisplay = class extends BaseComponent { sphere; constructor(region) { super("region-display"); const material = new SphericalPolygonMaterial(); material.highlightColor = defaultColors.region; this.sphere = new THREE10.Mesh( new THREE10.SphereGeometry(0.985, 64, 64), material ); this.add(this.sphere); if (region) { this.setRegion(region); } } get color() { const material = this.sphere.material; return material.highlightColor; } set color(color) { const material = this.sphere.material; material.highlightColor = color; } /** * Set the region of the display * * @param points - The bloch vectors that define the region. */ setRegion(points) { const material = this.sphere.material; material.setRegion(points); } }; // src/components/points-display.ts import * as THREE11 from "three"; var PointsDisplay = class extends BaseComponent { pointMaterial; constructor(points) { super("points-display"); this.pointMaterial = new THREE11.ShaderMaterial({ vertexShader: ` uniform float pointSize; varying vec2 vUv; varying float distanceToCamera; void main() { vUv = uv; vec4 mvPosition = modelViewMatrix * vec4(position, 1.0); distanceToCamera = -mvPosition.z; gl_PointSize = pointSize; gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); } `, fragmentShader: ` uniform vec3 color; varying float distanceToCamera; void main() { vec2 coord = gl_PointCoord - vec2(0.5); // Center if (length(coord) > 0.5) discard; // Make it circular gl_FragColor = vec4(color, 1.0); } `, uniforms: { color: { value: new THREE11.Color(1, 1, 1) }, pointSize: { value: 20 } } }); if (points) { this.set(points); } } /** * Set the size of the points */ get pointSize() { return this.pointMaterial.uniforms.pointSize.value; } set pointSize(size) { this.pointMaterial.uniforms.pointSize.value = size; } /** * Set the color of the points */ set color(color) { const colorValue = new THREE11.Color(color); this.pointMaterial.uniforms.color.value = colorValue.convertLinearToSRGB(); } /** * Set the points to display */ set(points) { this.clear(); const positions = new Float32Array(points.length * 3); for (const [i, point] of points.entries()) { const pos = point; positions[i * 3] = pos.x; positions[i * 3 + 1] = pos.y; positions[i * 3 + 2] = pos.z; } const geometry2 = new THREE11.BufferGeometry(); geometry2.setAttribute("position", new THREE11.BufferAttribute(positions, 3)); const pointsMesh = new THREE11.Points(geometry2, this.pointMaterial); this.add(pointsMesh); } }; // src/components/operator-display.ts import * as THREE12 from "three"; // src/math/operator.ts import { Quaternion } from "three"; var Operator = class _Operator { elements; static identity() { return new _Operator([ [Complex.ONE, Complex.ZERO], [Complex.ZERO, Complex.ONE] ]); } constructor(elements) { this.elements = elements; } /** * The first row, first column element of the operator */ get a() { return this.elements[0][0]; } /** * The first row, second column element of the operator */ get b() { return this.elements[0][1]; } /** * The second row, first column element of the operator */ get c() { return this.elements[1][0]; } /** * The second row, second column element of the operator */ get d() { return this.elements[1][1]; } /** * Multiply the operator by a scalar */ scale(scalar) { this.elements = this.elements.map((row) => row.map((e) => e.times(scalar))); return this; } /** * Multiply the operator by another operator */ times(other) { const a = this.a.times(other.a).plus(this.b.times(other.c)); const b = this.a.times(other.b).plus(this.b.times(other.d)); const c = this.c.times(other.a).plus(this.d.times(other.c)); const d = this.c.times(other.b).plus(this.d.times(other.d)); return new _Operator([ [a, b], [c, d] ]); } /** * Get the conjugate transpose of the operator as a new operator */ conjugateTranspose() { return new _Operator([ [this.a.conjugate(), this.c.conjugate()], [this.b.conjugate(), this.d.conjugate()] ]); } /** * Apply this operator to a density matrix */ applyTo(rho) { return this.times(new _Operator(rho)).times(this.conjugateTranspose()).elements; } /** * Add another operator to this operator */ plus(other) { const a = this.a.plus(other.a); const b = this.b.plus(other.b); const c = this.c.plus(other.c); const d = this.d.plus(other.d); return new _Operator([ [a, b], [c, d] ]); } /** * Get the determinant of the operator */ determinant() { return this.a.times(this.d).minus(this.b.times(this.c)); } /** * Get this operator as a THREE.Quaternion */ quaternion() { const w = this.a.plus(this.d).real; const x2 = this.c.plus(this.b); const y2 = this.c.minus(this.b); const z2 = this.a.minus(this.d); if (x2.real == 0 && y2.real == 0 && z2.real == 0) { return new Quaternion(x2.imag, y2.imag, z2.imag, w).normalize(); } else { return new Quaternion(x2.real, y2.real, z2.real, w).normalize(); } } }; // src/components/operator-display.ts var OperatorDisplay = class extends BaseComponent { operator; innerGroup; label; anim; constructor(op) { super("operator-display"); const innerGroup = new THREE12.Group(); this.innerGroup = innerGroup; const cyl = new THREE12.Mesh( new THREE12.CylinderGeometry(5e-3, 5e-3, 1.05, 32), new THREE12.MeshBasicMaterial({ color: defaultColors.operator, transparent: true, opacity: 0.5 }) ); cyl.rotation.x = Math.PI / 2; cyl.position.z = 0.525; innerGroup.add(cyl); const ringRadius = 0.7; const rings = new THREE12.Group(); const ring = new THREE12.Mesh( new THREE12.RingGeometry( ringRadius - 0.01, ringRadius, 64, 1, 0, Math.PI / 2 ), new THREE12.MeshBasicMaterial({ color: defaultColors.operator, side: THREE12.DoubleSide, transparent: true, opacity: 0.5 }) ); ring.material.depthTest = false; ring.renderOrder = 5; ring.position.z = 0; const ring2 = ring.clone(); ring2.rotation.z = Math.PI; rings.add(ring); rings.add(ring2); const disc = new THREE12.Mesh( new THREE12.CircleGeometry(ringRadius - 0.01, 64), new THREE12.MeshBasicMaterial({ color: defaultColors.operator, side: THREE12.DoubleSide, transparent: true, opacity: 0.1 }) ); disc.material.depthTest = false; innerGroup.add(disc); this.anim = animate( (k) => { rings.rotation.z = Math.PI * 2 * k; }, 3e3, "linear", true ); innerGroup.add(rings); this.label = new Label(""); this.label.position.z = 1.1; innerGroup.add(this.label); this.add(innerGroup); this.renderOrder = 4; this.operator = Operator.identity(); if (op) { this.set(op); } } /** * Set the operator to display * @param op The operator to display */ set(op) { this.operator = op; const q = this.operator.quaternion(); const n = new THREE12.Vector3(q.x, q.y, q.z); const rot = new THREE12.Quaternion().setFromUnitVectors( new THREE12.Vector3(0, 0, 1), n ); this.setRotationFromQuaternion(rot); } /** * Perform cleanup tasks */ dispose() { this.anim(); } }; // src/components/operator-path-display.ts import * as THREE14 from "three"; // src/math/geometry.ts import * as THREE13 from "three"; function getRotationArc(v, n, angle2) { const height = v.dot(n); const radius2 = Math.sqrt(1 - height ** 2); const c = v.projectOnPlane(n); let c0 = new THREE13.Vector3(1, 0, 0).projectOnPlane(n); if (c0.length() === 0) { c0 = new THREE13.Vector3(0, 0, 1).projectOnPlane(n); angle2 = -angle2; } const a0 = c.angleTo(c0) * (c0.cross(c).dot(n) < 0 ? -1 : 1); return { radius: radius2, height, norm: n.clone(), arcOffset: a0, arcAngle: angle2 }; } function getArcBetween(v1, v2) { const norm = v1.clone().cross(v2).normalize(); const arcAngle = v1.angleTo(v2); const rot = new THREE13.Quaternion().setFromUnitVectors( new THREE13.Vector3(0, 0, 1), norm ); const xaxis = new THREE13.Vector3(1, 0, 0).applyQuaternion(rot); const arcOffset = xaxis.angleTo(v1) * (v1.cross(xaxis).dot(norm) < 0 ? 1 : -1); return { norm, arcOffset, arcAngle }; } // src/components/operator-path-display.ts var OperatorPathDisplay = class extends BaseComponent { operator; vector; innerGroup; path; disc; constructor(op, v) { super("operator-path-display"); const innerGroup = new THREE14.Group(); this.innerGroup = innerGroup; this.path = new THREE14.Mesh( new THREE14.RingGeometry(0, 0.01, 64), new THREE14.MeshBasicMaterial({ color: defaultColors.operatorPath, side: THREE14.DoubleSide, transparent: true, opacity: 0.8 }) ); this.path.material.depthTest = false; innerGroup.add(this.path); this.disc = new THREE14.Mesh( new THREE14.CircleGeometry(1, 64), new THREE14.MeshBasicMaterial({ color: defaultColors.operatorPath, side: THREE14.DoubleSide, transparent: true, opacity: 0.15 }) ); this.disc.material.depthTest = false; this.add(innerGroup); this.operator = Operator.identity(); if (op) { this.setOperator(op); } this.vector = BlochVector.ONE; if (v) { this.setVector(v); } } /** * Set the operator and vector */ set(op, v) { this.operator = op; this.vector = v; const q = this.operator.quaternion(); const n = new THREE14.Vector3(q.x, q.y, q.z).normalize(); const rot = new THREE14.Quaternion().setFromUnitVectors( new THREE14.Vector3(0, 0, 1), n ); this.setRotationFromQuaternion(rot); let angle2 = 2 * Math.acos(q.w); const greatArc = getRotationArc(v, n, angle2); const { radius: radius2, height, arcOffset, arcAngle } = greatArc; this.path.geometry = new THREE14.RingGeometry( radius2, radius2 + 0.01, 64, 1, arcOffset, arcAngle ); this.disc.geometry = new THREE14.CircleGeometry(radius2, 64); this.innerGroup.position.z = height; } /** * Set the operator */ setOperator(op) { this.set(op, this.vector); } /** * Set the vector */ setVector(v) { this.set(this.operator, v); } }; // src/math/gates.ts var gates_exports = {}; __export(gates_exports, { hadamard: () => hadamard, identity: () => identity, not: () => not, phase: () => phase, rx: () => rx, ry: () => ry, rz: () => rz, x: () => x, y: () => y, z: () => z }); function identity() { return Operator.identity(); } function x() { return new Operator([ [Complex.ZERO, Complex.ONE], [Complex.ONE, Complex.ZERO] ]); } var not = x; function y() { return new Operator([ [Complex.ZERO, Complex.I.times(-1)], [Complex.I, Complex.ZERO] ]); } function z() { return new Operator([ [Complex.ONE, Complex.ZERO], [Complex.ZERO, Complex.ONE.times(-1)] ]); } function hadamard() { return x().plus(z()).scale(1 / Math.sqrt(2)); } function phase(phi) { return new Operator([ [Complex.ONE, Complex.ZERO], [Complex.ZERO, Complex.fromPolar(1, phi)] ]); } function rx(theta) { return new Operator([ [ Complex.from(Math.cos(theta / 2), 0), Complex.I.times(Math.sin(theta / 2)) ], [ Complex.I.times(Math.sin(theta / 2)), Complex.from(Math.cos(theta / 2), 0) ] ]); } function ry(theta) { return new Operator([ [ Complex.from(Math.cos(theta / 2), 0), Complex.from(-Math.sin(theta / 2), 0) ], [ Complex.from(Math.sin(theta / 2), 0), Complex.from(Math.cos(theta / 2), 0) ] ]); } function rz(theta) { return new Operator([ [ Complex.from(Math.cos(theta / 2), 0), Complex.I.times(Math.sin(theta / 2)) ], [ Complex.I.times(Math.sin(theta / 2)), Complex.from(Math.cos(theta / 2), 0) ] ]); } export { AngleIndicators, BaseComponent, BlochSphere, BlochSphereScene, BlochVector, Complex, Label, Operator, OperatorDisplay, OperatorPathDisplay, PathDisplay, PointsDisplay, QubitArrow, QubitDisplay, QubitProjWedge, RegionDisplay, Wedge, animate, formatDegrees, formatRadians, formatVector, gates_exports as gates, getArcBetween, getRotationArc, lerp };