@neuroequality/neuroadapt-quantum
Version:
Accessible quantum visualization and Bloch sphere rendering for NeuroAdapt SDK
590 lines (584 loc) • 19.8 kB
JavaScript
import { EventEmitter } from 'eventemitter3';
import * as THREE from 'three';
class BlochSphereRenderer extends EventEmitter {
constructor(options, accessibilityOptions = {}) {
super();
this.animationId = null;
this.container = options.container;
this.options = {
width: 400,
height: 400,
showAxes: true,
showLabels: true,
colorBlindFriendly: false,
highContrast: false,
animationSpeed: 1,
accessibleColors: true,
...options
};
this.accessibilityOptions = {
announceStateChanges: true,
verboseDescriptions: false,
colorBlindSupport: false,
highContrast: false,
reducedMotion: false,
audioFeedback: false,
...accessibilityOptions
};
this.initializeScene();
this.createBlochSphere();
this.createAxes();
this.createLabels();
this.setupControls();
this.render();
}
initializeScene() {
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(
this.accessibilityOptions.highContrast ? 0 : 16119285
);
this.camera = new THREE.PerspectiveCamera(
75,
this.options.width / this.options.height,
0.1,
1e3
);
this.camera.position.set(3, 3, 3);
this.camera.lookAt(0, 0, 0);
this.renderer = new THREE.WebGLRenderer({
antialias: !this.accessibilityOptions.reducedMotion,
alpha: true
});
this.renderer.setSize(this.options.width, this.options.height);
this.renderer.shadowMap.enabled = !this.accessibilityOptions.reducedMotion;
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
this.container.appendChild(this.renderer.domElement);
this.renderer.domElement.setAttribute("role", "img");
this.renderer.domElement.setAttribute("aria-label", "Bloch sphere quantum state visualization");
this.renderer.domElement.setAttribute("tabindex", "0");
}
createBlochSphere() {
const geometry = new THREE.SphereGeometry(1, 32, 32);
const material = new THREE.MeshPhongMaterial({
color: this.getAccessibleColor("sphere"),
transparent: true,
opacity: 0.3,
wireframe: this.accessibilityOptions.highContrast
});
this.sphere = new THREE.Mesh(geometry, material);
this.sphere.castShadow = true;
this.sphere.receiveShadow = true;
this.scene.add(this.sphere);
const ambientLight = new THREE.AmbientLight(4210752, 0.6);
this.scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(16777215, 0.8);
directionalLight.position.set(5, 5, 5);
directionalLight.castShadow = true;
this.scene.add(directionalLight);
}
createAxes() {
if (!this.options.showAxes) return;
this.axes = new THREE.Group();
const xAxis = new THREE.ArrowHelper(
new THREE.Vector3(1, 0, 0),
new THREE.Vector3(0, 0, 0),
1.5,
this.getAccessibleColor("x-axis"),
0.2,
0.1
);
this.axes.add(xAxis);
const yAxis = new THREE.ArrowHelper(
new THREE.Vector3(0, 1, 0),
new THREE.Vector3(0, 0, 0),
1.5,
this.getAccessibleColor("y-axis"),
0.2,
0.1
);
this.axes.add(yAxis);
const zAxis = new THREE.ArrowHelper(
new THREE.Vector3(0, 0, 1),
new THREE.Vector3(0, 0, 0),
1.5,
this.getAccessibleColor("z-axis"),
0.2,
0.1
);
this.axes.add(zAxis);
this.scene.add(this.axes);
}
createLabels() {
if (!this.options.showLabels) return;
this.labels = new THREE.Group();
new THREE.FontLoader();
const createLabel = (text, position, color) => {
const geometry = new THREE.PlaneGeometry(0.3, 0.2);
const material = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: this.accessibilityOptions.highContrast ? 1 : 0.8
});
const label = new THREE.Mesh(geometry, material);
label.position.copy(position);
label.lookAt(this.camera.position);
return label;
};
const labelData = [
{ text: "|0⟩", position: new THREE.Vector3(0, 0, 1.8), color: this.getAccessibleColor("label") },
{ text: "|1⟩", position: new THREE.Vector3(0, 0, -1.8), color: this.getAccessibleColor("label") },
{ text: "|+⟩", position: new THREE.Vector3(1.8, 0, 0), color: this.getAccessibleColor("label") },
{ text: "|-⟩", position: new THREE.Vector3(-1.8, 0, 0), color: this.getAccessibleColor("label") },
{ text: "|+i⟩", position: new THREE.Vector3(0, 1.8, 0), color: this.getAccessibleColor("label") },
{ text: "|-i⟩", position: new THREE.Vector3(0, -1.8, 0), color: this.getAccessibleColor("label") }
];
labelData.forEach(({ text, position, color }) => {
const label = createLabel(text, position, color);
this.labels.add(label);
});
this.scene.add(this.labels);
}
setupControls() {
this.renderer.domElement.addEventListener("keydown", (event) => {
switch (event.key) {
case "ArrowLeft":
this.camera.position.x -= 0.1;
break;
case "ArrowRight":
this.camera.position.x += 0.1;
break;
case "ArrowUp":
this.camera.position.y += 0.1;
break;
case "ArrowDown":
this.camera.position.y -= 0.1;
break;
case "Enter":
this.announceCurrentState();
break;
}
this.camera.lookAt(0, 0, 0);
event.preventDefault();
});
let isMouseDown = false;
let mouseX = 0;
let mouseY = 0;
this.renderer.domElement.addEventListener("mousedown", (event) => {
isMouseDown = true;
mouseX = event.clientX;
mouseY = event.clientY;
});
this.renderer.domElement.addEventListener("mousemove", (event) => {
if (!isMouseDown || this.accessibilityOptions.reducedMotion) return;
const deltaX = event.clientX - mouseX;
const deltaY = event.clientY - mouseY;
const spherical = new THREE.Spherical();
spherical.setFromVector3(this.camera.position);
spherical.theta -= deltaX * 0.01;
spherical.phi += deltaY * 0.01;
spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi));
this.camera.position.setFromSpherical(spherical);
this.camera.lookAt(0, 0, 0);
mouseX = event.clientX;
mouseY = event.clientY;
});
this.renderer.domElement.addEventListener("mouseup", () => {
isMouseDown = false;
});
}
updateState(qubitState) {
const vector = this.stateToBlochVector(qubitState);
this.updateBlochVector(vector);
if (this.accessibilityOptions.announceStateChanges) {
this.announceStateChange(vector);
}
this.emit("state-updated", { qubit: 0, state: qubitState, vector });
}
updateBlochVector(vector) {
if (this.stateVector) {
this.scene.remove(this.stateVector);
}
const direction = new THREE.Vector3(vector.x, vector.y, vector.z).normalize();
const length = Math.sqrt(vector.x ** 2 + vector.y ** 2 + vector.z ** 2);
this.stateVector = new THREE.ArrowHelper(
direction,
new THREE.Vector3(0, 0, 0),
length,
this.getAccessibleColor("state-vector"),
0.3,
0.2
);
this.scene.add(this.stateVector);
}
stateToBlochVector(state) {
const { alpha, beta } = state;
const x = 2 * (alpha.real * beta.real + alpha.imaginary * beta.imaginary);
const y = 2 * (alpha.imaginary * beta.real - alpha.real * beta.imaginary);
const z = alpha.real ** 2 + alpha.imaginary ** 2 - beta.real ** 2 - beta.imaginary ** 2;
return { x, y, z };
}
getAccessibleColor(element) {
if (this.accessibilityOptions.colorBlindSupport) {
const colorBlindPalette = {
"sphere": 4552116,
"x-axis": 14948892,
"y-axis": 5091146,
"z-axis": 3636920,
"state-vector": 16744192,
"label": 0
};
return colorBlindPalette[element] || 6710886;
}
if (this.accessibilityOptions.highContrast) {
const highContrastPalette = {
"sphere": 0,
"x-axis": 16777215,
"y-axis": 16777215,
"z-axis": 16777215,
"state-vector": 16776960,
"label": 16777215
};
return highContrastPalette[element] || 16777215;
}
const defaultPalette = {
"sphere": 8900331,
"x-axis": 16711680,
"y-axis": 65280,
"z-axis": 255,
"state-vector": 16739125,
"label": 3355443
};
return defaultPalette[element] || 6710886;
}
announceStateChange(vector) {
const magnitude = Math.sqrt(vector.x ** 2 + vector.y ** 2 + vector.z ** 2);
const description = `Quantum state updated. Bloch vector at coordinates: X ${vector.x.toFixed(2)}, Y ${vector.y.toFixed(2)}, Z ${vector.z.toFixed(2)}. Magnitude: ${magnitude.toFixed(2)}`;
this.announceToScreenReader(description);
}
announceCurrentState() {
if (!this.stateVector) return;
const position = this.stateVector.position;
const description = `Current quantum state: Bloch vector pointing to X ${position.x.toFixed(2)}, Y ${position.y.toFixed(2)}, Z ${position.z.toFixed(2)}`;
this.announceToScreenReader(description);
}
announceToScreenReader(message) {
let liveRegion = document.getElementById("bloch-sphere-announcements");
if (!liveRegion) {
liveRegion = document.createElement("div");
liveRegion.id = "bloch-sphere-announcements";
liveRegion.setAttribute("aria-live", "polite");
liveRegion.setAttribute("aria-atomic", "true");
liveRegion.style.position = "absolute";
liveRegion.style.left = "-10000px";
liveRegion.style.width = "1px";
liveRegion.style.height = "1px";
liveRegion.style.overflow = "hidden";
document.body.appendChild(liveRegion);
}
liveRegion.textContent = message;
}
exportFrame() {
return this.renderer.domElement.toDataURL("image/png");
}
resize(width, height) {
this.options.width = width;
this.options.height = height;
this.camera.aspect = width / height;
this.camera.updateProjectionMatrix();
this.renderer.setSize(width, height);
}
render() {
const animate = () => {
if (!this.accessibilityOptions.reducedMotion) {
this.animationId = requestAnimationFrame(animate);
}
if (this.labels) {
this.labels.children.forEach((label) => {
label.lookAt(this.camera.position);
});
}
this.renderer.render(this.scene, this.camera);
};
animate();
}
dispose() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
this.scene.clear();
this.renderer.dispose();
if (this.container.contains(this.renderer.domElement)) {
this.container.removeChild(this.renderer.domElement);
}
const liveRegion = document.getElementById("bloch-sphere-announcements");
if (liveRegion) {
document.body.removeChild(liveRegion);
}
}
}
class QuantumCircuitSimulator extends EventEmitter {
constructor(numQubits) {
super();
this.qubits = [];
this.executionStep = 0;
this.measurementResults = /* @__PURE__ */ new Map();
this.circuit = {
qubits: numQubits,
gates: [],
measurements: []
};
this.initializeQubits();
}
initializeQubits() {
this.qubits = Array(this.circuit.qubits).fill(null).map(() => ({
alpha: { real: 1, imaginary: 0 },
// |0⟩ state
beta: { real: 0, imaginary: 0 }
}));
}
addGate(gate) {
const gateWithMatrix = {
...gate,
matrix: this.getGateMatrix(gate.type, gate.angle)
};
this.circuit.gates.push(gateWithMatrix);
}
getGateMatrix(type, angle) {
const cos = (a) => Math.cos(a);
const sin = (a) => Math.sin(a);
const zero = { real: 0, imaginary: 0 };
const one = { real: 1, imaginary: 0 };
const minusOne = { real: -1, imaginary: 0 };
const i = { real: 0, imaginary: 1 };
const minusI = { real: 0, imaginary: -1 };
switch (type) {
case "X":
return [
[zero, one],
[one, zero]
];
case "Y":
return [
[zero, minusI],
[i, zero]
];
case "Z":
return [
[one, zero],
[zero, minusOne]
];
case "H":
const invSqrt2 = { real: 1 / Math.sqrt(2), imaginary: 0 };
return [
[invSqrt2, invSqrt2],
[invSqrt2, { real: -1 / Math.sqrt(2), imaginary: 0 }]
];
case "S":
return [
[one, zero],
[zero, i]
];
case "T":
return [
[one, zero],
[zero, { real: cos(Math.PI / 4), imaginary: sin(Math.PI / 4) }]
];
case "RX":
if (angle === void 0) throw new Error("RX gate requires angle parameter");
return [
[{ real: cos(angle / 2), imaginary: 0 }, { real: 0, imaginary: -sin(angle / 2) }],
[{ real: 0, imaginary: -sin(angle / 2) }, { real: cos(angle / 2), imaginary: 0 }]
];
case "RY":
if (angle === void 0) throw new Error("RY gate requires angle parameter");
return [
[{ real: cos(angle / 2), imaginary: 0 }, { real: -sin(angle / 2), imaginary: 0 }],
[{ real: sin(angle / 2), imaginary: 0 }, { real: cos(angle / 2), imaginary: 0 }]
];
case "RZ":
if (angle === void 0) throw new Error("RZ gate requires angle parameter");
return [
[{ real: cos(angle / 2), imaginary: -sin(angle / 2) }, zero],
[zero, { real: cos(angle / 2), imaginary: sin(angle / 2) }]
];
default:
throw new Error(`Unsupported gate type: ${type}`);
}
}
executeCircuit() {
this.qubits.map((q) => ({ ...q }));
for (const gate of this.circuit.gates) {
this.applyGate(gate);
this.executionStep++;
}
for (const measurement of this.circuit.measurements) {
this.performMeasurement(measurement);
}
this.emit("circuit-executed", {
circuit: this.circuit,
finalStates: this.qubits
});
}
applyGate(gate) {
if (!gate.matrix) return;
const beforeQubit = this.qubits[gate.target];
if (!beforeQubit) {
throw new Error(`Qubit at index ${gate.target} is undefined`);
}
const beforeState = {
alpha: { real: beforeQubit.alpha.real, imaginary: beforeQubit.alpha.imaginary },
beta: { real: beforeQubit.beta.real, imaginary: beforeQubit.beta.imaginary }
};
if (gate.type === "CNOT" && gate.control !== void 0) {
this.applyCNOTGate(gate.control, gate.target);
} else {
this.applySingleQubitGate(gate.target, gate.matrix);
}
const afterQubit = this.qubits[gate.target];
if (!afterQubit) {
throw new Error(`Qubit at index ${gate.target} is undefined after gate application`);
}
this.emit("gate-applied", {
gate,
beforeState,
afterState: afterQubit
});
}
applySingleQubitGate(qubit, matrix) {
if (qubit >= this.qubits.length) {
throw new Error(`Qubit index ${qubit} out of range`);
}
const state = this.qubits[qubit];
if (!state) {
throw new Error(`Qubit state at index ${qubit} is undefined`);
}
const [alpha, beta] = [state.alpha, state.beta];
const newAlpha = this.complexAdd(
this.complexMultiply(matrix[0]?.[0] ?? { real: 0, imaginary: 0 }, alpha),
this.complexMultiply(matrix[0]?.[1] ?? { real: 0, imaginary: 0 }, beta)
);
const newBeta = this.complexAdd(
this.complexMultiply(matrix[1]?.[0] ?? { real: 0, imaginary: 0 }, alpha),
this.complexMultiply(matrix[1]?.[1] ?? { real: 0, imaginary: 0 }, beta)
);
this.qubits[qubit] = { alpha: newAlpha, beta: newBeta };
}
applyCNOTGate(control, target) {
const controlState = this.qubits[control];
const targetState = this.qubits[target];
if (!controlState || !targetState) {
throw new Error(`Invalid qubit indices for CNOT gate: control=${control}, target=${target}`);
}
const controlProbabilityOne = this.getStateProbability(controlState, 1);
if (controlProbabilityOne > 0.5) {
const xMatrix = this.getGateMatrix("X");
this.applySingleQubitGate(target, xMatrix);
}
}
performMeasurement(measurement) {
const qubit = this.qubits[measurement.qubit];
if (!qubit) {
throw new Error(`Qubit at index ${measurement.qubit} is undefined`);
}
const probability = this.getStateProbability(qubit, 1);
const result = Math.random() < probability ? 1 : 0;
measurement.result = result;
this.measurementResults.set(measurement.qubit, result);
if (result === 0) {
this.qubits[measurement.qubit] = {
alpha: { real: 1, imaginary: 0 },
beta: { real: 0, imaginary: 0 }
};
} else {
this.qubits[measurement.qubit] = {
alpha: { real: 0, imaginary: 0 },
beta: { real: 1, imaginary: 0 }
};
}
this.emit("measurement-performed", { measurement, probability });
}
getStateProbability(state, outcome) {
if (outcome === 0) {
return state.alpha.real ** 2 + state.alpha.imaginary ** 2;
} else {
return state.beta.real ** 2 + state.beta.imaginary ** 2;
}
}
complexAdd(a, b) {
return {
real: a.real + b.real,
imaginary: a.imaginary + b.imaginary
};
}
complexMultiply(a, b) {
return {
real: a.real * b.real - a.imaginary * b.imaginary,
imaginary: a.real * b.imaginary + a.imaginary * b.real
};
}
getQubitState(index) {
if (index >= this.qubits.length) {
throw new Error(`Qubit index ${index} out of range`);
}
return { ...this.qubits[index] };
}
getAllStates() {
return this.qubits.map((q) => ({ ...q }));
}
getBlochVector(qubit) {
const state = this.getQubitState(qubit);
const x = 2 * (state.alpha.real * state.beta.real + state.alpha.imaginary * state.beta.imaginary);
const y = 2 * (state.alpha.imaginary * state.beta.real - state.alpha.real * state.beta.imaginary);
const z = state.alpha.real ** 2 + state.alpha.imaginary ** 2 - state.beta.real ** 2 - state.beta.imaginary ** 2;
return { x, y, z };
}
addMeasurement(qubit, basis = "computational") {
this.circuit.measurements.push({ qubit, basis });
}
reset() {
this.circuit.gates = [];
this.circuit.measurements = [];
this.measurementResults.clear();
this.executionStep = 0;
this.initializeQubits();
}
getCircuit() {
return { ...this.circuit };
}
getMeasurementResults() {
return new Map(this.measurementResults);
}
}
function createCircuit(numQubits) {
return new QuantumCircuitSimulator(numQubits);
}
function bellState() {
const circuit = new QuantumCircuitSimulator(2);
circuit.addGate({ type: "H", target: 0 });
circuit.addGate({ type: "CNOT", target: 1, control: 0 });
return circuit;
}
function ghzState(numQubits) {
const circuit = new QuantumCircuitSimulator(numQubits);
circuit.addGate({ type: "H", target: 0 });
for (let i = 1; i < numQubits; i++) {
circuit.addGate({ type: "CNOT", target: i, control: 0 });
}
return circuit;
}
function createQubitState(alpha, beta) {
return { alpha, beta };
}
function createGroundState() {
return createQubitState({ real: 1, imaginary: 0 }, { real: 0, imaginary: 0 });
}
function createExcitedState() {
return createQubitState({ real: 0, imaginary: 0 }, { real: 1, imaginary: 0 });
}
function createSuperpositionState() {
const norm = 1 / Math.sqrt(2);
return createQubitState({ real: norm, imaginary: 0 }, { real: norm, imaginary: 0 });
}
const VERSION = "1.1.0";
export { BlochSphereRenderer, QuantumCircuitSimulator, VERSION, bellState, createCircuit, createExcitedState, createGroundState, createQubitState, createSuperpositionState, ghzState };
//# sourceMappingURL=index.es.js.map