PageSourceSearch

https://bloqqer.onrender.com/js/composer/circuitComposer.js

js bloqqer.onrender.com collected 2026-10-03 11:39:26 UTC 33,768 bytes, 805 lines download raw bytes

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.