@qbead/bloch-sphere
Version:
A 3D Bloch Sphere visualisation built with Three.js and TypeScript.
1,752 lines (1,625 loc) • 48.3 kB
JavaScript
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3;var __defProp = Object.defineProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
// src/colors.ts
var _three = require('three'); var THREE4 = _interopRequireWildcard(_three); var THREE3 = _interopRequireWildcard(_three); var THREE2 = _interopRequireWildcard(_three); var THREE = _interopRequireWildcard(_three); var THREE5 = _interopRequireWildcard(_three); var THREE6 = _interopRequireWildcard(_three); var THREE7 = _interopRequireWildcard(_three); var THREE8 = _interopRequireWildcard(_three); var THREE10 = _interopRequireWildcard(_three); var THREE9 = _interopRequireWildcard(_three); var THREE11 = _interopRequireWildcard(_three); var THREE12 = _interopRequireWildcard(_three); var THREE14 = _interopRequireWildcard(_three); var THREE13 = _interopRequireWildcard(_three);
var dark = new (0, _three.Color)(1114112);
var darkBlue = new (0, _three.Color)(2501700);
var lightBlue = new (0, _three.Color)(5129949);
var greyBlue = new (0, _three.Color)(5003915);
var babyBlue = new (0, _three.Color)(4956899);
var green = new (0, _three.Color)(5617541);
var lightGreen = new (0, _three.Color)(9751106);
var magenta = new (0, _three.Color)(12792181);
var yellow = new (0, _three.Color)(14791998);
var beige = new (0, _three.Color)(14474984);
var red = new (0, _three.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
var _CSS2DRendererjs = require('three/examples/jsm/renderers/CSS2DRenderer.js');
var _Addonsjs = require('three/examples/jsm/Addons.js');
// src/bloch-sphere-scene.ts
// src/components/label.ts
// src/components/component.ts
var BaseComponent = class extends THREE.Object3D {
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 {
/**
* 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 (0, _CSS2DRendererjs.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 = class extends THREE3.Scene {
__init() {this.labels = {}}
__init2() {this.plotStage = new THREE3.Group()}
constructor(options) {
options = Object.assign(
{},
BlockSphereSceneOptions,
options
);
super();_class.prototype.__init.call(this);_class.prototype.__init2.call(this);;
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();
}
}, _class);
// src/bloch-sphere.ts
var BlochSphere = class {
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 (0, _Addonsjs.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 (0, _CSS2DRendererjs.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 = _nullishCoalesce(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 = _nullishCoalesce(_nullishCoalesce(width, () => ( _optionalChain([this, 'access', _ => _.el, 'access', _2 => _2.parentElement, 'optionalAccess', _3 => _3.clientWidth]))), () => ( 200));
height = _nullishCoalesce(_nullishCoalesce(height, () => ( _optionalChain([this, 'access', _4 => _4.el, 'access', _5 => _5.parentElement, 'optionalAccess', _6 => _6.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
// 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 {
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
// 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 = (_class2 = class extends BaseComponent {
__init3() {this.units = "deg"}
__init4() {this._phiColor = new THREE6.Color(defaultColors.text)}
__init5() {this._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");_class2.prototype.__init3.call(this);_class2.prototype.__init4.call(this);_class2.prototype.__init5.call(this);;
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;
}
}, _class2);
// src/components/wedge.ts
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 {
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
// src/math/interpolation.ts
function lerp(a, b, t) {
return a + t * (b - a);
}
// src/math/bloch-vector.ts
var BlochVector = class _BlochVector extends _three.Vector3 {
/**
* 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 _three.Vector3) {
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
var _intween = require('intween');
function animate(callback, duration = 1e3, easing = "linear", loop = false) {
const easeFn = _intween.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 = (_class3 = class extends BaseComponent {
__init6() {this._anim = null}
constructor(q) {
super("qubit-display");_class3.prototype.__init6.call(this);;
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();
_optionalChain([this, 'access', _7 => _7._anim, 'optionalAccess', _8 => _8.call, 'call', _9 => _9()]);
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);
}
}, _class3);
// src/components/path-display.ts
var BlochSpherePath = class extends THREE8.Curve {
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
// src/materials/spherical-polygon.ts
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 {
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 {
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
var PointsDisplay = class extends BaseComponent {
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
// src/math/operator.ts
var Operator = class _Operator {
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 (0, _three.Quaternion)(x2.imag, y2.imag, z2.imag, w).normalize();
} else {
return new (0, _three.Quaternion)(x2.real, y2.real, z2.real, w).normalize();
}
}
};
// src/components/operator-display.ts
var OperatorDisplay = class extends BaseComponent {
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
// src/math/geometry.ts
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 {
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)
]
]);
}
exports.AngleIndicators = AngleIndicators; exports.BaseComponent = BaseComponent; exports.BlochSphere = BlochSphere; exports.BlochSphereScene = BlochSphereScene; exports.BlochVector = BlochVector; exports.Complex = Complex; exports.Label = Label; exports.Operator = Operator; exports.OperatorDisplay = OperatorDisplay; exports.OperatorPathDisplay = OperatorPathDisplay; exports.PathDisplay = PathDisplay; exports.PointsDisplay = PointsDisplay; exports.QubitArrow = QubitArrow; exports.QubitDisplay = QubitDisplay; exports.QubitProjWedge = QubitProjWedge; exports.RegionDisplay = RegionDisplay; exports.Wedge = Wedge; exports.animate = animate; exports.formatDegrees = formatDegrees; exports.formatRadians = formatRadians; exports.formatVector = formatVector; exports.gates = gates_exports; exports.getArcBetween = getArcBetween; exports.getRotationArc = getRotationArc; exports.lerp = lerp;