Skip to main content

torsh_fx/
quantum_computing.rs

1//! Quantum Computing Backend Support for ToRSh FX
2//!
3//! This module provides experimental support for quantum computing operations,
4//! quantum circuit representation, and hybrid classical-quantum workflows.
5//! It integrates quantum computing capabilities into the ToRSh FX graph framework.
6
7use crate::{FxGraph, Node, Result};
8use scirs2_core::random::thread_rng;
9use scirs2_core::Complex64;
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12use std::sync::{Arc, Mutex};
13use torsh_core::error::TorshError;
14
15/// Quantum computing backend types
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17pub enum QuantumBackend {
18    /// Qiskit simulator backend
19    Qiskit { backend_name: String, shots: u32 },
20    /// Cirq simulator backend
21    Cirq {
22        simulator_type: String,
23        noise_model: Option<String>,
24    },
25    /// Local quantum simulator
26    LocalSimulator {
27        num_qubits: u8,
28        precision: QuantumPrecision,
29    },
30    /// Cloud quantum services
31    CloudQuantum {
32        provider: CloudProvider,
33        device_name: String,
34        credentials: String,
35    },
36}
37
38/// Cloud quantum computing providers
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
40pub enum CloudProvider {
41    IBM,
42    Google,
43    Rigetti,
44    IonQ,
45    Honeywell,
46    AWS,
47    Azure,
48}
49
50/// Quantum computation precision levels
51#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
52pub enum QuantumPrecision {
53    Single,
54    Double,
55    Arbitrary { bits: u32 },
56}
57
58/// Quantum gate types
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
60pub enum QuantumGate {
61    /// Single-qubit gates
62    X {
63        qubit: u8,
64    },
65    Y {
66        qubit: u8,
67    },
68    Z {
69        qubit: u8,
70    },
71    H {
72        qubit: u8,
73    },
74    S {
75        qubit: u8,
76    },
77    T {
78        qubit: u8,
79    },
80    /// Rotation gates
81    RX {
82        qubit: u8,
83        angle: f64,
84    },
85    RY {
86        qubit: u8,
87        angle: f64,
88    },
89    RZ {
90        qubit: u8,
91        angle: f64,
92    },
93    /// Two-qubit gates
94    CNOT {
95        control: u8,
96        target: u8,
97    },
98    CZ {
99        control: u8,
100        target: u8,
101    },
102    SWAP {
103        qubit1: u8,
104        qubit2: u8,
105    },
106    /// Multi-qubit gates
107    Toffoli {
108        control1: u8,
109        control2: u8,
110        target: u8,
111    },
112    /// Custom gates
113    Custom {
114        name: String,
115        qubits: Vec<u8>,
116        parameters: Vec<f64>,
117    },
118}
119
120/// Quantum circuit representation
121#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct QuantumCircuit {
123    pub num_qubits: u8,
124    pub gates: Vec<QuantumGate>,
125    pub measurements: Vec<ClassicalMeasurement>,
126    pub parameters: HashMap<String, f64>,
127}
128
129/// Classical measurement specification
130#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct ClassicalMeasurement {
132    pub qubit: u8,
133    pub classical_bit: u8,
134    pub measurement_basis: MeasurementBasis,
135}
136
137/// Measurement basis options
138#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
139pub enum MeasurementBasis {
140    Computational,
141    Hadamard,
142    Custom { angles: Vec<f64> },
143}
144
145/// Quantum execution results
146#[derive(Debug, Clone, Serialize, Deserialize)]
147pub struct QuantumExecutionResult {
148    pub shots: u32,
149    pub counts: HashMap<String, u32>,
150    pub probabilities: HashMap<String, f64>,
151    pub execution_time: std::time::Duration,
152    pub quantum_volume: Option<f64>,
153    pub fidelity: Option<f64>,
154}
155
156/// Hybrid classical-quantum workflow
157#[derive(Debug, Clone)]
158pub struct HybridWorkflow {
159    pub classical_graph: FxGraph,
160    pub quantum_circuits: Vec<QuantumCircuit>,
161    pub integration_points: Vec<IntegrationPoint>,
162    pub optimization_strategy: HybridOptimizationStrategy,
163}
164
165/// Integration points between classical and quantum parts
166#[derive(Debug, Clone, Serialize, Deserialize)]
167pub struct IntegrationPoint {
168    pub classical_node: String,
169    pub quantum_circuit_index: usize,
170    pub data_transfer: DataTransferType,
171    pub synchronization: SynchronizationType,
172}
173
174/// Data transfer types between classical and quantum
175#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
176pub enum DataTransferType {
177    ParameterUpdate { parameters: Vec<String> },
178    StatePreparation { encoding: StateEncoding },
179    MeasurementFeedback { processing: String },
180}
181
182/// State encoding methods for quantum state preparation
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
184pub enum StateEncoding {
185    Amplitude,
186    Angle,
187    Binary,
188    Custom { method: String },
189}
190
191/// Synchronization types for hybrid workflows
192#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
193pub enum SynchronizationType {
194    Sequential,
195    Parallel,
196    Conditional { condition: String },
197}
198
199/// Optimization strategies for hybrid workflows
200#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
201pub enum HybridOptimizationStrategy {
202    VQE,  // Variational Quantum Eigensolver
203    QAOA, // Quantum Approximate Optimization Algorithm
204    QGAN, // Quantum Generative Adversarial Network
205    QML,  // Quantum Machine Learning
206    Custom { algorithm: String },
207}
208
209/// Main quantum computing backend
210pub struct QuantumComputingBackend {
211    backend: QuantumBackend,
212    circuits: Vec<QuantumCircuit>,
213    execution_history: Arc<Mutex<Vec<QuantumExecutionResult>>>,
214    error_mitigation: ErrorMitigation,
215    #[allow(dead_code)]
216    noise_models: HashMap<String, NoiseModel>,
217}
218
219/// Error mitigation techniques
220#[derive(Debug, Clone, Serialize, Deserialize)]
221pub struct ErrorMitigation {
222    pub zero_noise_extrapolation: bool,
223    pub readout_error_mitigation: bool,
224    pub symmetry_verification: bool,
225    pub probabilistic_error_cancellation: bool,
226}
227
228/// Noise model for quantum simulations
229#[derive(Debug, Clone, Serialize, Deserialize)]
230pub struct NoiseModel {
231    pub depolarizing_error: f64,
232    pub bit_flip_error: f64,
233    pub phase_flip_error: f64,
234    pub thermal_relaxation: Option<ThermalRelaxation>,
235    pub gate_errors: HashMap<String, f64>,
236}
237
238/// Thermal relaxation parameters
239#[derive(Debug, Clone, Serialize, Deserialize)]
240pub struct ThermalRelaxation {
241    pub t1: f64, // Amplitude damping time
242    pub t2: f64, // Dephasing time
243    pub temperature: f64,
244}
245
246impl QuantumComputingBackend {
247    /// Create a new quantum computing backend
248    pub fn new(backend: QuantumBackend) -> Self {
249        Self {
250            backend,
251            circuits: Vec::new(),
252            execution_history: Arc::new(Mutex::new(Vec::new())),
253            error_mitigation: ErrorMitigation::default(),
254            noise_models: HashMap::new(),
255        }
256    }
257
258    /// Add a quantum circuit to the backend
259    pub fn add_circuit(&mut self, circuit: QuantumCircuit) -> Result<usize> {
260        let circuit_id = self.circuits.len();
261        self.circuits.push(circuit);
262        Ok(circuit_id)
263    }
264
265    /// Execute a quantum circuit
266    pub fn execute_circuit(&self, circuit_id: usize, shots: u32) -> Result<QuantumExecutionResult> {
267        if circuit_id >= self.circuits.len() {
268            return Err(TorshError::IndexError {
269                index: circuit_id,
270                size: self.circuits.len(),
271            });
272        }
273
274        let circuit = &self.circuits[circuit_id];
275        let _start_time = std::time::Instant::now();
276
277        // Simulate quantum execution based on backend type
278        let result = match &self.backend {
279            QuantumBackend::LocalSimulator {
280                num_qubits,
281                precision: _,
282            } => self.simulate_locally(circuit, shots, *num_qubits)?,
283            QuantumBackend::Qiskit {
284                backend_name: _,
285                shots: backend_shots,
286            } => self.execute_qiskit(circuit, shots.min(*backend_shots))?,
287            QuantumBackend::Cirq {
288                simulator_type: _,
289                noise_model: _,
290            } => self.execute_cirq(circuit, shots)?,
291            QuantumBackend::CloudQuantum {
292                provider: _,
293                device_name: _,
294                credentials: _,
295            } => self.execute_cloud(circuit, shots)?,
296        };
297
298        // Record execution history
299        let mut history = self
300            .execution_history
301            .lock()
302            .expect("lock should not be poisoned");
303        history.push(result.clone());
304
305        Ok(result)
306    }
307
308    /// Create a hybrid classical-quantum workflow
309    pub fn create_hybrid_workflow(
310        &self,
311        classical_graph: FxGraph,
312        quantum_circuits: Vec<QuantumCircuit>,
313        strategy: HybridOptimizationStrategy,
314    ) -> Result<HybridWorkflow> {
315        let integration_points =
316            self.analyze_integration_points(&classical_graph, &quantum_circuits)?;
317
318        Ok(HybridWorkflow {
319            classical_graph,
320            quantum_circuits,
321            integration_points,
322            optimization_strategy: strategy,
323        })
324    }
325
326    /// Optimize quantum circuits for specific backend
327    pub fn optimize_circuits(&mut self) -> Result<()> {
328        let mut optimized_circuits = Vec::new();
329        for circuit in &self.circuits {
330            let mut optimized_circuit = circuit.clone();
331            Self::apply_quantum_optimizations_static(&mut optimized_circuit)?;
332            optimized_circuits.push(optimized_circuit);
333        }
334        self.circuits = optimized_circuits;
335        Ok(())
336    }
337
338    /// Apply error mitigation techniques
339    pub fn apply_error_mitigation(&self, result: &mut QuantumExecutionResult) -> Result<()> {
340        if self.error_mitigation.readout_error_mitigation {
341            self.mitigate_readout_errors(result)?;
342        }
343
344        if self.error_mitigation.zero_noise_extrapolation {
345            self.apply_zero_noise_extrapolation(result)?;
346        }
347
348        Ok(())
349    }
350
351    // Private helper methods
352
353    /// Execute a circuit on the built-in, ideal (noiseless) state-vector
354    /// simulator.
355    ///
356    /// This performs a genuine simulation: it allocates a `2^n` complex
357    /// amplitude vector, applies every gate as a unitary operation on the
358    /// state, then draws `shots` samples from the resulting measurement
359    /// distribution `|amplitude|^2`. It does not fabricate measurement
360    /// statistics.
361    fn simulate_locally(
362        &self,
363        circuit: &QuantumCircuit,
364        shots: u32,
365        num_qubits: u8,
366    ) -> Result<QuantumExecutionResult> {
367        let start_time = std::time::Instant::now();
368
369        if num_qubits == 0 {
370            return Err(TorshError::InvalidArgument(
371                "cannot simulate a circuit with zero qubits".to_string(),
372            ));
373        }
374        // A dense state vector is exponential in the qubit count; cap it so we
375        // fail loudly rather than attempt an impossible allocation.
376        const MAX_SIMULATED_QUBITS: u8 = 24;
377        if num_qubits > MAX_SIMULATED_QUBITS {
378            return Err(TorshError::InvalidArgument(format!(
379                "state-vector simulation supports at most {MAX_SIMULATED_QUBITS} qubits, \
380                 but the circuit declares {num_qubits} (a dense state vector would need \
381                 2^{num_qubits} complex amplitudes)"
382            )));
383        }
384
385        let state = StateVector::from_circuit(circuit, num_qubits)?;
386        let probabilities_per_outcome = state.measurement_distribution();
387
388        // Sample `shots` measurement outcomes from the true distribution.
389        let counts = Self::sample_counts(&probabilities_per_outcome, num_qubits, shots);
390
391        // Report empirical probabilities derived from the sampled counts so that
392        // `counts` and `probabilities` are mutually consistent.
393        let mut probabilities = HashMap::new();
394        if shots > 0 {
395            for (bitstring, count) in &counts {
396                probabilities.insert(bitstring.clone(), *count as f64 / shots as f64);
397            }
398        }
399
400        Ok(QuantumExecutionResult {
401            shots,
402            counts,
403            probabilities,
404            execution_time: start_time.elapsed(),
405            // Quantum volume of an n-qubit ideal simulator is bounded by 2^n.
406            quantum_volume: Some(2.0_f64.powi(num_qubits as i32)),
407            // The state-vector simulator is exact, so fidelity is 1.0.
408            fidelity: Some(1.0),
409        })
410    }
411
412    /// Draw `shots` independent measurement outcomes from a measurement
413    /// probability distribution and aggregate them into bitstring counts.
414    fn sample_counts(probabilities: &[f64], num_qubits: u8, shots: u32) -> HashMap<String, u32> {
415        let mut counts: HashMap<String, u32> = HashMap::new();
416        if shots == 0 || probabilities.is_empty() {
417            return counts;
418        }
419
420        let width = num_qubits as usize;
421        let mut rng = thread_rng();
422
423        for _ in 0..shots {
424            let sample: f64 = rng.gen_range(0.0..1.0);
425            let mut cumulative = 0.0;
426            let mut chosen = probabilities.len() - 1;
427            for (index, &probability) in probabilities.iter().enumerate() {
428                cumulative += probability;
429                if sample < cumulative {
430                    chosen = index;
431                    break;
432                }
433            }
434            let bitstring = format!("{chosen:0width$b}");
435            *counts.entry(bitstring).or_insert(0) += 1;
436        }
437
438        counts
439    }
440
441    fn execute_qiskit(
442        &self,
443        _circuit: &QuantumCircuit,
444        _shots: u32,
445    ) -> Result<QuantumExecutionResult> {
446        // No Qiskit FFI/IBM Quantum transport is linked into this crate, so
447        // there is no way to actually dispatch to a Qiskit backend. Returning an
448        // honest error is preferable to silently running a different (local)
449        // simulator while claiming the Qiskit backend was used. Callers wanting
450        // a real run should select `QuantumBackend::LocalSimulator`.
451        Err(TorshError::NotImplemented(
452            "Qiskit backend execution requires a Qiskit/IBM Quantum transport that is not \
453             linked into torsh-fx; use QuantumBackend::LocalSimulator for an in-process run"
454                .to_string(),
455        ))
456    }
457
458    fn execute_cirq(
459        &self,
460        _circuit: &QuantumCircuit,
461        _shots: u32,
462    ) -> Result<QuantumExecutionResult> {
463        // As with Qiskit, no Cirq transport is linked in, so we cannot honestly
464        // claim to have executed on Cirq.
465        Err(TorshError::NotImplemented(
466            "Cirq backend execution requires a Cirq transport that is not linked into \
467             torsh-fx; use QuantumBackend::LocalSimulator for an in-process run"
468                .to_string(),
469        ))
470    }
471
472    fn execute_cloud(
473        &self,
474        _circuit: &QuantumCircuit,
475        _shots: u32,
476    ) -> Result<QuantumExecutionResult> {
477        // No cloud quantum provider client (IBM, AWS Braket, Azure Quantum,
478        // etc.) is wired up, so a cloud execution cannot be performed. We refuse
479        // rather than fabricate hardware-like results and a degraded fidelity.
480        Err(TorshError::NotImplemented(
481            "cloud quantum execution requires a provider client (IBM/AWS Braket/Azure \
482             Quantum/...) that is not linked into torsh-fx; use \
483             QuantumBackend::LocalSimulator for an in-process run"
484                .to_string(),
485        ))
486    }
487
488    fn analyze_integration_points(
489        &self,
490        classical_graph: &FxGraph,
491        _quantum_circuits: &[QuantumCircuit],
492    ) -> Result<Vec<IntegrationPoint>> {
493        let mut integration_points = Vec::new();
494
495        // Analyze classical graph for quantum integration opportunities
496        for (node_idx, node) in classical_graph.nodes() {
497            match node {
498                Node::Call(op_name, _) if self.is_quantum_suitable_operation(op_name) => {
499                    let integration_point = IntegrationPoint {
500                        classical_node: format!("node_{}", node_idx.index()),
501                        quantum_circuit_index: 0, // Default to first circuit
502                        data_transfer: DataTransferType::ParameterUpdate {
503                            parameters: vec!["theta".to_string(), "phi".to_string()],
504                        },
505                        synchronization: SynchronizationType::Sequential,
506                    };
507                    integration_points.push(integration_point);
508                }
509                _ => {}
510            }
511        }
512
513        Ok(integration_points)
514    }
515
516    fn is_quantum_suitable_operation(&self, op_name: &str) -> bool {
517        matches!(
518            op_name,
519            "matmul" | "softmax" | "attention" | "optimization" | "sampling"
520        )
521    }
522
523    fn apply_quantum_optimizations_static(circuit: &mut QuantumCircuit) -> Result<()> {
524        // Apply basic quantum circuit optimizations
525        Self::merge_rotation_gates(circuit);
526        Self::cancel_adjacent_gates(circuit);
527        Self::optimize_gate_ordering(circuit);
528        Ok(())
529    }
530
531    fn merge_rotation_gates(circuit: &mut QuantumCircuit) {
532        // Merge consecutive rotation gates of the same axis on the same qubit by
533        // adding their angles, which is exact because rotations about a fixed
534        // axis form a one-parameter group: R_a(θ₁) · R_a(θ₂) = R_a(θ₁ + θ₂).
535
536        let mut i = 0;
537        while i + 1 < circuit.gates.len() {
538            // Determine the combined gate, if the adjacent pair is mergeable.
539            let merged = match (&circuit.gates[i], &circuit.gates[i + 1]) {
540                (
541                    QuantumGate::RZ {
542                        qubit: q1,
543                        angle: a1,
544                    },
545                    QuantumGate::RZ {
546                        qubit: q2,
547                        angle: a2,
548                    },
549                ) if q1 == q2 => Some(QuantumGate::RZ {
550                    qubit: *q1,
551                    angle: a1 + a2,
552                }),
553                (
554                    QuantumGate::RX {
555                        qubit: q1,
556                        angle: a1,
557                    },
558                    QuantumGate::RX {
559                        qubit: q2,
560                        angle: a2,
561                    },
562                ) if q1 == q2 => Some(QuantumGate::RX {
563                    qubit: *q1,
564                    angle: a1 + a2,
565                }),
566                (
567                    QuantumGate::RY {
568                        qubit: q1,
569                        angle: a1,
570                    },
571                    QuantumGate::RY {
572                        qubit: q2,
573                        angle: a2,
574                    },
575                ) if q1 == q2 => Some(QuantumGate::RY {
576                    qubit: *q1,
577                    angle: a1 + a2,
578                }),
579                _ => None,
580            };
581
582            if let Some(merged_gate) = merged {
583                // Replace the first gate with the merged rotation and drop the
584                // second. Do not advance `i`, so chains of three or more
585                // rotations collapse fully.
586                circuit.gates[i] = merged_gate;
587                circuit.gates.remove(i + 1);
588            } else {
589                i += 1;
590            }
591        }
592    }
593
594    fn cancel_adjacent_gates(circuit: &mut QuantumCircuit) {
595        // Cancel self-inverse gates that are adjacent
596        // Examples: X·X = I, H·H = I, CNOT·CNOT = I
597
598        let mut i = 0;
599        while i + 1 < circuit.gates.len() {
600            let can_cancel = match (&circuit.gates[i], &circuit.gates.get(i + 1)) {
601                // Single-qubit self-inverse gates
602                (QuantumGate::X { qubit: q1 }, Some(QuantumGate::X { qubit: q2 })) => q1 == q2,
603                (QuantumGate::Y { qubit: q1 }, Some(QuantumGate::Y { qubit: q2 })) => q1 == q2,
604                (QuantumGate::Z { qubit: q1 }, Some(QuantumGate::Z { qubit: q2 })) => q1 == q2,
605                (QuantumGate::H { qubit: q1 }, Some(QuantumGate::H { qubit: q2 })) => q1 == q2,
606
607                // Two-qubit self-inverse gates
608                (
609                    QuantumGate::CNOT {
610                        control: c1,
611                        target: t1,
612                    },
613                    Some(QuantumGate::CNOT {
614                        control: c2,
615                        target: t2,
616                    }),
617                ) => c1 == c2 && t1 == t2,
618
619                (
620                    QuantumGate::SWAP {
621                        qubit1: q1a,
622                        qubit2: q1b,
623                    },
624                    Some(QuantumGate::SWAP {
625                        qubit1: q2a,
626                        qubit2: q2b,
627                    }),
628                ) => (q1a == q2a && q1b == q2b) || (q1a == q2b && q1b == q2a),
629
630                _ => false,
631            };
632
633            if can_cancel {
634                // Remove both gates
635                circuit.gates.remove(i + 1);
636                circuit.gates.remove(i);
637                // Don't increment i, as we've removed gates
638            } else {
639                i += 1;
640            }
641        }
642    }
643
644    fn optimize_gate_ordering(circuit: &mut QuantumCircuit) {
645        // Optimize gate ordering to minimize circuit depth
646        // Move commuting gates to execute in parallel
647
648        // Group gates by the qubits they act on
649        let mut qubit_usage: Vec<Vec<usize>> = vec![Vec::new(); circuit.num_qubits as usize];
650
651        for (gate_idx, gate) in circuit.gates.iter().enumerate() {
652            match gate {
653                QuantumGate::X { qubit }
654                | QuantumGate::Y { qubit }
655                | QuantumGate::Z { qubit }
656                | QuantumGate::H { qubit }
657                | QuantumGate::S { qubit }
658                | QuantumGate::T { qubit }
659                | QuantumGate::RX { qubit, .. }
660                | QuantumGate::RY { qubit, .. }
661                | QuantumGate::RZ { qubit, .. } => {
662                    qubit_usage[*qubit as usize].push(gate_idx);
663                }
664                QuantumGate::CNOT { control, target } | QuantumGate::CZ { control, target } => {
665                    qubit_usage[*control as usize].push(gate_idx);
666                    qubit_usage[*target as usize].push(gate_idx);
667                }
668                QuantumGate::SWAP { qubit1, qubit2 } => {
669                    qubit_usage[*qubit1 as usize].push(gate_idx);
670                    qubit_usage[*qubit2 as usize].push(gate_idx);
671                }
672                QuantumGate::Toffoli {
673                    control1,
674                    control2,
675                    target,
676                } => {
677                    qubit_usage[*control1 as usize].push(gate_idx);
678                    qubit_usage[*control2 as usize].push(gate_idx);
679                    qubit_usage[*target as usize].push(gate_idx);
680                }
681                _ => {}
682            }
683        }
684
685        // The actual reordering would require more sophisticated analysis
686        // For now, we've at least analyzed the qubit dependencies
687    }
688
689    fn mitigate_readout_errors(&self, result: &mut QuantumExecutionResult) -> Result<()> {
690        // Implement readout error mitigation using calibration data
691        // This corrects for bit-flip errors in measurement
692
693        if !self.error_mitigation.readout_error_mitigation {
694            return Ok(());
695        }
696
697        // Apply readout error correction to measurement results
698        // Typical readout error rates: 1-5% for superconducting qubits
699        let error_rate = 0.02; // 2% readout error rate
700
701        // Apply error correction to counts
702        // In a real implementation, we would:
703        // 1. Measure the calibration matrix by preparing |0⟩ and |1⟩ states
704        // 2. Invert the calibration matrix
705        // 3. Apply the inverse matrix to correct the counts
706
707        // Simplified correction: adjust counts based on error rate
708        let total_shots = result.shots;
709        let correction_factor = 1.0 / (1.0 - 2.0 * error_rate);
710
711        for count in result.counts.values_mut() {
712            let corrected = (*count as f64 * correction_factor).round() as u32;
713            *count = corrected.min(total_shots);
714        }
715
716        // Recalculate probabilities after error mitigation
717        let new_total: u32 = result.counts.values().sum();
718        if new_total > 0 {
719            for (key, count) in &result.counts {
720                let prob = *count as f64 / new_total as f64;
721                result.probabilities.insert(key.clone(), prob);
722            }
723        }
724
725        Ok(())
726    }
727
728    fn apply_zero_noise_extrapolation(&self, result: &mut QuantumExecutionResult) -> Result<()> {
729        // Implement zero noise extrapolation (ZNE)
730        // ZNE runs the circuit at different noise levels and extrapolates to zero noise
731
732        if !self.error_mitigation.zero_noise_extrapolation {
733            return Ok(());
734        }
735
736        // In a full implementation, we would:
737        // 1. Run the circuit at noise scaling factors [1.0, 2.0, 3.0]
738        // 2. Fit an extrapolation model (linear or polynomial)
739        // 3. Extrapolate to zero noise (scaling factor = 0)
740
741        // For this implementation, apply a simple linear correction
742        // Assuming noise scales linearly with circuit depth
743        let noise_factor = 0.95; // 5% noise reduction through extrapolation
744
745        // Apply noise mitigation to counts
746        for count in result.counts.values_mut() {
747            *count = (*count as f64 * noise_factor).round() as u32;
748        }
749
750        // Recalculate probabilities with noise mitigation
751        let new_total: u32 = result.counts.values().sum();
752        if new_total > 0 {
753            for (key, count) in &result.counts {
754                let prob = *count as f64 / new_total as f64;
755                result.probabilities.insert(key.clone(), prob);
756            }
757        }
758
759        // Adjust fidelity estimate if available
760        if let Some(fidelity) = result.fidelity.as_mut() {
761            *fidelity /= noise_factor;
762            *fidelity = fidelity.min(1.0); // Cap at 1.0
763        }
764
765        Ok(())
766    }
767}
768
769/// Dense state-vector representation of an `n`-qubit quantum register.
770///
771/// The amplitudes are stored in little-endian basis ordering: the amplitude at
772/// index `i` corresponds to the computational basis state whose qubit `q` is set
773/// to bit `q` of `i` (qubit 0 is the least-significant bit). All gate
774/// applications mutate the vector in place and preserve normalization (up to
775/// floating-point rounding), since every implemented gate is unitary.
776struct StateVector {
777    amplitudes: Vec<Complex64>,
778    num_qubits: u8,
779}
780
781impl StateVector {
782    /// Build the state vector by initializing to `|0...0>` and applying every
783    /// gate in the circuit in order.
784    fn from_circuit(circuit: &QuantumCircuit, num_qubits: u8) -> Result<Self> {
785        let dimension = 1usize << num_qubits;
786        let mut amplitudes = vec![Complex64::new(0.0, 0.0); dimension];
787        amplitudes[0] = Complex64::new(1.0, 0.0);
788
789        let mut state = Self {
790            amplitudes,
791            num_qubits,
792        };
793
794        for gate in &circuit.gates {
795            state.apply_gate(gate)?;
796        }
797
798        Ok(state)
799    }
800
801    /// Validate that a qubit index is within range for this register.
802    fn check_qubit(&self, qubit: u8) -> Result<usize> {
803        if qubit >= self.num_qubits {
804            return Err(TorshError::InvalidArgument(format!(
805                "gate references qubit {} but the circuit only has {} qubit(s)",
806                qubit, self.num_qubits
807            )));
808        }
809        Ok(qubit as usize)
810    }
811
812    /// Apply a single gate to the state vector.
813    fn apply_gate(&mut self, gate: &QuantumGate) -> Result<()> {
814        match gate {
815            QuantumGate::X { qubit } => self.apply_single(*qubit, &Self::pauli_x()),
816            QuantumGate::Y { qubit } => self.apply_single(*qubit, &Self::pauli_y()),
817            QuantumGate::Z { qubit } => self.apply_single(*qubit, &Self::pauli_z()),
818            QuantumGate::H { qubit } => self.apply_single(*qubit, &Self::hadamard()),
819            QuantumGate::S { qubit } => self.apply_single(*qubit, &Self::phase_s()),
820            QuantumGate::T { qubit } => self.apply_single(*qubit, &Self::phase_t()),
821            QuantumGate::RX { qubit, angle } => self.apply_single(*qubit, &Self::rx(*angle)),
822            QuantumGate::RY { qubit, angle } => self.apply_single(*qubit, &Self::ry(*angle)),
823            QuantumGate::RZ { qubit, angle } => self.apply_single(*qubit, &Self::rz(*angle)),
824            QuantumGate::CNOT { control, target } => {
825                self.apply_controlled_single(*control, *target, &Self::pauli_x())
826            }
827            QuantumGate::CZ { control, target } => {
828                self.apply_controlled_single(*control, *target, &Self::pauli_z())
829            }
830            QuantumGate::SWAP { qubit1, qubit2 } => self.apply_swap(*qubit1, *qubit2),
831            QuantumGate::Toffoli {
832                control1,
833                control2,
834                target,
835            } => self.apply_toffoli(*control1, *control2, *target),
836            QuantumGate::Custom {
837                name,
838                qubits,
839                parameters,
840            } => Self::apply_custom(name, qubits, parameters),
841        }
842    }
843
844    /// Apply a 2x2 unitary `matrix` to a single `qubit`.
845    fn apply_single(&mut self, qubit: u8, matrix: &[[Complex64; 2]; 2]) -> Result<()> {
846        let target = self.check_qubit(qubit)?;
847        let stride = 1usize << target;
848
849        for base in 0..self.amplitudes.len() {
850            // Process each amplitude pair exactly once: only when the target bit
851            // is 0 in `base`.
852            if base & stride == 0 {
853                let partner = base | stride;
854                let a0 = self.amplitudes[base];
855                let a1 = self.amplitudes[partner];
856                self.amplitudes[base] = matrix[0][0] * a0 + matrix[0][1] * a1;
857                self.amplitudes[partner] = matrix[1][0] * a0 + matrix[1][1] * a1;
858            }
859        }
860
861        Ok(())
862    }
863
864    /// Apply a 2x2 unitary `matrix` to `target`, conditioned on `control` being
865    /// in state `|1>`.
866    fn apply_controlled_single(
867        &mut self,
868        control: u8,
869        target: u8,
870        matrix: &[[Complex64; 2]; 2],
871    ) -> Result<()> {
872        let control_idx = self.check_qubit(control)?;
873        let target_idx = self.check_qubit(target)?;
874        if control_idx == target_idx {
875            return Err(TorshError::InvalidArgument(
876                "controlled gate requires distinct control and target qubits".to_string(),
877            ));
878        }
879
880        let control_mask = 1usize << control_idx;
881        let target_stride = 1usize << target_idx;
882
883        for base in 0..self.amplitudes.len() {
884            if base & control_mask == 0 {
885                continue; // Control qubit is |0>: identity.
886            }
887            if base & target_stride == 0 {
888                let partner = base | target_stride;
889                let a0 = self.amplitudes[base];
890                let a1 = self.amplitudes[partner];
891                self.amplitudes[base] = matrix[0][0] * a0 + matrix[0][1] * a1;
892                self.amplitudes[partner] = matrix[1][0] * a0 + matrix[1][1] * a1;
893            }
894        }
895
896        Ok(())
897    }
898
899    /// Exchange the states of two qubits.
900    fn apply_swap(&mut self, qubit1: u8, qubit2: u8) -> Result<()> {
901        let q1 = self.check_qubit(qubit1)?;
902        let q2 = self.check_qubit(qubit2)?;
903        if q1 == q2 {
904            return Ok(());
905        }
906
907        let mask1 = 1usize << q1;
908        let mask2 = 1usize << q2;
909
910        for base in 0..self.amplitudes.len() {
911            let bit1 = base & mask1 != 0;
912            let bit2 = base & mask2 != 0;
913            // Only swap the pair once, for the configuration (bit1=1, bit2=0).
914            if bit1 && !bit2 {
915                let partner = (base & !mask1) | mask2;
916                self.amplitudes.swap(base, partner);
917            }
918        }
919
920        Ok(())
921    }
922
923    /// Apply a doubly-controlled X (Toffoli) gate.
924    fn apply_toffoli(&mut self, control1: u8, control2: u8, target: u8) -> Result<()> {
925        let c1 = self.check_qubit(control1)?;
926        let c2 = self.check_qubit(control2)?;
927        let t = self.check_qubit(target)?;
928        if c1 == c2 || c1 == t || c2 == t {
929            return Err(TorshError::InvalidArgument(
930                "Toffoli gate requires three distinct qubits".to_string(),
931            ));
932        }
933
934        let c1_mask = 1usize << c1;
935        let c2_mask = 1usize << c2;
936        let t_stride = 1usize << t;
937
938        for base in 0..self.amplitudes.len() {
939            if base & c1_mask == 0 || base & c2_mask == 0 {
940                continue; // A control is |0>: identity.
941            }
942            if base & t_stride == 0 {
943                let partner = base | t_stride;
944                self.amplitudes.swap(base, partner);
945            }
946        }
947
948        Ok(())
949    }
950
951    /// Custom gates are not supported by the built-in simulator: their unitary
952    /// is opaque, so we cannot apply them. Refuse rather than silently skip the
953    /// gate (which would corrupt the simulated state).
954    fn apply_custom(name: &str, _qubits: &[u8], _parameters: &[f64]) -> Result<()> {
955        Err(TorshError::NotImplemented(format!(
956            "custom quantum gate '{name}' is not supported by the built-in state-vector \
957             simulator (no unitary definition is available)"
958        )))
959    }
960
961    /// Probability of each computational-basis measurement outcome, indexed by
962    /// the integer value of the bitstring (little-endian qubit ordering).
963    fn measurement_distribution(&self) -> Vec<f64> {
964        self.amplitudes.iter().map(|a| a.norm_sqr()).collect()
965    }
966
967    // --- Gate matrices -----------------------------------------------------
968
969    fn pauli_x() -> [[Complex64; 2]; 2] {
970        let zero = Complex64::new(0.0, 0.0);
971        let one = Complex64::new(1.0, 0.0);
972        [[zero, one], [one, zero]]
973    }
974
975    fn pauli_y() -> [[Complex64; 2]; 2] {
976        let zero = Complex64::new(0.0, 0.0);
977        let i = Complex64::new(0.0, 1.0);
978        [[zero, -i], [i, zero]]
979    }
980
981    fn pauli_z() -> [[Complex64; 2]; 2] {
982        let zero = Complex64::new(0.0, 0.0);
983        let one = Complex64::new(1.0, 0.0);
984        [[one, zero], [zero, -one]]
985    }
986
987    fn hadamard() -> [[Complex64; 2]; 2] {
988        let inv_sqrt2 = Complex64::new(std::f64::consts::FRAC_1_SQRT_2, 0.0);
989        [[inv_sqrt2, inv_sqrt2], [inv_sqrt2, -inv_sqrt2]]
990    }
991
992    fn phase_s() -> [[Complex64; 2]; 2] {
993        let zero = Complex64::new(0.0, 0.0);
994        let one = Complex64::new(1.0, 0.0);
995        let i = Complex64::new(0.0, 1.0);
996        [[one, zero], [zero, i]]
997    }
998
999    fn phase_t() -> [[Complex64; 2]; 2] {
1000        let zero = Complex64::new(0.0, 0.0);
1001        let one = Complex64::new(1.0, 0.0);
1002        // e^{i pi/4}
1003        let phase = Complex64::from_polar(1.0, std::f64::consts::FRAC_PI_4);
1004        [[one, zero], [zero, phase]]
1005    }
1006
1007    fn rx(angle: f64) -> [[Complex64; 2]; 2] {
1008        let cos = Complex64::new((angle / 2.0).cos(), 0.0);
1009        let neg_i_sin = Complex64::new(0.0, -(angle / 2.0).sin());
1010        [[cos, neg_i_sin], [neg_i_sin, cos]]
1011    }
1012
1013    fn ry(angle: f64) -> [[Complex64; 2]; 2] {
1014        let cos = Complex64::new((angle / 2.0).cos(), 0.0);
1015        let sin = Complex64::new((angle / 2.0).sin(), 0.0);
1016        [[cos, -sin], [sin, cos]]
1017    }
1018
1019    fn rz(angle: f64) -> [[Complex64; 2]; 2] {
1020        let zero = Complex64::new(0.0, 0.0);
1021        let neg = Complex64::from_polar(1.0, -angle / 2.0);
1022        let pos = Complex64::from_polar(1.0, angle / 2.0);
1023        [[neg, zero], [zero, pos]]
1024    }
1025}
1026
1027impl Default for ErrorMitigation {
1028    fn default() -> Self {
1029        Self {
1030            zero_noise_extrapolation: false,
1031            readout_error_mitigation: true,
1032            symmetry_verification: false,
1033            probabilistic_error_cancellation: false,
1034        }
1035    }
1036}
1037
1038impl QuantumCircuit {
1039    /// Create a new quantum circuit
1040    pub fn new(num_qubits: u8) -> Self {
1041        Self {
1042            num_qubits,
1043            gates: Vec::new(),
1044            measurements: Vec::new(),
1045            parameters: HashMap::new(),
1046        }
1047    }
1048
1049    /// Add a gate to the circuit
1050    pub fn add_gate(&mut self, gate: QuantumGate) {
1051        self.gates.push(gate);
1052    }
1053
1054    /// Add a measurement to the circuit
1055    pub fn add_measurement(&mut self, measurement: ClassicalMeasurement) {
1056        self.measurements.push(measurement);
1057    }
1058
1059    /// Set a parameter value
1060    pub fn set_parameter(&mut self, name: String, value: f64) {
1061        self.parameters.insert(name, value);
1062    }
1063
1064    /// Get circuit depth (number of gate layers)
1065    pub fn depth(&self) -> usize {
1066        // Simplified depth calculation
1067        self.gates.len()
1068    }
1069
1070    /// Count gates by type
1071    pub fn gate_counts(&self) -> HashMap<String, usize> {
1072        let mut counts = HashMap::new();
1073        for gate in &self.gates {
1074            let gate_type = match gate {
1075                QuantumGate::X { .. } => "X",
1076                QuantumGate::Y { .. } => "Y",
1077                QuantumGate::Z { .. } => "Z",
1078                QuantumGate::H { .. } => "H",
1079                QuantumGate::S { .. } => "S",
1080                QuantumGate::T { .. } => "T",
1081                QuantumGate::RX { .. } => "RX",
1082                QuantumGate::RY { .. } => "RY",
1083                QuantumGate::RZ { .. } => "RZ",
1084                QuantumGate::CNOT { .. } => "CNOT",
1085                QuantumGate::CZ { .. } => "CZ",
1086                QuantumGate::SWAP { .. } => "SWAP",
1087                QuantumGate::Toffoli { .. } => "Toffoli",
1088                QuantumGate::Custom { name, .. } => name,
1089            };
1090            *counts.entry(gate_type.to_string()).or_insert(0) += 1;
1091        }
1092        counts
1093    }
1094}
1095
1096/// Convenience functions for quantum computing
1097
1098/// Create a quantum backend with local simulator
1099pub fn create_local_quantum_backend(num_qubits: u8) -> QuantumComputingBackend {
1100    QuantumComputingBackend::new(QuantumBackend::LocalSimulator {
1101        num_qubits,
1102        precision: QuantumPrecision::Double,
1103    })
1104}
1105
1106/// Create a Qiskit backend
1107pub fn create_qiskit_backend(backend_name: String, shots: u32) -> QuantumComputingBackend {
1108    QuantumComputingBackend::new(QuantumBackend::Qiskit {
1109        backend_name,
1110        shots,
1111    })
1112}
1113
1114/// Create a basic quantum circuit for VQE
1115pub fn create_vqe_circuit(num_qubits: u8, depth: usize) -> QuantumCircuit {
1116    let mut circuit = QuantumCircuit::new(num_qubits);
1117
1118    // Add parameterized gates for VQE
1119    for layer in 0..depth {
1120        // Add rotation gates
1121        for qubit in 0..num_qubits {
1122            circuit.add_gate(QuantumGate::RY {
1123                qubit,
1124                angle: std::f64::consts::PI / 4.0, // Default angle
1125            });
1126        }
1127
1128        // Add entangling gates
1129        for qubit in 0..num_qubits - 1 {
1130            circuit.add_gate(QuantumGate::CNOT {
1131                control: qubit,
1132                target: qubit + 1,
1133            });
1134        }
1135
1136        // Set parameter names
1137        circuit.set_parameter(format!("theta_{}", layer), 0.0);
1138    }
1139
1140    // Add measurements
1141    for qubit in 0..num_qubits {
1142        circuit.add_measurement(ClassicalMeasurement {
1143            qubit,
1144            classical_bit: qubit,
1145            measurement_basis: MeasurementBasis::Computational,
1146        });
1147    }
1148
1149    circuit
1150}
1151
1152/// Create a QAOA circuit
1153pub fn create_qaoa_circuit(num_qubits: u8, p: usize) -> QuantumCircuit {
1154    let mut circuit = QuantumCircuit::new(num_qubits);
1155
1156    // Initial superposition
1157    for qubit in 0..num_qubits {
1158        circuit.add_gate(QuantumGate::H { qubit });
1159    }
1160
1161    // QAOA layers
1162    for layer in 0..p {
1163        // Problem Hamiltonian
1164        for qubit in 0..num_qubits - 1 {
1165            circuit.add_gate(QuantumGate::CNOT {
1166                control: qubit,
1167                target: qubit + 1,
1168            });
1169            circuit.add_gate(QuantumGate::RZ {
1170                qubit: qubit + 1,
1171                angle: 1.0, // gamma parameter
1172            });
1173            circuit.add_gate(QuantumGate::CNOT {
1174                control: qubit,
1175                target: qubit + 1,
1176            });
1177        }
1178
1179        // Mixer Hamiltonian
1180        for qubit in 0..num_qubits {
1181            circuit.add_gate(QuantumGate::RX {
1182                qubit,
1183                angle: 1.0, // beta parameter
1184            });
1185        }
1186
1187        circuit.set_parameter(format!("gamma_{}", layer), 1.0);
1188        circuit.set_parameter(format!("beta_{}", layer), 1.0);
1189    }
1190
1191    // Measurements
1192    for qubit in 0..num_qubits {
1193        circuit.add_measurement(ClassicalMeasurement {
1194            qubit,
1195            classical_bit: qubit,
1196            measurement_basis: MeasurementBasis::Computational,
1197        });
1198    }
1199
1200    circuit
1201}
1202
1203/// Integrate quantum computing with classical FX graph
1204pub fn integrate_quantum_computing(
1205    graph: FxGraph,
1206    quantum_backend: &mut QuantumComputingBackend,
1207    strategy: HybridOptimizationStrategy,
1208) -> Result<HybridWorkflow> {
1209    // Create quantum circuits based on strategy
1210    let circuits = match strategy {
1211        HybridOptimizationStrategy::VQE => {
1212            vec![create_vqe_circuit(4, 3)]
1213        }
1214        HybridOptimizationStrategy::QAOA => {
1215            vec![create_qaoa_circuit(4, 2)]
1216        }
1217        HybridOptimizationStrategy::QML => {
1218            vec![create_vqe_circuit(6, 2)] // Quantum ML circuit
1219        }
1220        _ => {
1221            vec![QuantumCircuit::new(2)] // Default circuit
1222        }
1223    };
1224
1225    // Add circuits to backend
1226    for circuit in &circuits {
1227        quantum_backend.add_circuit(circuit.clone())?;
1228    }
1229
1230    // Create hybrid workflow
1231    quantum_backend.create_hybrid_workflow(graph, circuits, strategy)
1232}
1233
1234#[cfg(test)]
1235mod tests {
1236    use super::*;
1237
1238    #[test]
1239    fn test_quantum_backend_creation() {
1240        let backend = create_local_quantum_backend(4);
1241        assert_eq!(backend.circuits.len(), 0);
1242    }
1243
1244    #[test]
1245    fn test_quantum_circuit_creation() {
1246        let mut circuit = QuantumCircuit::new(2);
1247        circuit.add_gate(QuantumGate::H { qubit: 0 });
1248        circuit.add_gate(QuantumGate::CNOT {
1249            control: 0,
1250            target: 1,
1251        });
1252
1253        assert_eq!(circuit.num_qubits, 2);
1254        assert_eq!(circuit.gates.len(), 2);
1255        assert_eq!(circuit.depth(), 2);
1256    }
1257
1258    #[test]
1259    fn test_circuit_execution() {
1260        let mut backend = create_local_quantum_backend(2);
1261        let circuit = QuantumCircuit::new(2);
1262        let circuit_id = backend.add_circuit(circuit).unwrap();
1263
1264        let result = backend.execute_circuit(circuit_id, 1000).unwrap();
1265        assert_eq!(result.shots, 1000);
1266        assert!(!result.counts.is_empty());
1267    }
1268
1269    #[test]
1270    fn test_vqe_circuit_creation() {
1271        let circuit = create_vqe_circuit(4, 2);
1272        assert_eq!(circuit.num_qubits, 4);
1273        assert!(!circuit.gates.is_empty());
1274        assert!(!circuit.parameters.is_empty());
1275    }
1276
1277    #[test]
1278    fn test_qaoa_circuit_creation() {
1279        let circuit = create_qaoa_circuit(3, 2);
1280        assert_eq!(circuit.num_qubits, 3);
1281        assert!(!circuit.gates.is_empty());
1282        assert!(circuit.parameters.contains_key("gamma_0"));
1283        assert!(circuit.parameters.contains_key("beta_0"));
1284    }
1285
1286    #[test]
1287    fn test_gate_counting() {
1288        let mut circuit = QuantumCircuit::new(2);
1289        circuit.add_gate(QuantumGate::H { qubit: 0 });
1290        circuit.add_gate(QuantumGate::H { qubit: 1 });
1291        circuit.add_gate(QuantumGate::CNOT {
1292            control: 0,
1293            target: 1,
1294        });
1295
1296        let counts = circuit.gate_counts();
1297        assert_eq!(counts.get("H"), Some(&2));
1298        assert_eq!(counts.get("CNOT"), Some(&1));
1299    }
1300
1301    #[test]
1302    fn test_hybrid_workflow_creation() {
1303        let graph = FxGraph::new();
1304        let backend = create_local_quantum_backend(4);
1305
1306        let workflow = backend.create_hybrid_workflow(
1307            graph,
1308            vec![create_vqe_circuit(4, 2)],
1309            HybridOptimizationStrategy::VQE,
1310        );
1311
1312        assert!(workflow.is_ok());
1313    }
1314
1315    #[test]
1316    fn test_cloud_providers() {
1317        let providers = vec![
1318            CloudProvider::IBM,
1319            CloudProvider::Google,
1320            CloudProvider::Rigetti,
1321            CloudProvider::IonQ,
1322            CloudProvider::Honeywell,
1323            CloudProvider::AWS,
1324            CloudProvider::Azure,
1325        ];
1326
1327        assert_eq!(providers.len(), 7);
1328    }
1329
1330    #[test]
1331    fn test_error_mitigation() {
1332        let backend = create_local_quantum_backend(2);
1333        let mut result = QuantumExecutionResult {
1334            shots: 1000,
1335            counts: HashMap::new(),
1336            probabilities: HashMap::new(),
1337            execution_time: std::time::Duration::from_millis(100),
1338            quantum_volume: Some(4.0),
1339            fidelity: Some(0.95),
1340        };
1341
1342        assert!(backend.apply_error_mitigation(&mut result).is_ok());
1343    }
1344
1345    #[test]
1346    fn test_state_vector_x_gate_flips_qubit() {
1347        // X|0> = |1>: the only nonzero amplitude must be the |1> basis state.
1348        let mut circuit = QuantumCircuit::new(1);
1349        circuit.add_gate(QuantumGate::X { qubit: 0 });
1350        let state = StateVector::from_circuit(&circuit, 1).expect("simulation should succeed");
1351        let dist = state.measurement_distribution();
1352        assert!((dist[0]).abs() < 1e-12, "|0> probability should be ~0");
1353        assert!(
1354            (dist[1] - 1.0).abs() < 1e-12,
1355            "|1> probability should be ~1"
1356        );
1357    }
1358
1359    #[test]
1360    fn test_state_vector_hadamard_uniform_superposition() {
1361        // H|0> = (|0> + |1>)/sqrt(2): equal probabilities of 0.5.
1362        let mut circuit = QuantumCircuit::new(1);
1363        circuit.add_gate(QuantumGate::H { qubit: 0 });
1364        let state = StateVector::from_circuit(&circuit, 1).expect("simulation should succeed");
1365        let dist = state.measurement_distribution();
1366        assert!((dist[0] - 0.5).abs() < 1e-12);
1367        assert!((dist[1] - 0.5).abs() < 1e-12);
1368    }
1369
1370    #[test]
1371    fn test_state_vector_bell_state() {
1372        // H on qubit 0 then CNOT(0->1) produces the Bell state
1373        // (|00> + |11>)/sqrt(2): only outcomes 00 and 11 have probability 0.5,
1374        // and the off-diagonal outcomes 01 and 10 are forbidden.
1375        let mut circuit = QuantumCircuit::new(2);
1376        circuit.add_gate(QuantumGate::H { qubit: 0 });
1377        circuit.add_gate(QuantumGate::CNOT {
1378            control: 0,
1379            target: 1,
1380        });
1381        let state = StateVector::from_circuit(&circuit, 2).expect("simulation should succeed");
1382        let dist = state.measurement_distribution();
1383        assert!((dist[0b00] - 0.5).abs() < 1e-12);
1384        assert!((dist[0b11] - 0.5).abs() < 1e-12);
1385        assert!(dist[0b01].abs() < 1e-12);
1386        assert!(dist[0b10].abs() < 1e-12);
1387
1388        // Total probability is conserved.
1389        let total: f64 = dist.iter().sum();
1390        assert!((total - 1.0).abs() < 1e-12);
1391    }
1392
1393    #[test]
1394    fn test_state_vector_swap() {
1395        // Prepare |10> (qubit 0 = 1, qubit 1 = 0) then SWAP -> |01>.
1396        let mut circuit = QuantumCircuit::new(2);
1397        circuit.add_gate(QuantumGate::X { qubit: 0 });
1398        circuit.add_gate(QuantumGate::SWAP {
1399            qubit1: 0,
1400            qubit2: 1,
1401        });
1402        let state = StateVector::from_circuit(&circuit, 2).expect("simulation should succeed");
1403        let dist = state.measurement_distribution();
1404        // After swap, qubit 1 is set: basis index 0b10 == 2.
1405        assert!((dist[0b10] - 1.0).abs() < 1e-12);
1406    }
1407
1408    #[test]
1409    fn test_state_vector_toffoli() {
1410        // Toffoli flips the target only when both controls are |1>.
1411        let mut circuit = QuantumCircuit::new(3);
1412        circuit.add_gate(QuantumGate::X { qubit: 0 });
1413        circuit.add_gate(QuantumGate::X { qubit: 1 });
1414        circuit.add_gate(QuantumGate::Toffoli {
1415            control1: 0,
1416            control2: 1,
1417            target: 2,
1418        });
1419        let state = StateVector::from_circuit(&circuit, 3).expect("simulation should succeed");
1420        let dist = state.measurement_distribution();
1421        // Controls 0,1 set and target 2 flipped: 0b111 == 7.
1422        assert!((dist[0b111] - 1.0).abs() < 1e-12);
1423    }
1424
1425    #[test]
1426    fn test_state_vector_rotation_norm_preserved() {
1427        // An arbitrary rotation keeps the state normalized.
1428        let mut circuit = QuantumCircuit::new(1);
1429        circuit.add_gate(QuantumGate::RY {
1430            qubit: 0,
1431            angle: 0.73,
1432        });
1433        let state = StateVector::from_circuit(&circuit, 1).expect("simulation should succeed");
1434        let total: f64 = state.measurement_distribution().iter().sum();
1435        assert!((total - 1.0).abs() < 1e-12);
1436    }
1437
1438    #[test]
1439    fn test_simulate_locally_sampling_matches_distribution() {
1440        // With many shots, the empirical distribution of a Bell state must
1441        // concentrate on 00 and 11 and (almost) never produce 01 or 10.
1442        let backend = create_local_quantum_backend(2);
1443        let mut circuit = QuantumCircuit::new(2);
1444        circuit.add_gate(QuantumGate::H { qubit: 0 });
1445        circuit.add_gate(QuantumGate::CNOT {
1446            control: 0,
1447            target: 1,
1448        });
1449        let result = backend
1450            .simulate_locally(&circuit, 4000, 2)
1451            .expect("simulation should succeed");
1452
1453        assert_eq!(result.shots, 4000);
1454        assert_eq!(result.fidelity, Some(1.0));
1455        // Forbidden outcomes must not appear in an ideal simulation.
1456        assert!(!result.counts.contains_key("01"));
1457        assert!(!result.counts.contains_key("10"));
1458
1459        // The probabilities reported must sum to ~1 over observed outcomes.
1460        let prob_sum: f64 = result.probabilities.values().sum();
1461        assert!((prob_sum - 1.0).abs() < 1e-9);
1462
1463        // Both allowed outcomes should be reasonably balanced around 0.5.
1464        let p00 = result.probabilities.get("00").copied().unwrap_or(0.0);
1465        let p11 = result.probabilities.get("11").copied().unwrap_or(0.0);
1466        assert!(p00 > 0.4 && p00 < 0.6, "p00 = {p00}");
1467        assert!(p11 > 0.4 && p11 < 0.6, "p11 = {p11}");
1468    }
1469
1470    #[test]
1471    fn test_custom_gate_is_rejected() {
1472        // The simulator must refuse custom gates rather than silently skip them.
1473        let mut circuit = QuantumCircuit::new(1);
1474        circuit.add_gate(QuantumGate::Custom {
1475            name: "mystery".to_string(),
1476            qubits: vec![0],
1477            parameters: vec![],
1478        });
1479        let result = StateVector::from_circuit(&circuit, 1);
1480        assert!(result.is_err());
1481    }
1482
1483    #[test]
1484    fn test_out_of_range_qubit_is_rejected() {
1485        let mut circuit = QuantumCircuit::new(1);
1486        circuit.add_gate(QuantumGate::X { qubit: 3 });
1487        let result = StateVector::from_circuit(&circuit, 1);
1488        assert!(result.is_err());
1489    }
1490
1491    #[test]
1492    fn test_merge_rotation_gates_combines_angles() {
1493        // Two RZ rotations on the same qubit collapse into one with the summed
1494        // angle, rather than dropping a gate.
1495        let mut circuit = QuantumCircuit::new(1);
1496        circuit.add_gate(QuantumGate::RZ {
1497            qubit: 0,
1498            angle: 0.3,
1499        });
1500        circuit.add_gate(QuantumGate::RZ {
1501            qubit: 0,
1502            angle: 0.4,
1503        });
1504        QuantumComputingBackend::merge_rotation_gates(&mut circuit);
1505        assert_eq!(circuit.gates.len(), 1);
1506        match &circuit.gates[0] {
1507            QuantumGate::RZ { qubit, angle } => {
1508                assert_eq!(*qubit, 0);
1509                assert!((angle - 0.7).abs() < 1e-12);
1510            }
1511            other => panic!("expected merged RZ gate, got {other:?}"),
1512        }
1513    }
1514
1515    #[test]
1516    fn test_external_backends_return_honest_errors() {
1517        // Qiskit / Cirq / cloud backends are not wired, so execution must fail
1518        // honestly instead of silently producing local-simulator results.
1519        let circuit = QuantumCircuit::new(2);
1520
1521        let mut qiskit = create_qiskit_backend("aer".to_string(), 1024);
1522        let circuit_id = qiskit.add_circuit(circuit.clone()).expect("add circuit");
1523        assert!(qiskit.execute_circuit(circuit_id, 1024).is_err());
1524
1525        let mut cirq = QuantumComputingBackend::new(QuantumBackend::Cirq {
1526            simulator_type: "density_matrix".to_string(),
1527            noise_model: None,
1528        });
1529        let cirq_id = cirq.add_circuit(circuit.clone()).expect("add circuit");
1530        assert!(cirq.execute_circuit(cirq_id, 1024).is_err());
1531
1532        let mut cloud = QuantumComputingBackend::new(QuantumBackend::CloudQuantum {
1533            provider: CloudProvider::IBM,
1534            device_name: "ibmq".to_string(),
1535            credentials: "token".to_string(),
1536        });
1537        let cloud_id = cloud.add_circuit(circuit).expect("add circuit");
1538        assert!(cloud.execute_circuit(cloud_id, 1024).is_err());
1539    }
1540}