UNPKG

@neuroequality/neuroadapt-quantum

Version:

Accessible quantum visualization and Bloch sphere rendering for NeuroAdapt SDK

590 lines (584 loc) 19.8 kB
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