1/** 2 * Circuit Reflector - Animates gate effects on the Bloch sphere step-by-step 3 */ 4class CircuitReflector { 5 constructor(composer) { 6 this.composer = composer; 7 this.quantumState = composer.quantumState; 8 this.isReflecting = false; 9 this.currentStep = 0; 10 this.stateHistory = []; 11 this.originalState = null; 12 this.animationSpeed = 1000; // ms per step 13 this.animationTimer = null; 14 } 15 16 /** 17 * Start circuit reflection 18 */ 19 startReflection() { 20 if (this.isReflecting) return; 21 22 this.isReflecting = true; 23 this.currentStep = 0; 24 25 // Save original state 26 this.originalState = { 27 qubits: this.quantumState.qubits, 28 stateVector: this.quantumState.stateVector.map(c => math.complex(math.re(c), math.im(c))), 29 qubitCoordinates: JSON.parse(JSON.stringify(this.quantumState.qubitCoordinates)), 30 alpha: this.quantumState.alpha, 31 beta: this.quantumState.beta, 32 blochCoordinates: { ...this.quantumState.blochCoordinates } 33 }; 34 35 // Initialize state history 36 this.stateHistory = [{ 37 step: 0, 38 state: { ...this.originalState }, 39 description: 'Initial state |00>' 40 }]; 41 42 // Enable path tracing with bright color on both spheres 43 this.composer.blochSphere0.setPathEnabled(true); 44 this.composer.blochSphere1.setPathEnabled(true); 45 this.setReflectionPathColor(); 46 47 // Ensure path tracing is visible 48 this.composer.blochSphere0.clearPath(); 49 this.composer.blochSphere1.clearPath(); 50 51 // Apply first step if circuit has gates 52 const circuit = this.composer.getCircuit(); 53 if (circuit.length > 0) { 54 this.applyStep(0); 55 } 56 } 57 58 /** 59 * Apply a specific step 60 */ 61 applyStep(stepIndex) { 62 const circuit = this.composer.getCircuit(); 63 if (stepIndex >= circuit.length) return; 64 65 const gate = circuit[stepIndex]; 66 67 // Apply gate to quantum state 68 this.applyGateToState(gate); 69 70 // Determine animation flags for each qubit 71 const targetQubit = gate.qubit; 72 let animateQ0 = false; 73 let animateQ1 = false; 74 75 if (gate.type === 'CNOT' || gate.type === 'CZ') { 76 animateQ0 = (gate.control === 0 || gate.target === 0); 77 animateQ1 = (gate.control === 1 || gate.target === 1); 78 } else if (gate.type === 'SWAP') { 79 animateQ0 = (gate.qubit1 === 0 || gate.qubit2 === 0); 80 animateQ1 = (gate.qubit1 === 1 || gate.qubit2 === 1); 81 } else { 82 animateQ0 = (targetQubit === 0); 83 animateQ1 = (targetQubit === 1); 84 } 85 86 // Update both spheres: target animates (true), others update instantly (false) 87 const coords0 = this.quantumState.qubitCoordinates[0] || { x: 0, y: 0, z: 1 }; 88 const coords1 = this.quantumState.qubitCoordinates[1] || { x: 0, y: 0, z: 1 }; 89 90 this.composer.blochSphere0.updateStateVector(coords0, animateQ0, gate); 91 this.composer.blochSphere1.updateStateVector(coords1, animateQ1, gate); 92 93 // Set distinct color for this step 94 const stepColor = this.getStateColor(stepIndex); 95 this.composer.blochSphere0.setPathColor(stepColor); 96 this.composer.blochSphere1.setPathColor(stepColor); 97 98 // Add to state history 99 this.stateHistory.push({ 100 step: stepIndex + 1, 101 state: { 102 qubits: this.quantumState.qubits, 103 stateVector: this.quantumState.stateVector.map(c => math.complex(math.re(c), math.im(c))), 104 qubitCoordinates: JSON.parse(JSON.stringify(this.quantumState.qubitCoordinates)), 105 alpha: this.quantumState.alpha, 106 beta: this.quantumState.beta, 107 blochCoordinates: { ...this.quantumState.blochCoordinates } 108 }, 109 description: this.getGateDescription(gate) 110 }); 111 112 // Highlight current gate in circuit 113 this.highlightGate(stepIndex); 114 115 // Update step counter 116 this.currentStep = stepIndex + 1; 117 } 118 119 /** 120 * Go to a specific step (without applying subsequent gates) 121 */ 122 goToStep(stepIndex) { 123 if (stepIndex < 0 || stepIndex > this.stateHistory.length - 1) return; 124 125 // Restore state from history 126 const historyEntry = this.stateHistory[stepIndex]; 127 this.quantumState.qubits = historyEntry.state.qubits; 128 if (historyEntry.state.stateVector) { 129 this.quantumState.stateVector = historyEntry.state.stateVector.map(c => math.complex(math.re(c), math.im(c))); 130 } 131 if (historyEntry.state.qubitCoordinates) { 132 this.quantumState.qubitCoordinates = JSON.parse(JSON.stringify(historyEntry.state.qubitCoordinates)); 133 } 134 this.quantumState.alpha = historyEntry.state.alpha; 135 this.quantumState.beta = historyEntry.state.beta; 136 this.quantumState.blochCoordinates = { ...historyEntry.state.blochCoordinates }; 137 138 // Update Bloch spheres with proper arrow movement 139 const coords0 = this.quantumState.qubitCoordinates[0] || { x: 0, y: 0, z: 1 }; 140 const coords1 = this.quantumState.qubitCoordinates[1] || { x: 0, y: 0, z: 1 }; 141 142 this.composer.blochSphere0.updateStateVector(coords0, true); 143 this.composer.blochSphere1.updateStateVector(coords1, true); 144 145 // Update step counter 146 this.currentStep = stepIndex; 147 148 // Highlight current gate 149 this.highlightGate(stepIndex - 1); 150 } 151 152 /** 153 * Apply gate to quantum state 154 */ 155 applyGateToState(gate) { 156 const { type, qubit, params } = gate; 157 158 // Define matrices for standard single-qubit gates 159 const H = [ 160 [math.divide(1, math.sqrt(2)), math.divide(1, math.sqrt(2))], 161 [math.divide(1, math.sqrt(2)), math.divide(-1, math.sqrt(2))] 162 ]; 163 const X = [[0, 1], [1, 0]]; 164 const Y = [[0, math.complex(0, -1)], [math.complex(0, 1), 0]]; 165 const Z = [[1, 0], [0, -1]]; 166 const S = [[1, 0], [0, math.complex(0, 1)]]; 167 const T = [[1, 0], [0, math.exp(math.complex(0, Math.PI / 4))]]; 168 169 switch (type) { 170 case 'H': 171 this.quantumState.applySingleQubitGate(qubit, H); 172 break; 173 case 'X': 174 this.quantumState.applySingleQubitGate(qubit, X); 175 break; 176 case 'Y': 177 this.quantumState.applySingleQubitGate(qubit, Y); 178 break; 179 case 'Z': 180 this.quantumState.applySingleQubitGate(qubit, Z); 181 break; 182 case 'S': 183 this.quantumState.applySingleQubitGate(qubit, S); 184 break; 185 case 'T': 186 this.quantumState.applySingleQubitGate(qubit, T); 187 break; 188 case 'RX': { 189 const rxAngle = (params.angle * Math.PI) / 180; 190 const rx = [ 191 [Math.cos(rxAngle / 2), math.complex(0, -Math.sin(rxA
191ngle / 2))], 192 [math.complex(0, -Math.sin(rxAngle / 2)), Math.cos(rxAngle / 2)] 193 ]; 194 this.quantumState.applySingleQubitGate(qubit, rx); 195 break; 196 } 197 case 'RY': { 198 const ryAngle = (params.angle * Math.PI) / 180; 199 const ry = [ 200 [Math.cos(ryAngle / 2), -Math.sin(ryAngle / 2)], 201 [Math.sin(ryAngle / 2), Math.cos(ryAngle / 2)] 202 ]; 203 this.quantumState.applySingleQubitGate(qubit, ry); 204 break; 205 } 206 case 'RZ': { 207 const rzAngle = (params.angle * Math.PI) / 180; 208 const rz = [ 209 [math.exp(math.complex(0, -rzAngle / 2)), 0], 210 [0, math.exp(math.complex(0, rzAngle / 2))] 211 ]; 212 this.quantumState.applySingleQubitGate(qubit, rz); 213 break; 214 } 215 case 'CNOT': { 216 const control = gate.control !== undefined ? gate.control : qubit; 217 const target = gate.target !== undefined ? gate.target : (qubit + 1) % this.quantumState.qubits; 218 this.quantumState.applyCNOT(control, target); 219 break; 220 } 221 case 'CZ': { 222 const control = gate.control !== undefined ? gate.control : qubit; 223 const target = gate.target !== undefined ? gate.target : (qubit + 1) % this.quantumState.qubits; 224 this.quantumState.applyCZ(control, target); 225 break; 226 } 227 case 'SWAP': { 228 const qubit1 = gate.qubit1 !== undefined ? gate.qubit1 : qubit; 229 const qubit2 = gate.qubit2 !== undefined ? gate.qubit2 : (qubit + 1) % this.quantumState.qubits; 230 this.quantumState.applySWAP(qubit1, qubit2); 231 break; 232 } 233 case 'TOFFOLI': { 234 const control1 = gate.control1 !== undefined ? gate.control1 : qubit; 235 const control2 = gate.control2 !== undefined ? gate.control2 : (qubit + 1) % this.quantumState.qubits; 236 const target = gate.target !== undefined ? gate.target : (qubit + 2) % this.quantumState.qubits; 237 this.quantumState.applyToffoli(control1, control2, target); 238 break; 239 } 240 case 'M': 241 // Measurement is a no-op during animation reflection 242 break; 243 default: 244 console.warn(`Unknown gate type: ${type}`); 245 } 246 247 // Update Bloch coordinates 248 this.quantumState.updateBlochCoordinates(); 249 } 250 251 /** 252 * Get description for a gate 253 */ 254 getGateDescription(gate) { 255 const { type, qubit, params } = gate; 256 257 let description = `${type} on Q${qubit}`; 258 259 if (params.angle !== undefined) { 260 description += ` (${params.angle}°)`; 261 } 262 263 if (gate.type === 'CNOT') { 264 description = `CNOT: Q${gate.control} â Q${gate.target}`; 265 } else if (gate.type === 'SWAP') { 266 description = `SWAP: Q${gate.qubit1} â Q${gate.qubit2}`; 267 } else if (gate.type === 'TOFFOLI') { 268 description = `Toffoli: Q${gate.control1},Q${gate.control2} â Q${gate.target}`; 269 } 270 271 return description; 272 } 273 274 /** 275 * Highlight current gate in circuit 276 */ 277 highlightGate(stepIndex) { 278 // Remove previous highlights 279 document.querySelectorAll('.gate-element.highlighted').forEach(el => { 280 el.classList.remove('highlighted'); 281 }); 282 283 // Add highlight to current gate 284 const circuit = this.composer.getCircuit(); 285 if (stepIndex >= 0 && stepIndex < circuit.length) { 286 const gate = circuit[stepIndex]; 287 const gateElement = document.querySelector( 288 `.gate-element[data-qubit="${gate.qubit}"][data-time="${gate.time}"]` 289 ); 290 if (gateElement) { 291 gateElement.classList.add('highlighted'); 292 } 293 } 294 } 295 296 /** 297 * Set reflection path color 298 */ 299 setReflectionPathColor() { 300 // Use signature vibrant green color for reflection traces 301 this.composer.blochSphere0.setPathColor('#34C759'); 302 this.composer.blochSphere1.setPathColor('#34C759'); 303 } 304 305 /** 306 * Get color for qubit state based on step 307 */ 308 getStateColor(stepIndex) { 309 const colors = [ 310 '#34C759', // Vibrant Green (--accent-green) 311 '#FF9F0A', // Vibrant Orange (--accent-orange) 312 '#64D2FF', // Sleek Sky Blue 313 '#BF5AF2', // Premium Violet 314 '#FF6482', // Elegant Rose 315 '#E4E4E7', // Crisp Zinc White (--accent-zinc) 316 '#FFD60A', // Warm Yellow 317 '#30D158', // Minty Green 318 '#FF453A', // Modern Soft Red 319 '#98989D' // Medium Zinc Grey 320 ]; 321 return colors[stepIndex % colors.length]; 322 } 323 324 /** 325 * Reset to original state 326 */ 327 reset() { 328 if (!this.originalState) return; 329 330 // Restore original state 331 this.quantumState.qubits = this.originalState.qubits; 332 if (this.originalState.stateVector) { 333 this.quantumState.stateVector = this.originalState.stateVector.map(c => math.complex(math.re(c), math.im(c))); 334 } 335 if (this.originalState.qubitCoordinates) { 336 this.quantumState.qubitCoordinates = JSON.parse(JSON.stringify(this.originalState.qubitCoordinates)); 337 } 338 this.quantumState.alpha = this.originalState.alpha; 339 this.quantumState.beta = this.originalState.beta; 340 this.quantumState.blochCoordinates = { ...this.originalState.blochCoordinates }; 341 342 // Update Bloch sphere with proper arrow movement 343 const coords0 = this.quantumState.qubitCoordinates[0] || { x: 0, y: 0, z: 1 }; 344 const coords1 = this.quantumState.qubitCoordinates[1] || { x: 0, y: 0, z: 1 }; 345 this.composer.blochSphere0.updateStateVector(coords0, true); 346 this.composer.blochSphere1.updateStateVector(coords1, true); 347 348 // Clear state history 349 this.stateHistory = [{ 350 step: 0, 351 state: { ...this.originalState }, 352 description: 'Initial state |00>' 353 }]; 354 355 // Reset step counter 356 this.currentStep = 0; 357 358 // Clear the visual path trail so the next reflection starts fresh 359 this.composer.blochSphere0.clearPath(); 360 this.composer.blochSphere1.clearPath(); 361 362 // Remove highlights 363 document.querySelectorAll('.gate-element.highlighted').forEach(el => { 364 el.classList.remove('highlighted'); 365 }); 366 } 367 368 /** 369 * Stop reflection 370 */ 371 stop() { 372 this.isReflecting = false;
373 374 // Clear animation timer 375 if (this.animationTimer) { 376 clearTimeout(this.animationTimer); 377 this.animationTimer = null; 378 } 379 380 // Remove highlights 381 document.querySelectorAll('.gate-element.highlighted').forEach(el => { 382 el.classList.remove('highlighted'); 383 }); 384 385 // Reset path color to brand orange 386 this.composer.blochSphere0.setPathColor('#FF9F0A'); 387 this.composer.blochSphere1.setPathColor('#FF9F0A'); 388 } 389 390 /** 391 * Animate through all steps automatically 392 */ 393 animateAllSteps() { 394 // If already reflecting, stop the current session before restarting 395 if (this.isReflecting) { 396 this.stop(); 397 } 398 399 this.startReflection(); 400 this.animateNextStep(); 401 } 402 403 /** 404 * Animate to next step 405 */ 406 animateNextStep() { 407 const circuit = this.composer.getCircuit(); 408 409 if (this.currentStep < circuit.length) { 410 this.applyStep(this.currentStep); 411 412 // Schedule next step 413 this.animationTimer = setTimeout(() => { 414 this.animateNextStep(); 415 }, this.animationSpeed); 416 } else { 417 // Animation complete 418 this.isReflecting = false; 419 this.animationTimer = null; 420 } 421 } 422 423 /** 424 * Pause animation 425 */ 426 pauseAnimation() { 427 if (this.animationTimer) { 428 clearTimeout(this.animationTimer); 429 this.animationTimer = null; 430 } 431 } 432 433 /** 434 * Resume animation 435 */ 436 resumeAnimation() { 437 if (this.isReflecting && !this.animationTimer) { 438 this.animateNextStep(); 439 } 440 } 441 442 /** 443 * Set animation speed 444 */ 445 setAnimationSpeed(speedMs) { 446 this.animationSpeed = Math.max(100, Math.min(5000, speedMs)); 447 } 448 449 /** 450 * Get current state description 451 */ 452 getCurrentStateDescription() { 453 if (this.stateHistory.length === 0) return 'No state'; 454 455 const currentEntry = this.stateHistory[this.currentStep]; 456 return currentEntry ? currentEntry.description : 'Unknown state'; 457 } 458 459 /** 460 * Get state history 461 */ 462 getStateHistory() { 463 return this.stateHistory; 464 } 465 466 /** 467 * Get current step 468 */ 469 getCurrentStep() { 470 return this.currentStep; 471 } 472 473 /** 474 * Check if reflection is active 475 */ 476 isActive() { 477 return this.isReflecting; 478 } 479 480 /** 481 * Get circuit statistics for reflection 482 */ 483 getReflectionStats() { 484 const circuit = this.composer.getCircuit(); 485 const totalSteps = circuit.length; 486 const completedSteps = this.currentStep; 487 const progress = totalSteps > 0 ? (completedSteps / totalSteps) * 100 : 0; 488 489 return { 490 totalSteps, 491 completedSteps, 492 progress, 493 isComplete: completedSteps >= totalSteps, 494 currentState: this.getCurrentStateDescription() 495 }; 496 } 497 498 /** 499 * Export reflection data 500 */ 501 exportReflectionData() { 502 return { 503 circuit: this.composer.getCircuit(), 504 stateHistory: this.stateHistory, 505 originalState: this.originalState, 506 stats: this.getReflectionStats(), 507 exportDate: new Date().toISOString() 508 }; 509 } 510 511 /** 512 * Import reflection data 513 */ 514 importReflectionData(data) { 515 try { 516 // Import circuit 517 if (data.circuit) { 518 this.composer.clearCircuit(); 519 data.circuit.forEach(gate => { 520 this.composer.addGate(gate.type, gate.qubit, gate.time, gate.params); 521 }); 522 } 523 524 // Import state history 525 if (data.stateHistory) { 526 this.stateHistory = data.stateHistory; 527 } 528 529 // Import original state 530 if (data.originalState) { 531 this.originalState = data.originalState; 532 } 533 534 return true; 535 } catch (error) { 536 console.error('Error importing reflection data:', error); 537 return false;
538 } 539 } 540}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.