1/** 2 * Circuit Composer - Main module for quantum circuit composition 3 * Integrates grid, gates, drag handling, and reflection functionality 4 */ 5class CircuitComposer { 6 constructor(blochSphere0, blochSphere1, quantumState) { 7 this.blochSphere0 = blochSphere0; 8 this.blochSphere1 = blochSphere1; 9 this.quantumState = quantumState; 10 11 // Circuit state 12 this.qubits = 1; // Default to 1 qubit 13 this.maxTimeSteps = 10; 14 this.circuit = []; 15 this.selectedGate = null; 16 this.isReflecting = false; 17 this.reflectionStep = 0; 18 this.isMeasurementCollapseMode = false; 19 this.measurementCache = new Map(); 20 21 // Track last rendered qubit-visibility state so forceResize() is only 22 // called when visibility genuinely changes, not on every executeCircuit(). 23 this._lastVisibleQ1 = null; 24 25 // Initialize sub-modules 26 this.grid = new CircuitGrid(this); 27 this.gates = new CircuitGates(this); 28 this.dragHandler = new CircuitDragHandler(this); 29 this.exporter = new CircuitExporter(this); 30 this.reflector = new CircuitReflector(this); 31 32 // Create UI 33 this.createUI(); 34 this.initEventListeners(); 35 36 // Execute circuit to sync initial state and layout 37 this.executeCircuit(); 38 } 39 40 /** 41 * Create the circuit composer UI 42 */ 43 createUI() { 44 // Create main container 45 this.container = document.createElement('div'); 46 this.container.className = 'circuit-composer'; 47 this.container.innerHTML = ` 48 <div class="circuit-header"> 49 <h2>Compose Circuit</h2> 50 <div class="circuit-controls"> 51 <button id="add-qubit" class="circuit-btn">+ Qubit</button> 52 <button id="remove-qubit" class="circuit-btn">- Qubit</button> 53 <button id="clear-circuit" class="circuit-btn">Clear</button> 54 <button id="export-circuit" class="circuit-btn">Export</button> 55 <button id="reflect-circuit" class="circuit-btn primary">Implement</button> 56 <button id="execute-circuit" class="circuit-btn execute">Execute</button> 57 <label class="collapse-toggle-label" style="display: inline-flex; align-items: center; margin-left: 10px; font-size: 12px; color: rgba(255, 255, 255, 0.7); cursor: pointer; user-select: none;"> 58 <input type="checkbox" id="toggle-collapse-mode" style="margin-right: 5px; cursor: pointer;"> Collapse on M 59 </label> 60 </div> 61 </div> 62 63 <div class="circuit-toolbar"> 64 <div class="gate-toolbar"> 65 <h3>Single Qubit Gates</h3> 66 <div class="gate-buttons"> 67 <button class="gate-tool-btn" data-gate="H">H</button> 68 <button class="gate-tool-btn" data-gate="X">X</button> 69 <button class="gate-tool-btn" data-gate="Y">Y</button> 70 <button class="gate-tool-btn" data-gate="Z">Z</button> 71 <button class="gate-tool-btn" data-gate="S">S</button> 72 <button class="gate-tool-btn" data-gate="T">T</button> 73 <button class="gate-tool-btn" data-gate="M" style="border-color: #bf5af2; color: #bf5af2;">M</button> 74 </div> 75 <h3>Rotation Gates</h3> 76 <div class="gate-buttons"> 77 <button class="gate-tool-btn" data-gate="RX">RX</button> 78 <button class="gate-tool-btn" data-gate="RY">RY</button> 79 <button class="gate-tool-btn" data-gate="RZ">RZ</button> 80 </div> 81 <h3>Multi Qubit Gates</h3> 82 <div class="gate-buttons"> 83 <button class="gate-tool-btn" data-gate="CNOT">CNOT</button> 84 <button class="gate-tool-btn" data-gate="SWAP">SWAP</button> 85 <button class="gate-tool-btn" data-gate="TOFFOLI">Toffoli</button> 86 </div> 87 </div> 88 </div> 89 90 <div class="circuit-workspace"> 91 <div id="circuit-grid" class="circuit-grid"></div> 92 </div> 93 94 <div class="reflection-controls" style="display: none;"> 95 <div class="reflection-info">
96 <span id="reflection-step">Step 0/0</span> 97 <span id="reflection-gate">No gate</span> 98 </div> 99 <div class="reflection-buttons"> 100 <button id="prev-step" class="circuit-btn">Previous</button> 101 <button id="next-step" class="circuit-btn">Next</button> 102 <button id="reset-reflection" class="circuit-btn">Reset</button> 103 <button id="stop-reflection" class="circuit-btn">Stop</button> 104 </div> 105 </div> 106 107 <div class="execution-output" style="display: none;"> 108 <h3>Circuit Output</h3> 109 <div class="ket-output"> 110 <span class="ket-label">|Ïâ© = </span> 111 <span id="ket-result" class="ket-formula"></span> 112 </div> 113 <div class="output-details"> 114 <div class="output-item"> 115 <span class="label">Bloch Coordinates:</span> 116 <span id="bloch-result"></span> 117 </div> 118 <div class="output-item"> 119 <span class="label">Circuit Depth:</span> 120 <span id="depth-result"></span> 121 </div> 122 <div class="output-item"> 123 <span class="label">Total Gates:</span> 124 <span id="gates-result"></span> 125 </div> 126 </div> 127 </div> 128 `; 129 130 // Insert into the visualizer-circuit-section (below the Bloch sphere) 131 const visualizerSection = document.querySelector('.visualizer-circuit-section'); 132 133 // Insert the circuit composer after the visualization container 134 visualizerSection.appendChild(this.container); 135 136 // Initialize grid 137 this.grid.init(); 138 139 // Rebuild qubit selector tabs 140 this.rebuildQubitTabs(); 141 142 // Enable drag handler 143 this.dragHandler.enable(); 144 } 145 146 /** 147 * Rebuild the qubit segment tabs above the Bloch sphere to supp
147ort multi-qubit inspection 148 */ 149 rebuildQubitTabs() { 150 let tabsContainer = document.querySelector('.qubit-selector-tabs'); 151 if (!tabsContainer) { 152 const sectionLabel = document.querySelector('.visualizer-circuit-section .section-label'); 153 if (sectionLabel) { 154 tabsContainer = document.createElement('div'); 155 tabsContainer.className = 'qubit-selector-tabs'; 156 sectionLabel.parentNode.insertBefore(tabsContainer, sectionLabel.nextSibling); 157 } 158 } 159 160 if (tabsContainer) { 161 if (this.activeQubitIndex === undefined) { 162 this.activeQubitIndex = 0; 163 } 164 if (this.activeQubitIndex >= this.qubits) { 165 this.activeQubitIndex = 0; 166 } 167 168 let html = `<span class="selector-label">Inspect Qubit:</span>`; 169 for (let q = 0; q < this.qubits; q++) { 170 const isActive = q === this.activeQubitIndex; 171 html += `<button class="qubit-tab-btn ${isActive ? 'active' : ''}" data-qubit="${q}">Q${q}</button>`; 172 } 173 tabsContainer.innerHTML = html; 174 175 // Add click listeners 176 tabsContainer.querySelectorAll('.qubit-tab-btn').forEach(btn => { 177 btn.addEventListener('click', (e) => { 178 const selectedQubit = parseInt(e.target.getAttribute('data-qubit')); 179 this.activeQubitIndex = selectedQubit; 180 181 // Update active button styling 182 tabsContainer.querySelectorAll('.qubit-tab-btn').forEach(b => b.classList.remove('active')); 183 e.target.classList.add('active'); 184 185 // Sync with Control Panel target qubit selector (Q0/Q1) 186 if (window.app && window.app.controls && selectedQubit <= 1) { 187 window.app.controls.setTargetQubit(selectedQubit); 188 } 189 190 // Trigger a re-render/update of the Bloch sphere 191 if (this.lastExecutedState) { 192 const coords = this.lastExecutedState.qubitCoordinates[selectedQubit] || { x: 0, y: 0, z: 1 }; 193 if (selectedQubit === 0) { 194 this.blochSphere0.updateStateVector(coords); 195 } else if (selectedQubit === 1) { 196 this.blochSphere1.updateStateVector(coords); 197 } 198 199 // Also update the main text display 200 const coordsText = document.getElementById('bloch-coords'); 201 if (coordsText) { 202 coordsText.textContent = `(${coords.x.toFixed(2)}, ${coords.y.toFixed(2)}, ${coords.z.toFixed(2)})`; 203 } 204 } 205 }); 206 }); 207 } 208 } 209 210 /** 211 * Updates the visibility of the second Bloch sphere (Q1) and target buttons based on qubit count 212 * @param {number} qubitCount - Current number of qubits in the circuit 213 */ 214 updateQubitVisibility(qubitCount) { 215 const q1Wrapper = document.getElementById('sphere-q1-wrapper'); 216 const q1TargetBtn = document.querySelector('.target-qubit-btn[data-qubit="1"]'); 217 218 if (qubitCount >= 2) { 219 if (q1Wrapper) q1Wrapper.classList.remove('hidden'); 220 if (q1TargetBtn) q1TargetBtn.classList.remove('hidden'); 221 } else { 222 if (q1Wrapper) q1Wrapper.classList.add('hidden'); 223 if (q1TargetBtn) q1TargetBtn.classList.add('hidden'); 224 225 // If the selected target qubit was Q1, switch it back to Q0 226 if (window.app && window.app.controls && window.app.controls.targetQubit === 1) { 227 window.app.controls.setTargetQubit(0); 228 } 229 } 230 231 // forceResize() clears and re-sizes the WebGL canvas â only trigger it when 232 // Q1 visibility actually toggles (1â2 qubits), not on every gate add/remove. 233 const q1NowVisible = (qubitCount >= 2); 234 if (q1NowVisible !== this._lastVisibleQ1) { 235 this._lastVisibleQ1 = q1NowVisible; 236 if (this.blochSphere0) this.blochSphere0.forceResize(); 237 if (this.blochSphere1) this.blochSphere1.forceResize(); 238 } 239 } 240 241 initEventListeners() { 242 // Circuit controls 243 document.getElementById('add-qubit').addEventListener('click', () => this.addQubit()); 244 document.getElementById('remove-qubit').addEventListener('click', () => this.removeQubit()); 245 document.getElementById('clear-circuit').addEventListener('click', () => this.clearCircuit()); 246 document.getElementById('export-circuit').addEventListener('click', () => this.exportCircuit()); 247 document.getElementById('reflect-circuit').addEventListener('click', () => this.startReflection()); 248 document.getElementById('execute-circuit').addEventListener('click', () => { 249 // Explicitly clearing measurement cache forces a fresh Born-rule measurement collapse 250 this.measurementCache.clear(); 251 this.executeCircuit(); 252 }); 253 254 // Collapse toggle checkbox 255 const toggleCollapse = document.getElementById('toggle-collapse-mode'); 256 if (toggleCollapse) { 257 toggleCollapse.addEventListener('change', (e) => { 258 this.isMeasurementCollapseMode = e.target.checked; 259 this.measurementCache.clear(); 260 this.executeCircuit(); 261 }); 262 } 263 264 // Gate toolbar 265 document.querySelectorAll('.gate-tool-btn').forEach(btn => { 266 btn.addEventListener('click', (e) => { 267 const gateType = e.target.getAttribute('data-gate'); 268 this.selectGate(gateType); 269 }); 270 }); 271 272 // Reflection controls 273 document.getElementById('prev-step').addEventListener('click', () => this.prevStep()); 274 document.getElementById('next-step').addEventListener('click', () => this.nextStep()); 275 document.getElementById('reset-reflection').addEventListener('click', () => this.resetReflection()); 276 document.getElementById('stop-reflection').addEventListener('click', () => this.stopReflection()); 277 278 // Listen for animation events to disable/enable reflection buttons 279 const onStart = () => { 280 this.setReflectionButtonsDisabled(true); 281 }; 282 const onComplete = () => { 283 if (!this.blochSphere0.isAnimating && !this.blochSphere1.isAnimating) { 284 this.setReflectionButtonsDisabled(false); 285 } 286 }; 287 this.blochSphere0.container.addEventListener('bloch-animation-start', onStart); 288 this.blochSphere0.container.addEventListener('bloch-animation-complete', onComplete); 289 this.blochSphere1.container.addEventListener('bloch-animation-start', onStart); 290 this.blochSphere1.container.addEventListener('bloch-animation-complete', onComplete); 291 } 292 293 /** 294 * Add a qubit to the circuit 295 */ 296 addQubit() { 297 if (this.qubits < 5) { 298 this.qubits++; 299 this.grid.updateGrid(); 300 this.rebuildQubitTabs(); 301 this.executeCircuit(); 302 } 303 } 304 305 /** 306 * Remove a qubit from the circuit 307 */ 308 removeQubit() { 309 if (this.qubits > 1) { 310 this.qubits--; 311 this.circuit = this.circuit.filter(gate => { 312 if (gate.qubit >= this.qubits) return false;
313 if (gate.type === 'CNOT' || gate.type === 'CZ') { 314 const control = gate.control !== undefined ? gate.control : gate.qubit; 315 const target = gate.target !== undefined ? gate.target : (gate.qubit + 1) % (this.qubits + 1); 316 if (control >= this.qubits || target >= this.qubits) return false; 317 } else if (gate.type === 'SWAP') { 318 const qubit1 = gate.qubit1 !== undefined ? gate.qubit1 : gate.qubit; 319 const qubit2 = gate.qubit2 !== undefined ? gate.qubit2 : (gate.qubit + 1) % (this.qubits + 1); 320 if (qubit1 >= this.qubits || qubit2 >= this.qubits) return false; 321 } else if (gate.type === 'TOFFOLI') { 322 const control1 = gate.control1 !== undefined ? gate.control1 : gate.qubit; 323 const control2 = gate.control2 !== undefined ? gate.control2 : (gate.qubit + 1) % (this.qubits + 1); 324 const target = gate.target !== undefined ? gate.target : (gate.qubit + 2) % (this.qubits + 1); 325 if (control1 >= this.qubits || control2 >= this.qubits || target >= this.qubits) return false; 326 } 327 return true; 328 }); 329 this.grid.updateGrid(); 330 if (this.activeQubitIndex >= this.qubits) { 331 this.activeQubitIndex = this.qubits - 1; 332 } 333 this.rebuildQubitTabs(); 334 this.executeCircuit(); 335 } 336 } 337 338 /** 339 * Clear the entire circuit 340 */ 341 clearCircuit() { 342 this.circuit = []; 343 this.grid.clearGrid(); 344 this.stopReflection(); 345 346 // Hide execution output 347 document.querySelector('.execution-output').style.display = 'none'; 348 this.executeCircuit(); 349 } 350 351 /** 352 * Export circuit to JSON 353 */ 354 exportCircuit() { 355 const json = this.exporter.exportToJSON(); 356 const blob = new Blob([JSON.stringify(json, null, 2)], { type: 'application/json' }); 357 const url = URL.createObjectURL(blob); 358 const a = document.createElement('a'); 359 a.href = url; 360 a.download = 'quantum-circuit.json'; 361 a.click(); 362 URL.revokeObjectURL(url); 363 } 364 365 /** 366 * Select a gate type for placement 367 */ 368 selectGate(gateType) { 369 this.selectedGate = gateType; 370 document.querySelectorAll('.gate-tool-btn').forEach(btn => { 371 btn.classList.remove('selected'); 372 }); 373 document.querySelector(`[data-gate="${gateType}"]`).classList.add('selected'); 374 } 375 376 /** 377 * Add a gate to the circuit 378 */ 379 addGate(gateType, qubit, time, params = {}) { 380 const gate = { 381 type: gateType, 382 qubit: qubit, 383 time: time, 384 params: params 385 }; 386 387 // Handle multi-qubit gates 388 if (gateType === 'CNOT' || gateType === 'CZ') { 389 gate.control = params.control !== undefined ? params.control : qubit; 390 gate.target = params.target !== undefined ? params.target : (qubit + 1) % this.qubits; 391 } else if (gateType === 'SWAP') { 392 gate.qubit1 = params.qubit1 !== undefined ? params.qubit1 : qubit; 393 gate.qubit2 = params.qubit2 !== undefined ? params.qubit2 : (qubit + 1) % this.qubits; 394 } else if (gateType === 'TOFFOLI') { 395 gate.control1 = params.control1 !== undefined ? params.control1 : qubit; 396 gate.control2 = params.control2 !== undefined ? params.control2 : (qubit + 1) % this.qubits; 397 gate.target = params.target !== undefined ? params.target : (qubit + 2) % this.qubits; 398 } 399 400 this.circuit.push(gate); 401 this.grid.addGateVisual(gate); 402 this.executeCircuit(); 403 } 404 405 /** 406 * Remove a gate from the circuit 407 */ 408 removeGate(qubit, time) { 409 this.circuit = this.circuit.filter(gate => 410 !(gate.qubit === qubit && gate.time === time) 411 ); 412 this.grid.removeGateVisual(qubit, time); 413 this.executeCircuit(); 414 } 415 416 /** 417 * Start circuit reflection 418 */ 419 startReflection() { 420 if (this.circuit.length === 0) { 421 alert('No circuit to reflect. Add some gates first!'); 422 return; 423 } 424 425 this.isReflecting = true; 426 this.reflectionStep = 0; 427 this.reflector.startReflection(); 428 this.showReflectionControls(); 429 } 430 431 /** 432 * Show reflection controls 433 */ 434 showReflectionControls() { 435 document.querySelector('.reflection-controls').style.display = 'flex'; 436 this.updateReflectionInfo(); 437 } 438 439 /** 440 * Hide reflection controls 441 */ 442 hideReflectionControls() { 443 document.querySelector('.reflection-controls').style.display = 'none'; 444 } 445 446 /** 447 * Update reflection info display 448 */ 449 updateReflectionInfo() { 450 const stepEl = document.getElementById('reflection-step'); 451 const gateEl = document.getElementById('reflection-gate'); 452 453 stepEl.textContent = `Step ${this.reflectionStep}/${this.circuit.length}`; 454 455 if (this.reflectionStep < this.circuit.length) { 456 const gate = this.circuit[this.reflectionStep]; 457 gateEl.textContent = `${gate.type} on Q${gate.qubit}`; 458 } else { 459 gateEl.textContent = 'Complete'; 460 } 461 } 462 463 /** 464 * Go to previous step 465 */ 466 prevStep() { 467 if (this.blochSphere0.isAnimating || this.blochSphere1.isAnimating) return; 468 if (this.reflectionStep > 0) { 469 this.reflectionStep--; 470 this.reflector.goToStep(this.reflectionStep); 471 this.updateReflectionInfo(); 472 } 473 } 474 475 /**
476 * Go to next step 477 */ 478 nextStep() { 479 if (this.blochSphere0.isAnimating || this.blochSphere1.isAnimating) return; 480 if (this.reflectionStep < this.circuit.length) { 481 this.reflector.applyStep(this.reflectionStep); 482 this.reflectionStep++; 483 this.updateReflectionInfo(); 484 } 485 } 486 487 /** 488 * Reset reflection 489 */ 490 resetReflection() { 491 if (this.blochSphere0.isAnimating || this.blochSphere1.isAnimating) return; 492 this.reflectionStep = 0; 493 this.reflector.reset(); 494 this.updateReflectionInfo(); 495 } 496 497 /** 498 * Stop reflection 499 */ 500 stopReflection() { 501 this.isReflecting = false; 502 this.reflector.stop(); 503 this.hideReflectionControls(); 504 } 505 506 /** 507 * Disable or enable reflection controls during active animations 508 * @param {boolean} disabled - Whether to disable controls 509 */ 510 setReflectionButtonsDisabled(disabled) { 511 const prev = document.getElementById('prev-step'); 512 const next = document.getElementById('next-step'); 513 const reset = document.getElementById('reset-reflection'); 514 if (prev) prev.disabled = disabled; 515 if (next) next.disabled = disabled; 516 if (reset) reset.disabled = disabled; 517 } 518 519 /** 520 * Execute circuit and show final state (auto-runs on every change) 521 */ 522 executeCircuit() { 523 const qubitCount = this.getQubitCount(); 524 this.updateQubitVisibility(qubitCount); 525 const state = new QuantumState(qubitCount); 526 527 // Apply all gates in sequence sorted by time step 528 const sortedGates = [...this.circuit].sort((a, b) => a.time - b.time); 529 530 sortedGates.forEach(gate => { 531 this.applyGateToState(gate, state); 532 }); 533 534 // Update Bloch coordinates for all qubits 535 state.updateBlochCoordinates(); 536 537 // Store state for inspection changes 538 this.lastExecutedState = state; 539 540 // Display results 541 this.showExecutionOutput(state); 542 543 // Update neon probability readouts 544 this.grid.updateMeasurementReadouts(state); 545 546 // Update both Bloch spheres and explicitly request a render so the new 547 // positions are painted even if isAnimating/needsRender were already false. 548 const coords0 = state.qubitCoordinates[0] || { x: 0, y: 0, z: 1 }; 549 const coords1 = state.qubitCoordinates[1] || { x: 0, y: 0, z: 1 }; 550 this.blochSphere0.updateStateVector(coords0); 551 this.blochSphere1.updateStateVector(coords1); 552 this.blochSphere0.requestRender(); 553 this.blochSphere1.requestRender(); 554 555 const activeQ = this.activeQubitIndex || 0; 556 const coords = state.qubitCoordinates[activeQ] || { x: 0, y: 0, z: 1 }; 557 558 // Sync control panel coords readout if visible 559 const coordsText = document.getElementById('bloch-coords'); 560 if (coordsText) { 561 coordsText.textContent = `(${coords.x.toFixed(2)}, ${coords.y.toFixed(2)}, ${coords.z.toFixed(2)})`; 562 } 563 564 // Sync main state ket representation text if visible 565 const currentKetText = document.getElementById('current-state'); 566 if (currentKetText) { 567 currentKetText.textContent = state.getKetString(); 568 } 569 } 570 571 /** 572 * Apply gate to a quantum state (for execution) 573 */ 574 applyGateToState(gate, state) { 575 const { type, qubit, params } = gate; 576 577 // Ensure primary qubit is within physical boundaries 578 if (qubit >= state.qubits) return; 579 580 // Define matrices for standard single-qubit gates 581 const H = [ 582 [math.divide(1, math.sqrt(2)), math.divide(1, math.sqrt(2))], 583 [math.divide(1, math.sqrt(2)), math.divide(-1, math.sqrt(2))] 584 ]; 585 const X = [[0, 1], [1, 0]]; 586 const Y = [[0, math.complex(0, -1)], [math.complex(0, 1), 0]]; 587 const Z = [[1, 0], [0, -1]]; 588 const S = [[1, 0], [0, math.complex(0, 1)]]; 589 const T = [[1, 0], [0, math.exp(math.complex(0, Math.PI / 4))]]; 590 591 switch (type) { 592 case 'H': 593 state.applySingleQubitGate(qubit, H); 594 break; 595 case 'X': 596 state.applySingleQubitGate(qubit, X); 597 break; 598 case 'Y': 599 state.applySingleQubitGate(qubit, Y); 600 break; 601 case 'Z': 602 state.applySingleQubitGate(qubit, Z); 603 break; 604 case 'S': 605 state.applySingleQubitGate(qubit, S); 606 break; 607 case 'T': 608 state.applySingleQubitGate(qubit, T); 609 break; 610 case 'RX': { 611 const rxAngle = (params.angle * Math.PI) / 180; 612 const rx = [ 613 [Math.cos(rxAngle / 2), math.complex(0, -Math.sin(rxA
613ngle / 2))], 614 [math.complex(0, -Math.sin(rxAngle / 2)), Math.cos(rxAngle / 2)] 615 ]; 616 state.applySingleQubitGate(qubit, rx); 617 break; 618 } 619 case 'RY': { 620 const ryAngle = (params.angle * Math.PI) / 180; 621 const ry = [ 622 [Math.cos(ryAngle / 2), -Math.sin(ryAngle / 2)], 623 [Math.sin(ryAngle / 2), Math.cos(ryAngle / 2)] 624 ]; 625 state.applySingleQubitGate(qubit, ry); 626 break; 627 } 628 case 'RZ': { 629 const rzAngle = (params.angle * Math.PI) / 180; 630 const rz = [ 631 [math.exp(math.complex(0, -rzAngle / 2)), 0], 632 [0, math.exp(math.complex(0, rzAngle / 2))] 633 ]; 634 state.applySingleQubitGate(qubit, rz); 635 break; 636 } 637 case 'CNOT': { 638 const control = gate.control !== undefined ? gate.control : qubit; 639 const target = gate.target !== undefined ? gate.target : (qubit + 1) % state.qubits; 640 if (control < 0 || control >= state.qubits || target < 0 || target >= state.qubits || control === target) return; 641 state.applyCNOT(control, target); 642 break; 643 } 644 case 'CZ': { 645 const control = gate.control !== undefined ? gate.control : qubit; 646 const target = gate.target !== undefined ? gate.target : (qubit + 1) % state.qubits; 647 if (control < 0 || control >= state.qubits || target < 0 || target >= state.qubits || control === target) return; 648 state.applyCZ(control, target); 649 break; 650 } 651 case 'SWAP': { 652 const qubit1 = gate.qubit1 !== undefined ? gate.qubit1 : qubit; 653 const qubit2 = gate.qubit2 !== undefined ? gate.qubit2 : (qubit + 1) % state.qubits; 654 if (qubit1 < 0 || qubit1 >= state.qubits || qubit2 < 0 || qubit2 >= state.qubits || qubit1 === qubit2) return; 655 state.applySWAP(qubit1, qubit2); 656 break; 657 } 658 case 'TOFFOLI': { 659 const control1 = gate.control1 !== undefined ? gate.control1 : qubit; 660 const control2 = gate.control2 !== undefined ? gate.control2 : (qubit + 1) % state.qubits; 661 const target = gate.target !== undefined ? gate.target : (qubit + 2) % state.qubits; 662 if (control1 < 0 || control1 >= state.qubits || control2 < 0 || control2 >= state.qubits || target < 0 || target >= state.qubits) return; 663 if (control1 === control2 || control1 === target || control2 === target) return; 664 state.applyToffoli(control1, control2, target); 665 break; 666 } 667 case 'M': { 668 if (this.isMeasurementCollapseMode) { 669 const cacheKey = `${qubit}-${gate.time}`; 670 let forcedOutcome = null; 671 if (this.measurementCache.has(cacheKey)) { 672 forcedOutcome = this.measurementCache.get(cacheKey); 673 } 674 const outcome = state.collapseQubit(qubit, forcedOutcome); 675 this.measurementCache.set(cacheKey, outcome); 676 } 677 break; 678 } 679 default: 680 console.warn(`Unknown gate type: ${type}`); 681 } 682 } 683 684 /** 685 * Show execution output with beautiful ket notation 686 */ 687 showExecutionOutput(quantumState) { 688 const outputDiv = document.querySelector('.execution-output'); 689 outputDiv.style.display = 'block'; 690 691 // Format ket notation 692 const ketString = this.formatKetNotation(quantumState); 693 document.getElementById('ket-result').innerHTML = ketString; 694 695 // Trigger MathJax rendering if available 696 if (window.MathJax) { 697 window.MathJax.typesetPromise([document.getElementById('ket-result')]).c
697atch((err) => { 698 console.log('MathJax error:', err); 699 }); 700 } 701 702 // Format Bloch coordinates 703 const activeQ = this.activeQubitIndex || 0; 704 const coords = quantumState.qubitCoordinates[activeQ] || { x: 0, y: 0, z: 1 }; 705 document.getElementById('bloch-result').textContent = 706 `Q${activeQ}: (${coords.x.toFixed(3)}, ${coords.y.toFixed(3)}, ${coords.z.toFixed(3)})`; 707 708 // Circuit statistics 709 const stats = this.exporter.getCircuitStats(); 710 document.getElementById('depth-result').textContent = stats.maxTime + 1; 711 document.getElementById('gates-result').textContent = stats.totalGates; 712 } 713 714 /** 715 * Format ket notation with MathJax-like styling 716 */ 717 formatKetNotation(quantumState) { 718 const size = Math.pow(2, quantumState.qubits); 719 720 const formatComplex = (c) => { 721 const re = math.round(math.re(c), 3); 722 const im = math.round(math.im(c), 3); 723 724 if (im === 0) { 725 return re === 0 ? '0' : re.toString(); 726 } else if (re === 0) { 727 return im === 1 ? 'i' : im === -1 ? '-i' : `${im}i`; 728 } else { 729 const imStr = im === 1 ? '+i' : im === -1 ? '-i' : im > 0 ? `+${im}i` : `${im}i`; 730 return `${re}${imStr}`; 731 } 732 }; 733 734 if (window.MathJax) { 735 let mathString = ''; 736 for (let i = 0; i < size; i++) { 737 const coef = quantumState.stateVector[i]; 738 if (math.abs(coef) > 0.001) { 739 if (mathString !== '') mathString += ' + '; 740 const coefStr = formatComplex(coef); 741 const binaryStr = i.toString(2).padStart(quantumState.qubits, '0'); 742 if (coefStr === '1') { 743 mathString += `|${binaryStr}\\rangle`; 744 } else if (coefStr === '-1') { 745 mathString += `-|${binaryStr}\\rangle`; 746 } else { 747 const needsParens = coefStr.includes('+') || (coefStr.includes('-') && coefStr.indexOf('-') !== 0) || coefStr.includes('i'); 748 if (needsParens) { 749 mathString += `(${coefStr})|${binaryStr}\\rangle`; 750 } else { 751 mathString += `${coefStr}|${binaryStr}\\rangle`; 752 } 753 } 754 } 755 } 756 if (mathString === '') mathString = '0'; 757 return `\\(${mathString}\\)`; 758 } else { 759 let ketString = ''; 760 for (let i = 0; i < size; i++) { 761 const coef = quantumState.stateVector[i]; 762 if (math.abs(coef) > 0.001) { 763 if (ketString !== '') ketString += ' + '; 764 const coefStr = formatComplex(coef); 765 const binaryStr = i.toString(2).padStart(quantumState.qubits, '0'); 766 if (coefStr === '1') { 767 ketString += `<span class="ket">|${binaryStr}â©</span>`; 768 } else if (coefStr === '-1') { 769 ketString += `<span class="coefficient">-</span><span class="ket">|${binaryStr}â©</span>`; 770 } else { 771 const needsParens = coefStr.includes('+') || (coefStr.includes('-') && coefStr.indexOf('-') !== 0) || coefStr.includes('i'); 772 if (needsParens) { 773 ketString += `<span class="coefficient">(${coefStr})</span><span class="ket">|${binaryStr}â©</span>`; 774 } else { 775 ketString += `<span class="coefficient">${coefStr}</span><span class="ket">|${binaryStr}â©</span>`; 776 } 777 } 778 } 779 } 780 if (ketString === '') ketString = '<span class="coefficient">0</span>'; 781 return ketString; 782 } 783 } 784 785 /** 786 * Get circuit data 787 */ 788 getCircuit() { 789 return this.circuit; 790 } 791 792 /** 793 * Get number of qubits 794 */ 795 getQubitCount() { 796 return this.qubits; 797 } 798 799 /** 800 * Get max time steps 801 */ 802 getMaxTimeSteps() { 803 return this.maxTimeSteps; 804 } 805}
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.