quantrs2_circuit/
equivalence.rs

1//! Circuit equivalence checking algorithms with `SciRS2` numerical tolerance
2//!
3//! This module provides various methods to check if two quantum circuits
4//! are equivalent, including exact and approximate equivalence using
5//! `SciRS2`'s advanced numerical analysis capabilities.
6
7use crate::builder::Circuit;
8use crate::scirs2_integration::{AnalyzerConfig, SciRS2CircuitAnalyzer};
9use quantrs2_core::{
10    error::{QuantRS2Error, QuantRS2Result},
11    gate::{
12        multi::{CRX, CRY, CRZ},
13        single::{RotationX, RotationY, RotationZ},
14        GateOp,
15    },
16    qubit::QubitId,
17};
18use scirs2_core::ndarray::{array, Array2, ArrayView2};
19use scirs2_core::Complex64;
20use serde::{Deserialize, Serialize};
21use std::collections::HashMap;
22
23/// Default tolerance for numerical comparisons
24const DEFAULT_TOLERANCE: f64 = 1e-10;
25
26/// Enhanced tolerance with `SciRS2` statistical analysis
27const SCIRS2_DEFAULT_TOLERANCE: f64 = 1e-12;
28
29/// Tolerance for complex number comparisons
30const COMPLEX_TOLERANCE: f64 = 1e-14;
31
32/// Enhanced result of equivalence check with `SciRS2` analysis
33#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct EquivalenceResult {
35    /// Whether the circuits are equivalent
36    pub equivalent: bool,
37    /// Type of equivalence check performed
38    pub check_type: EquivalenceType,
39    /// Maximum difference found (for numerical checks)
40    pub max_difference: Option<f64>,
41    /// Additional details about the check
42    pub details: String,
43    /// `SciRS2` numerical analysis results
44    pub numerical_analysis: Option<NumericalAnalysis>,
45    /// Confidence score (0.0 to 1.0)
46    pub confidence_score: f64,
47    /// Statistical significance (p-value if applicable)
48    pub statistical_significance: Option<f64>,
49    /// Error bounds and uncertainty quantification
50    pub error_bounds: Option<ErrorBounds>,
51}
52
53/// `SciRS2` numerical analysis for equivalence checking
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct NumericalAnalysis {
56    /// Condition number of the matrices involved
57    pub condition_number: Option<f64>,
58    /// Numerical rank of difference matrix
59    pub numerical_rank: Option<usize>,
60    /// Frobenius norm of the difference
61    pub frobenius_norm: f64,
62    /// Spectral norm of the difference
63    pub spectral_norm: Option<f64>,
64    /// Adaptive tolerance used based on circuit complexity
65    pub adaptive_tolerance: f64,
66    /// Matrix factorization stability indicator
67    pub stability_indicator: f64,
68}
69
70/// Error bounds and uncertainty quantification
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ErrorBounds {
73    /// Lower bound of the error estimate
74    pub lower_bound: f64,
75    /// Upper bound of the error estimate
76    pub upper_bound: f64,
77    /// Confidence interval level (e.g., 0.95 for 95%)
78    pub confidence_level: f64,
79    /// Standard deviation of error estimates
80    pub standard_deviation: Option<f64>,
81}
82
83/// Types of equivalence checks with `SciRS2` enhancements
84#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
85pub enum EquivalenceType {
86    /// Check if circuits produce identical unitaries
87    UnitaryEquivalence,
88    /// Check if circuits produce same output states for all inputs
89    StateVectorEquivalence,
90    /// Check if measurement probabilities are identical
91    ProbabilisticEquivalence,
92    /// Check if circuits have identical gate structure
93    StructuralEquivalence,
94    /// Check if circuits are equivalent up to a global phase
95    GlobalPhaseEquivalence,
96    /// SciRS2-powered numerical equivalence with adaptive tolerance
97    SciRS2NumericalEquivalence,
98    /// `SciRS2` statistical equivalence with confidence intervals
99    SciRS2StatisticalEquivalence,
100    /// `SciRS2` graph-based structural equivalence
101    SciRS2GraphEquivalence,
102}
103
104/// Enhanced options for equivalence checking with `SciRS2` features
105#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct EquivalenceOptions {
107    /// Numerical tolerance for comparisons
108    pub tolerance: f64,
109    /// Whether to ignore global phase differences
110    pub ignore_global_phase: bool,
111    /// Whether to check all computational basis states
112    pub check_all_states: bool,
113    /// Maximum circuit size for unitary construction
114    pub max_unitary_qubits: usize,
115    /// Enable `SciRS2` adaptive tolerance
116    pub enable_adaptive_tolerance: bool,
117    /// Enable `SciRS2` statistical analysis
118    pub enable_statistical_analysis: bool,
119    /// Enable `SciRS2` numerical stability analysis
120    pub enable_stability_analysis: bool,
121    /// Enable `SciRS2` graph-based comparison
122    pub enable_graph_comparison: bool,
123    /// Confidence level for statistical tests (e.g., 0.95)
124    pub confidence_level: f64,
125    /// Maximum condition number for numerical stability
126    pub max_condition_number: f64,
127    /// `SciRS2` analyzer configuration
128    pub scirs2_config: Option<AnalyzerConfig>,
129    /// Complex number tolerance
130    pub complex_tolerance: f64,
131    /// Enable parallel computation for large circuits
132    pub enable_parallel_computation: bool,
133}
134
135impl Default for EquivalenceOptions {
136    fn default() -> Self {
137        Self {
138            tolerance: DEFAULT_TOLERANCE,
139            ignore_global_phase: true,
140            check_all_states: true,
141            max_unitary_qubits: 10,
142            enable_adaptive_tolerance: true,
143            enable_statistical_analysis: true,
144            enable_stability_analysis: true,
145            enable_graph_comparison: false, // Optional for performance
146            confidence_level: 0.95,
147            max_condition_number: 1e12,
148            scirs2_config: None, // Will use default if needed
149            complex_tolerance: COMPLEX_TOLERANCE,
150            enable_parallel_computation: true,
151        }
152    }
153}
154
155/// Enhanced circuit equivalence checker with `SciRS2` integration
156pub struct EquivalenceChecker {
157    options: EquivalenceOptions,
158    scirs2_analyzer: Option<SciRS2CircuitAnalyzer>,
159    numerical_cache: HashMap<String, NumericalAnalysis>,
160}
161
162impl EquivalenceChecker {
163    /// Create a new equivalence checker with options
164    #[must_use]
165    pub fn new(options: EquivalenceOptions) -> Self {
166        let scirs2_analyzer = if options.enable_graph_comparison
167            || options.enable_statistical_analysis
168            || options.enable_stability_analysis
169        {
170            Some(SciRS2CircuitAnalyzer::new())
171        } else {
172            None
173        };
174
175        Self {
176            options,
177            scirs2_analyzer,
178            numerical_cache: HashMap::new(),
179        }
180    }
181
182    /// Create a new equivalence checker with default options
183    #[must_use]
184    pub fn default() -> Self {
185        Self::new(EquivalenceOptions::default())
186    }
187
188    /// Create a new equivalence checker with custom `SciRS2` configuration
189    #[must_use]
190    pub fn with_scirs2_config(config: AnalyzerConfig) -> Self {
191        let scirs2_analyzer = Some(SciRS2CircuitAnalyzer::with_config(config.clone()));
192
193        Self {
194            options: EquivalenceOptions {
195                scirs2_config: Some(config),
196                enable_graph_comparison: true,
197                ..Default::default()
198            },
199            scirs2_analyzer,
200            numerical_cache: HashMap::new(),
201        }
202    }
203
204    /// Check if two circuits are equivalent using all methods including `SciRS2`
205    pub fn check_equivalence<const N: usize>(
206        &mut self,
207        circuit1: &Circuit<N>,
208        circuit2: &Circuit<N>,
209    ) -> QuantRS2Result<EquivalenceResult> {
210        // Try SciRS2 graph-based equivalence first if enabled
211        if self.options.enable_graph_comparison {
212            if let Ok(result) = self.check_scirs2_graph_equivalence(circuit1, circuit2) {
213                if result.equivalent {
214                    return Ok(result);
215                }
216            }
217        }
218
219        // Try structural equivalence (fastest)
220        if let Ok(result) = self.check_structural_equivalence(circuit1, circuit2) {
221            if result.equivalent {
222                return Ok(result);
223            }
224        }
225
226        // Try SciRS2 numerical equivalence if enabled
227        if (self.options.enable_adaptive_tolerance || self.options.enable_statistical_analysis)
228            && N <= self.options.max_unitary_qubits
229        {
230            return self.check_scirs2_numerical_equivalence(circuit1, circuit2);
231        }
232
233        // Try unitary equivalence if circuits are small enough
234        if N <= self.options.max_unitary_qubits {
235            return self.check_unitary_equivalence(circuit1, circuit2);
236        }
237
238        // For larger circuits, use state vector equivalence
239        self.check_state_vector_equivalence(circuit1, circuit2)
240    }
241
242    /// Check equivalence using `SciRS2` numerical analysis with adaptive tolerance
243    pub fn check_scirs2_numerical_equivalence<const N: usize>(
244        &mut self,
245        circuit1: &Circuit<N>,
246        circuit2: &Circuit<N>,
247    ) -> QuantRS2Result<EquivalenceResult> {
248        if N > self.options.max_unitary_qubits {
249            return Err(QuantRS2Error::InvalidInput(format!(
250                "Circuit too large for SciRS2 numerical analysis: {} qubits (max: {})",
251                N, self.options.max_unitary_qubits
252            )));
253        }
254
255        // Get unitaries for both circuits
256        let unitary1 = self.get_circuit_unitary(circuit1)?;
257        let unitary2 = self.get_circuit_unitary(circuit2)?;
258
259        // Perform SciRS2 numerical analysis
260        let numerical_analysis = self.perform_scirs2_numerical_analysis(&unitary1, &unitary2)?;
261
262        // Calculate adaptive tolerance based on circuit complexity
263        let adaptive_tolerance = self.calculate_adaptive_tolerance::<N>(N, &numerical_analysis);
264
265        // Compare unitaries with enhanced numerical analysis
266        let (equivalent, max_diff, confidence_score, error_bounds) =
267            self.scirs2_unitaries_equal(&unitary1, &unitary2, adaptive_tolerance)?;
268
269        // Calculate statistical significance if enabled
270        let statistical_significance = if self.options.enable_statistical_analysis {
271            Some(self.calculate_statistical_significance(&unitary1, &unitary2, max_diff)?)
272        } else {
273            None
274        };
275
276        Ok(EquivalenceResult {
277            equivalent,
278            check_type: EquivalenceType::SciRS2NumericalEquivalence,
279            max_difference: Some(max_diff),
280            details: format!(
281                "SciRS2 numerical analysis: tolerance={:.2e}, confidence={:.3}, condition_number={:.2e}",
282                adaptive_tolerance,
283                confidence_score,
284                numerical_analysis.condition_number.unwrap_or(0.0)
285            ),
286            numerical_analysis: Some(numerical_analysis),
287            confidence_score,
288            statistical_significance,
289            error_bounds: Some(error_bounds),
290        })
291    }
292
293    /// Check equivalence using `SciRS2` graph-based analysis
294    pub fn check_scirs2_graph_equivalence<const N: usize>(
295        &mut self,
296        circuit1: &Circuit<N>,
297        circuit2: &Circuit<N>,
298    ) -> QuantRS2Result<EquivalenceResult> {
299        let analyzer = self.scirs2_analyzer.as_mut().ok_or_else(|| {
300            QuantRS2Error::InvalidInput("SciRS2 analyzer not initialized".to_string())
301        })?;
302
303        // Convert circuits to SciRS2 graphs
304        let graph1 = analyzer.circuit_to_scirs2_graph(circuit1)?;
305        let graph2 = analyzer.circuit_to_scirs2_graph(circuit2)?;
306
307        // Perform graph-based equivalence checking
308        let (equivalent, similarity_score, graph_details) =
309            self.compare_scirs2_graphs(&graph1, &graph2)?;
310
311        Ok(EquivalenceResult {
312            equivalent,
313            check_type: EquivalenceType::SciRS2GraphEquivalence,
314            max_difference: Some(1.0 - similarity_score),
315            details: graph_details,
316            numerical_analysis: None,
317            confidence_score: similarity_score,
318            statistical_significance: None,
319            error_bounds: None,
320        })
321    }
322
323    /// Check structural equivalence (exact gate-by-gate match)
324    pub fn check_structural_equivalence<const N: usize>(
325        &self,
326        circuit1: &Circuit<N>,
327        circuit2: &Circuit<N>,
328    ) -> QuantRS2Result<EquivalenceResult> {
329        if circuit1.num_gates() != circuit2.num_gates() {
330            return Ok(EquivalenceResult {
331                equivalent: false,
332                check_type: EquivalenceType::StructuralEquivalence,
333                max_difference: None,
334                details: format!(
335                    "Different number of gates: {} vs {}",
336                    circuit1.num_gates(),
337                    circuit2.num_gates()
338                ),
339                numerical_analysis: None,
340                confidence_score: 0.0,
341                statistical_significance: None,
342                error_bounds: None,
343            });
344        }
345
346        let gates1 = circuit1.gates();
347        let gates2 = circuit2.gates();
348
349        for (i, (gate1, gate2)) in gates1.iter().zip(gates2.iter()).enumerate() {
350            if !self.gates_equal(gate1.as_ref(), gate2.as_ref()) {
351                return Ok(EquivalenceResult {
352                    equivalent: false,
353                    check_type: EquivalenceType::StructuralEquivalence,
354                    max_difference: None,
355                    details: format!(
356                        "Gates differ at position {}: {} vs {}",
357                        i,
358                        gate1.name(),
359                        gate2.name()
360                    ),
361                    numerical_analysis: None,
362                    confidence_score: 0.0,
363                    statistical_significance: None,
364                    error_bounds: None,
365                });
366            }
367        }
368
369        Ok(EquivalenceResult {
370            equivalent: true,
371            check_type: EquivalenceType::StructuralEquivalence,
372            max_difference: Some(0.0),
373            details: "Circuits are structurally identical".to_string(),
374            numerical_analysis: None,
375            confidence_score: 1.0,
376            statistical_significance: None,
377            error_bounds: None,
378        })
379    }
380
381    /// Check if two gates are equal
382    ///
383    /// Compares gates by name, qubits, and parameters (for parametric gates).
384    /// Uses numerical tolerance for parameter comparison.
385    fn gates_equal(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
386        // Check gate names
387        if gate1.name() != gate2.name() {
388            return false;
389        }
390
391        // Check qubits
392        let qubits1 = gate1.qubits();
393        let qubits2 = gate2.qubits();
394
395        if qubits1.len() != qubits2.len() {
396            return false;
397        }
398
399        for (q1, q2) in qubits1.iter().zip(qubits2.iter()) {
400            if q1 != q2 {
401                return false;
402            }
403        }
404
405        // Check parameters for parametric gates
406        if !self.check_gate_parameters(gate1, gate2) {
407            return false;
408        }
409
410        true
411    }
412
413    /// Check if parameters of two gates are equal (for parametric gates)
414    fn check_gate_parameters(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> bool {
415        // Try to downcast to known parametric gate types and compare parameters
416        // Single-qubit rotation gates
417        if let Some(rx1) = gate1.as_any().downcast_ref::<RotationX>() {
418            if let Some(rx2) = gate2.as_any().downcast_ref::<RotationX>() {
419                return (rx1.theta - rx2.theta).abs() < self.options.tolerance;
420            }
421        }
422
423        if let Some(ry1) = gate1.as_any().downcast_ref::<RotationY>() {
424            if let Some(ry2) = gate2.as_any().downcast_ref::<RotationY>() {
425                return (ry1.theta - ry2.theta).abs() < self.options.tolerance;
426            }
427        }
428
429        if let Some(rz1) = gate1.as_any().downcast_ref::<RotationZ>() {
430            if let Some(rz2) = gate2.as_any().downcast_ref::<RotationZ>() {
431                return (rz1.theta - rz2.theta).abs() < self.options.tolerance;
432            }
433        }
434
435        // Controlled rotation gates
436        if let Some(crx1) = gate1.as_any().downcast_ref::<CRX>() {
437            if let Some(crx2) = gate2.as_any().downcast_ref::<CRX>() {
438                return (crx1.theta - crx2.theta).abs() < self.options.tolerance;
439            }
440        }
441
442        if let Some(cry1) = gate1.as_any().downcast_ref::<CRY>() {
443            if let Some(cry2) = gate2.as_any().downcast_ref::<CRY>() {
444                return (cry1.theta - cry2.theta).abs() < self.options.tolerance;
445            }
446        }
447
448        if let Some(crz1) = gate1.as_any().downcast_ref::<CRZ>() {
449            if let Some(crz2) = gate2.as_any().downcast_ref::<CRZ>() {
450                return (crz1.theta - crz2.theta).abs() < self.options.tolerance;
451            }
452        }
453
454        // If not a known parametric gate type, assume parameters match
455        // (non-parametric gates always match on parameters)
456        true
457    }
458
459    /// Check unitary equivalence
460    pub fn check_unitary_equivalence<const N: usize>(
461        &self,
462        circuit1: &Circuit<N>,
463        circuit2: &Circuit<N>,
464    ) -> QuantRS2Result<EquivalenceResult> {
465        if N > self.options.max_unitary_qubits {
466            return Err(QuantRS2Error::InvalidInput(format!(
467                "Circuit too large for unitary construction: {} qubits (max: {})",
468                N, self.options.max_unitary_qubits
469            )));
470        }
471
472        // Get unitaries for both circuits
473        let unitary1 = self.get_circuit_unitary(circuit1)?;
474        let unitary2 = self.get_circuit_unitary(circuit2)?;
475
476        // Compare unitaries
477        let (equivalent, max_diff) = if self.options.ignore_global_phase {
478            self.unitaries_equal_up_to_phase(&unitary1, &unitary2)
479        } else {
480            self.unitaries_equal(&unitary1, &unitary2)
481        };
482
483        Ok(EquivalenceResult {
484            equivalent,
485            check_type: if self.options.ignore_global_phase {
486                EquivalenceType::GlobalPhaseEquivalence
487            } else {
488                EquivalenceType::UnitaryEquivalence
489            },
490            max_difference: Some(max_diff),
491            details: if equivalent {
492                "Unitaries are equivalent".to_string()
493            } else {
494                format!("Maximum unitary difference: {max_diff:.2e}")
495            },
496            numerical_analysis: None,
497            confidence_score: if equivalent {
498                1.0 - (max_diff / self.options.tolerance)
499            } else {
500                0.0
501            },
502            statistical_significance: None,
503            error_bounds: None,
504        })
505    }
506
507    /// Get the unitary matrix for a circuit
508    fn get_circuit_unitary<const N: usize>(
509        &self,
510        circuit: &Circuit<N>,
511    ) -> QuantRS2Result<Array2<Complex64>> {
512        let dim = 1 << N;
513        let mut unitary = Array2::eye(dim);
514
515        // Apply each gate to the unitary
516        for gate in circuit.gates() {
517            self.apply_gate_to_unitary(&mut unitary, gate.as_ref(), N)?;
518        }
519
520        Ok(unitary)
521    }
522
523    /// Apply a gate to a unitary matrix
524    fn apply_gate_to_unitary(
525        &self,
526        unitary: &mut Array2<Complex64>,
527        gate: &dyn GateOp,
528        num_qubits: usize,
529    ) -> QuantRS2Result<()> {
530        let gate_matrix = self.get_gate_matrix(gate)?;
531        let qubits = gate.qubits();
532
533        // Apply the gate matrix to the full unitary
534        match qubits.len() {
535            1 => {
536                let qubit_idx = qubits[0].id() as usize;
537                self.apply_single_qubit_gate(unitary, &gate_matrix, qubit_idx, num_qubits)?;
538            }
539            2 => {
540                let control_idx = qubits[0].id() as usize;
541                let target_idx = qubits[1].id() as usize;
542                self.apply_two_qubit_gate(
543                    unitary,
544                    &gate_matrix,
545                    control_idx,
546                    target_idx,
547                    num_qubits,
548                )?;
549            }
550            _ => {
551                return Err(QuantRS2Error::UnsupportedOperation(format!(
552                    "Gates with {} qubits not yet supported",
553                    qubits.len()
554                )));
555            }
556        }
557
558        Ok(())
559    }
560
561    /// Get the matrix representation of a gate
562    fn get_gate_matrix(&self, gate: &dyn GateOp) -> QuantRS2Result<Array2<Complex64>> {
563        let c0 = Complex64::new(0.0, 0.0);
564        let c1 = Complex64::new(1.0, 0.0);
565        let ci = Complex64::new(0.0, 1.0);
566
567        match gate.name() {
568            "H" => {
569                let sqrt2_inv = 1.0 / std::f64::consts::SQRT_2;
570                Ok(array![
571                    [c1 * sqrt2_inv, c1 * sqrt2_inv],
572                    [c1 * sqrt2_inv, -c1 * sqrt2_inv]
573                ])
574            }
575            "X" => Ok(array![[c0, c1], [c1, c0]]),
576            "Y" => Ok(array![[c0, -ci], [ci, c0]]),
577            "Z" => Ok(array![[c1, c0], [c0, -c1]]),
578            "S" => Ok(array![[c1, c0], [c0, ci]]),
579            "T" => Ok(array![
580                [c1, c0],
581                [
582                    c0,
583                    Complex64::new(
584                        1.0 / std::f64::consts::SQRT_2,
585                        1.0 / std::f64::consts::SQRT_2
586                    )
587                ]
588            ]),
589            "CNOT" | "CX" => Ok(array![
590                [c1, c0, c0, c0],
591                [c0, c1, c0, c0],
592                [c0, c0, c0, c1],
593                [c0, c0, c1, c0]
594            ]),
595            "CZ" => Ok(array![
596                [c1, c0, c0, c0],
597                [c0, c1, c0, c0],
598                [c0, c0, c1, c0],
599                [c0, c0, c0, -c1]
600            ]),
601            "SWAP" => Ok(array![
602                [c1, c0, c0, c0],
603                [c0, c0, c1, c0],
604                [c0, c1, c0, c0],
605                [c0, c0, c0, c1]
606            ]),
607            _ => Err(QuantRS2Error::UnsupportedOperation(format!(
608                "Gate '{}' matrix not yet implemented",
609                gate.name()
610            ))),
611        }
612    }
613
614    /// Apply a single-qubit gate to a unitary matrix
615    fn apply_single_qubit_gate(
616        &self,
617        unitary: &mut Array2<Complex64>,
618        gate_matrix: &Array2<Complex64>,
619        qubit_idx: usize,
620        num_qubits: usize,
621    ) -> QuantRS2Result<()> {
622        let dim = 1 << num_qubits;
623        let mut new_unitary = Array2::zeros((dim, dim));
624
625        // Apply gate to each basis state
626        for col in 0..dim {
627            for row in 0..dim {
628                let mut sum = Complex64::new(0.0, 0.0);
629
630                // Check if this element should be affected by the gate
631                let row_bit = (row >> qubit_idx) & 1;
632                let col_bit = (col >> qubit_idx) & 1;
633
634                for k in 0..dim {
635                    let k_bit = (k >> qubit_idx) & 1;
636
637                    // Only mix states that differ in the target qubit
638                    if (row ^ k) == ((row_bit ^ k_bit) << qubit_idx) {
639                        sum += gate_matrix[[row_bit, k_bit]] * unitary[[k, col]];
640                    }
641                }
642
643                new_unitary[[row, col]] = sum;
644            }
645        }
646
647        *unitary = new_unitary;
648        Ok(())
649    }
650
651    /// Apply a two-qubit gate to a unitary matrix
652    fn apply_two_qubit_gate(
653        &self,
654        unitary: &mut Array2<Complex64>,
655        gate_matrix: &Array2<Complex64>,
656        qubit1_idx: usize,
657        qubit2_idx: usize,
658        num_qubits: usize,
659    ) -> QuantRS2Result<()> {
660        let dim = 1 << num_qubits;
661        let mut new_unitary = Array2::zeros((dim, dim));
662
663        for col in 0..dim {
664            for row in 0..dim {
665                let mut sum = Complex64::new(0.0, 0.0);
666
667                // Extract relevant qubit states
668                let row_q1 = (row >> qubit1_idx) & 1;
669                let row_q2 = (row >> qubit2_idx) & 1;
670                let row_gate_idx = (row_q1 << 1) | row_q2;
671
672                let col_q1 = (col >> qubit1_idx) & 1;
673                let col_q2 = (col >> qubit2_idx) & 1;
674
675                for k in 0..dim {
676                    let k_q1 = (k >> qubit1_idx) & 1;
677                    let k_q2 = (k >> qubit2_idx) & 1;
678                    let k_gate_idx = (k_q1 << 1) | k_q2;
679
680                    // Check if k differs from row only in the gate qubits
681                    let diff = row ^ k;
682                    let expected_diff =
683                        ((row_q1 ^ k_q1) << qubit1_idx) | ((row_q2 ^ k_q2) << qubit2_idx);
684
685                    if diff == expected_diff {
686                        sum += gate_matrix[[row_gate_idx, k_gate_idx]] * unitary[[k, col]];
687                    }
688                }
689
690                new_unitary[[row, col]] = sum;
691            }
692        }
693
694        *unitary = new_unitary;
695        Ok(())
696    }
697
698    /// Check if two unitaries are equal
699    fn unitaries_equal(&self, u1: &Array2<Complex64>, u2: &Array2<Complex64>) -> (bool, f64) {
700        if u1.shape() != u2.shape() {
701            return (false, f64::INFINITY);
702        }
703
704        let mut max_diff = 0.0;
705        for (a, b) in u1.iter().zip(u2.iter()) {
706            let diff = (a - b).norm();
707            if diff > max_diff {
708                max_diff = diff;
709            }
710            if diff > self.options.tolerance {
711                return (false, max_diff);
712            }
713        }
714
715        (true, max_diff)
716    }
717
718    /// Check if two unitaries are equal up to a global phase
719    fn unitaries_equal_up_to_phase(
720        &self,
721        u1: &Array2<Complex64>,
722        u2: &Array2<Complex64>,
723    ) -> (bool, f64) {
724        if u1.shape() != u2.shape() {
725            return (false, f64::INFINITY);
726        }
727
728        // Find the first non-zero element to determine phase
729        let mut phase = None;
730        for (a, b) in u1.iter().zip(u2.iter()) {
731            if a.norm() > self.options.tolerance && b.norm() > self.options.tolerance {
732                phase = Some(b / a);
733                break;
734            }
735        }
736
737        let phase = match phase {
738            Some(p) => p,
739            None => return (false, f64::INFINITY),
740        };
741
742        // Check all elements with phase adjustment
743        let mut max_diff = 0.0;
744        for (a, b) in u1.iter().zip(u2.iter()) {
745            let adjusted = a * phase;
746            let diff = (adjusted - b).norm();
747            if diff > max_diff {
748                max_diff = diff;
749            }
750            if diff > self.options.tolerance {
751                return (false, max_diff);
752            }
753        }
754
755        (true, max_diff)
756    }
757
758    /// Check state vector equivalence
759    pub fn check_state_vector_equivalence<const N: usize>(
760        &self,
761        circuit1: &Circuit<N>,
762        circuit2: &Circuit<N>,
763    ) -> QuantRS2Result<EquivalenceResult> {
764        let mut max_diff = 0.0;
765        let num_states = if self.options.check_all_states {
766            1 << N
767        } else {
768            // Check a subset of states for large circuits
769            std::cmp::min(1 << N, 100)
770        };
771
772        for state_idx in 0..num_states {
773            let state1 = self.apply_circuit_to_state(circuit1, state_idx, N)?;
774            let state2 = self.apply_circuit_to_state(circuit2, state_idx, N)?;
775
776            let (equal, diff) = if self.options.ignore_global_phase {
777                self.states_equal_up_to_phase(&state1, &state2)
778            } else {
779                self.states_equal(&state1, &state2)
780            };
781
782            if diff > max_diff {
783                max_diff = diff;
784            }
785
786            if !equal {
787                return Ok(EquivalenceResult {
788                    equivalent: false,
789                    check_type: EquivalenceType::StateVectorEquivalence,
790                    max_difference: Some(max_diff),
791                    details: format!(
792                        "States differ for input |{state_idx:0b}>: max difference {max_diff:.2e}"
793                    ),
794                    numerical_analysis: None,
795                    confidence_score: 0.0,
796                    statistical_significance: None,
797                    error_bounds: None,
798                });
799            }
800        }
801
802        Ok(EquivalenceResult {
803            equivalent: true,
804            check_type: EquivalenceType::StateVectorEquivalence,
805            max_difference: Some(max_diff),
806            details: format!("Checked {num_states} computational basis states"),
807            numerical_analysis: None,
808            confidence_score: 1.0 - (max_diff / self.options.tolerance).min(1.0),
809            statistical_significance: None,
810            error_bounds: None,
811        })
812    }
813
814    /// Apply circuit to a computational basis state
815    fn apply_circuit_to_state<const N: usize>(
816        &self,
817        circuit: &Circuit<N>,
818        state_idx: usize,
819        num_qubits: usize,
820    ) -> QuantRS2Result<Vec<Complex64>> {
821        let dim = 1 << num_qubits;
822        let mut state = vec![Complex64::new(0.0, 0.0); dim];
823        state[state_idx] = Complex64::new(1.0, 0.0);
824
825        // Apply each gate to the state vector
826        for gate in circuit.gates() {
827            self.apply_gate_to_state(&mut state, gate.as_ref(), num_qubits)?;
828        }
829
830        Ok(state)
831    }
832
833    /// Apply a gate to a state vector
834    fn apply_gate_to_state(
835        &self,
836        state: &mut Vec<Complex64>,
837        gate: &dyn GateOp,
838        num_qubits: usize,
839    ) -> QuantRS2Result<()> {
840        let gate_matrix = self.get_gate_matrix(gate)?;
841        let qubits = gate.qubits();
842
843        match qubits.len() {
844            1 => {
845                let qubit_idx = qubits[0].id() as usize;
846                self.apply_single_qubit_gate_to_state(state, &gate_matrix, qubit_idx, num_qubits)?;
847            }
848            2 => {
849                let control_idx = qubits[0].id() as usize;
850                let target_idx = qubits[1].id() as usize;
851                self.apply_two_qubit_gate_to_state(
852                    state,
853                    &gate_matrix,
854                    control_idx,
855                    target_idx,
856                    num_qubits,
857                )?;
858            }
859            _ => {
860                return Err(QuantRS2Error::UnsupportedOperation(format!(
861                    "Gates with {} qubits not yet supported",
862                    qubits.len()
863                )));
864            }
865        }
866
867        Ok(())
868    }
869
870    /// Apply a single-qubit gate to a state vector
871    fn apply_single_qubit_gate_to_state(
872        &self,
873        state: &mut Vec<Complex64>,
874        gate_matrix: &Array2<Complex64>,
875        qubit_idx: usize,
876        num_qubits: usize,
877    ) -> QuantRS2Result<()> {
878        let dim = 1 << num_qubits;
879        let mut new_state = vec![Complex64::new(0.0, 0.0); dim];
880
881        for i in 0..dim {
882            let bit = (i >> qubit_idx) & 1;
883
884            for j in 0..2 {
885                let other_idx = i ^ ((bit ^ j) << qubit_idx);
886                new_state[i] += gate_matrix[[bit, j]] * state[other_idx];
887            }
888        }
889
890        *state = new_state;
891        Ok(())
892    }
893
894    /// Apply a two-qubit gate to a state vector
895    fn apply_two_qubit_gate_to_state(
896        &self,
897        state: &mut Vec<Complex64>,
898        gate_matrix: &Array2<Complex64>,
899        qubit1_idx: usize,
900        qubit2_idx: usize,
901        num_qubits: usize,
902    ) -> QuantRS2Result<()> {
903        let dim = 1 << num_qubits;
904        let mut new_state = vec![Complex64::new(0.0, 0.0); dim];
905
906        for i in 0..dim {
907            let bit1 = (i >> qubit1_idx) & 1;
908            let bit2 = (i >> qubit2_idx) & 1;
909            let gate_row = (bit1 << 1) | bit2;
910
911            for gate_col in 0..4 {
912                let new_bit1 = (gate_col >> 1) & 1;
913                let new_bit2 = gate_col & 1;
914
915                let j = i ^ ((bit1 ^ new_bit1) << qubit1_idx) ^ ((bit2 ^ new_bit2) << qubit2_idx);
916                new_state[i] += gate_matrix[[gate_row, gate_col]] * state[j];
917            }
918        }
919
920        *state = new_state;
921        Ok(())
922    }
923
924    /// Check if two state vectors are equal
925    fn states_equal(&self, s1: &[Complex64], s2: &[Complex64]) -> (bool, f64) {
926        if s1.len() != s2.len() {
927            return (false, f64::INFINITY);
928        }
929
930        let mut max_diff = 0.0;
931        for (a, b) in s1.iter().zip(s2.iter()) {
932            let diff = (a - b).norm();
933            if diff > max_diff {
934                max_diff = diff;
935            }
936            if diff > self.options.tolerance {
937                return (false, max_diff);
938            }
939        }
940
941        (true, max_diff)
942    }
943
944    /// Check if two state vectors are equal up to a global phase
945    fn states_equal_up_to_phase(&self, s1: &[Complex64], s2: &[Complex64]) -> (bool, f64) {
946        if s1.len() != s2.len() {
947            return (false, f64::INFINITY);
948        }
949
950        // Find phase from first non-zero element
951        let mut phase = None;
952        for (a, b) in s1.iter().zip(s2.iter()) {
953            if a.norm() > self.options.tolerance && b.norm() > self.options.tolerance {
954                phase = Some(b / a);
955                break;
956            }
957        }
958
959        let phase = match phase {
960            Some(p) => p,
961            None => return (false, f64::INFINITY),
962        };
963
964        // Check all elements with phase adjustment
965        let mut max_diff = 0.0;
966        for (a, b) in s1.iter().zip(s2.iter()) {
967            let adjusted = a * phase;
968            let diff = (adjusted - b).norm();
969            if diff > max_diff {
970                max_diff = diff;
971            }
972            if diff > self.options.tolerance {
973                return (false, max_diff);
974            }
975        }
976
977        (true, max_diff)
978    }
979
980    /// Check probabilistic equivalence (measurement outcomes)
981    pub fn check_probabilistic_equivalence<const N: usize>(
982        &self,
983        circuit1: &Circuit<N>,
984        circuit2: &Circuit<N>,
985    ) -> QuantRS2Result<EquivalenceResult> {
986        // For each computational basis state, check measurement probabilities
987        let mut max_diff = 0.0;
988
989        for state_idx in 0..(1 << N) {
990            let probs1 = self.get_measurement_probabilities(circuit1, state_idx, N)?;
991            let probs2 = self.get_measurement_probabilities(circuit2, state_idx, N)?;
992
993            for (p1, p2) in probs1.iter().zip(probs2.iter()) {
994                let diff = (p1 - p2).abs();
995                if diff > max_diff {
996                    max_diff = diff;
997                }
998                if diff > self.options.tolerance {
999                    return Ok(EquivalenceResult {
1000                        equivalent: false,
1001                        check_type: EquivalenceType::ProbabilisticEquivalence,
1002                        max_difference: Some(max_diff),
1003                        details: format!(
1004                            "Measurement probabilities differ for input |{state_idx:0b}>"
1005                        ),
1006                        numerical_analysis: None,
1007                        confidence_score: 0.0,
1008                        statistical_significance: None,
1009                        error_bounds: None,
1010                    });
1011                }
1012            }
1013        }
1014
1015        Ok(EquivalenceResult {
1016            equivalent: true,
1017            check_type: EquivalenceType::ProbabilisticEquivalence,
1018            max_difference: Some(max_diff),
1019            details: "Measurement probabilities match for all inputs".to_string(),
1020            numerical_analysis: None,
1021            confidence_score: 1.0 - (max_diff / self.options.tolerance).min(1.0),
1022            statistical_significance: None,
1023            error_bounds: None,
1024        })
1025    }
1026
1027    /// Get measurement probabilities for a circuit and input state
1028    fn get_measurement_probabilities<const N: usize>(
1029        &self,
1030        circuit: &Circuit<N>,
1031        state_idx: usize,
1032        num_qubits: usize,
1033    ) -> QuantRS2Result<Vec<f64>> {
1034        // Apply circuit to get final state
1035        let final_state = self.apply_circuit_to_state(circuit, state_idx, num_qubits)?;
1036
1037        // Calculate probabilities from amplitudes
1038        let probs: Vec<f64> = final_state
1039            .iter()
1040            .map(scirs2_core::Complex::norm_sqr)
1041            .collect();
1042
1043        Ok(probs)
1044    }
1045
1046    // ===== SciRS2 Enhanced Methods =====
1047
1048    /// Perform comprehensive numerical analysis using `SciRS2` capabilities
1049    fn perform_scirs2_numerical_analysis(
1050        &self,
1051        unitary1: &Array2<Complex64>,
1052        unitary2: &Array2<Complex64>,
1053    ) -> QuantRS2Result<NumericalAnalysis> {
1054        // Calculate difference matrix
1055        let diff_matrix = unitary1 - unitary2;
1056
1057        // Calculate Frobenius norm
1058        let frobenius_norm = diff_matrix
1059            .iter()
1060            .map(scirs2_core::Complex::norm_sqr)
1061            .sum::<f64>()
1062            .sqrt();
1063
1064        // Calculate condition number using SVD approximation
1065        let condition_number = if self.options.enable_stability_analysis {
1066            Some(self.estimate_condition_number(unitary1)?)
1067        } else {
1068            None
1069        };
1070
1071        // Calculate spectral norm (largest singular value of difference)
1072        let spectral_norm = if self.options.enable_stability_analysis {
1073            Some(self.calculate_spectral_norm(&diff_matrix)?)
1074        } else {
1075            None
1076        };
1077
1078        // Estimate numerical rank
1079        let numerical_rank = self.estimate_numerical_rank(&diff_matrix);
1080
1081        // Calculate stability indicator
1082        let stability_indicator = if let Some(cond_num) = condition_number {
1083            1.0 / (1.0 + (cond_num / self.options.max_condition_number).log10())
1084        } else {
1085            1.0
1086        };
1087
1088        // Calculate adaptive tolerance
1089        let adaptive_tolerance = self.calculate_adaptive_tolerance_internal(
1090            unitary1.nrows(),
1091            frobenius_norm,
1092            condition_number.unwrap_or(1.0),
1093        );
1094
1095        Ok(NumericalAnalysis {
1096            condition_number,
1097            numerical_rank: Some(numerical_rank),
1098            frobenius_norm,
1099            spectral_norm,
1100            adaptive_tolerance,
1101            stability_indicator,
1102        })
1103    }
1104
1105    /// Calculate adaptive tolerance based on circuit complexity and numerical properties
1106    fn calculate_adaptive_tolerance<const N: usize>(
1107        &self,
1108        num_qubits: usize,
1109        analysis: &NumericalAnalysis,
1110    ) -> f64 {
1111        let base_tolerance = if self.options.enable_adaptive_tolerance {
1112            SCIRS2_DEFAULT_TOLERANCE
1113        } else {
1114            self.options.tolerance
1115        };
1116
1117        // Scale tolerance based on circuit size (more qubits = less precision)
1118        let size_factor = (num_qubits as f64).powf(1.5).mul_add(1e-15, 1.0);
1119
1120        // Scale based on condition number
1121        let condition_factor = if let Some(cond_num) = analysis.condition_number {
1122            (cond_num / 1e12).log10().max(0.0).mul_add(1e-2, 1.0)
1123        } else {
1124            1.0
1125        };
1126
1127        // Scale based on Frobenius norm
1128        let norm_factor = analysis.frobenius_norm.mul_add(1e-3, 1.0);
1129
1130        base_tolerance * size_factor * condition_factor * norm_factor
1131    }
1132
1133    /// Internal helper for adaptive tolerance calculation
1134    fn calculate_adaptive_tolerance_internal(
1135        &self,
1136        matrix_size: usize,
1137        frobenius_norm: f64,
1138        condition_number: f64,
1139    ) -> f64 {
1140        let base_tolerance = SCIRS2_DEFAULT_TOLERANCE;
1141        let size_factor = (matrix_size as f64).sqrt().mul_add(1e-15, 1.0);
1142        let condition_factor = (condition_number / 1e12)
1143            .log10()
1144            .max(0.0)
1145            .mul_add(1e-2, 1.0);
1146        let norm_factor = frobenius_norm.mul_add(1e-3, 1.0);
1147
1148        base_tolerance * size_factor * condition_factor * norm_factor
1149    }
1150
1151    /// Compare unitaries using `SciRS2` enhanced numerical analysis
1152    fn scirs2_unitaries_equal(
1153        &self,
1154        u1: &Array2<Complex64>,
1155        u2: &Array2<Complex64>,
1156        adaptive_tolerance: f64,
1157    ) -> QuantRS2Result<(bool, f64, f64, ErrorBounds)> {
1158        if u1.shape() != u2.shape() {
1159            return Ok((
1160                false,
1161                f64::INFINITY,
1162                0.0,
1163                ErrorBounds {
1164                    lower_bound: f64::INFINITY,
1165                    upper_bound: f64::INFINITY,
1166                    confidence_level: 0.0,
1167                    standard_deviation: None,
1168                },
1169            ));
1170        }
1171
1172        let mut max_diff = 0.0;
1173        let mut differences = Vec::new();
1174
1175        // Calculate element-wise differences
1176        for (a, b) in u1.iter().zip(u2.iter()) {
1177            let diff = if self.options.ignore_global_phase {
1178                // Find global phase from first non-zero element
1179                let phase = if a.norm() > adaptive_tolerance && b.norm() > adaptive_tolerance {
1180                    b / a
1181                } else {
1182                    Complex64::new(1.0, 0.0)
1183                };
1184                (a * phase - b).norm()
1185            } else {
1186                (a - b).norm()
1187            };
1188
1189            differences.push(diff);
1190            if diff > max_diff {
1191                max_diff = diff;
1192            }
1193        }
1194
1195        // Calculate statistical measures
1196        let mean_diff = differences.iter().sum::<f64>() / differences.len() as f64;
1197        let variance = differences
1198            .iter()
1199            .map(|d| (d - mean_diff).powi(2))
1200            .sum::<f64>()
1201            / differences.len() as f64;
1202        let std_dev = variance.sqrt();
1203
1204        // Calculate confidence score based on how well differences fit expected distribution
1205        let confidence_score = if max_diff <= adaptive_tolerance {
1206            1.0 - (max_diff / adaptive_tolerance).min(1.0)
1207        } else {
1208            0.0
1209        };
1210
1211        // Calculate error bounds
1212        let error_bounds = ErrorBounds {
1213            lower_bound: 2.0f64.mul_add(-std_dev, mean_diff).max(0.0),
1214            upper_bound: 2.0f64.mul_add(std_dev, mean_diff),
1215            confidence_level: self.options.confidence_level,
1216            standard_deviation: Some(std_dev),
1217        };
1218
1219        let equivalent = max_diff <= adaptive_tolerance;
1220
1221        Ok((equivalent, max_diff, confidence_score, error_bounds))
1222    }
1223
1224    /// Compare `SciRS2` graphs for structural equivalence
1225    fn compare_scirs2_graphs(
1226        &self,
1227        graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
1228        graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
1229    ) -> QuantRS2Result<(bool, f64, String)> {
1230        // Basic structural comparison
1231        if graph1.nodes.len() != graph2.nodes.len() {
1232            return Ok((
1233                false,
1234                0.0,
1235                format!(
1236                    "Different number of nodes: {} vs {}",
1237                    graph1.nodes.len(),
1238                    graph2.nodes.len()
1239                ),
1240            ));
1241        }
1242
1243        if graph1.edges.len() != graph2.edges.len() {
1244            return Ok((
1245                false,
1246                0.0,
1247                format!(
1248                    "Different number of edges: {} vs {}",
1249                    graph1.edges.len(),
1250                    graph2.edges.len()
1251                ),
1252            ));
1253        }
1254
1255        // Calculate graph similarity metrics
1256        let node_similarity = self.calculate_node_similarity(graph1, graph2);
1257        let edge_similarity = self.calculate_edge_similarity(graph1, graph2);
1258        let topology_similarity = self.calculate_topology_similarity(graph1, graph2);
1259
1260        // Combined similarity score
1261        let overall_similarity = (node_similarity + edge_similarity + topology_similarity) / 3.0;
1262
1263        let equivalent = overall_similarity > 0.95; // 95% similarity threshold
1264
1265        let details = format!(
1266            "Graph similarity analysis: nodes={node_similarity:.3}, edges={edge_similarity:.3}, topology={topology_similarity:.3}, overall={overall_similarity:.3}"
1267        );
1268
1269        Ok((equivalent, overall_similarity, details))
1270    }
1271
1272    /// Calculate node similarity between graphs
1273    fn calculate_node_similarity(
1274        &self,
1275        graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
1276        graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
1277    ) -> f64 {
1278        if graph1.nodes.is_empty() && graph2.nodes.is_empty() {
1279            return 1.0;
1280        }
1281
1282        let total_nodes = graph1.nodes.len().max(graph2.nodes.len());
1283        let mut matching_nodes = 0;
1284
1285        // Simple node type matching (could be enhanced with graph isomorphism)
1286        for node1 in graph1.nodes.values() {
1287            for node2 in graph2.nodes.values() {
1288                if node1.node_type == node2.node_type {
1289                    matching_nodes += 1;
1290                    break;
1291                }
1292            }
1293        }
1294
1295        f64::from(matching_nodes) / total_nodes as f64
1296    }
1297
1298    /// Calculate edge similarity between graphs
1299    fn calculate_edge_similarity(
1300        &self,
1301        graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
1302        graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
1303    ) -> f64 {
1304        if graph1.edges.is_empty() && graph2.edges.is_empty() {
1305            return 1.0;
1306        }
1307
1308        let total_edges = graph1.edges.len().max(graph2.edges.len());
1309        let mut matching_edges = 0;
1310
1311        // Simple edge type matching
1312        for edge1 in graph1.edges.values() {
1313            for edge2 in graph2.edges.values() {
1314                if edge1.edge_type == edge2.edge_type {
1315                    matching_edges += 1;
1316                    break;
1317                }
1318            }
1319        }
1320
1321        f64::from(matching_edges) / total_edges as f64
1322    }
1323
1324    /// Calculate topology similarity using adjacency matrix comparison
1325    fn calculate_topology_similarity(
1326        &self,
1327        graph1: &crate::scirs2_integration::SciRS2CircuitGraph,
1328        graph2: &crate::scirs2_integration::SciRS2CircuitGraph,
1329    ) -> f64 {
1330        if graph1.adjacency_matrix.len() != graph2.adjacency_matrix.len() {
1331            return 0.0;
1332        }
1333
1334        let mut total_elements = 0;
1335        let mut matching_elements = 0;
1336
1337        for (row1, row2) in graph1
1338            .adjacency_matrix
1339            .iter()
1340            .zip(graph2.adjacency_matrix.iter())
1341        {
1342            if row1.len() != row2.len() {
1343                return 0.0;
1344            }
1345
1346            for (elem1, elem2) in row1.iter().zip(row2.iter()) {
1347                total_elements += 1;
1348                if elem1 == elem2 {
1349                    matching_elements += 1;
1350                }
1351            }
1352        }
1353
1354        if total_elements == 0 {
1355            1.0
1356        } else {
1357            f64::from(matching_elements) / f64::from(total_elements)
1358        }
1359    }
1360
1361    /// Estimate condition number using power iteration method
1362    fn estimate_condition_number(&self, matrix: &Array2<Complex64>) -> QuantRS2Result<f64> {
1363        // Simplified condition number estimation
1364        // In practice, this would use more sophisticated SVD methods
1365        let n = matrix.nrows();
1366        if n == 0 {
1367            return Ok(1.0);
1368        }
1369
1370        // Estimate largest singular value using power iteration
1371        let mut v = vec![Complex64::new(1.0, 0.0); n];
1372        for _ in 0..10 {
1373            // v = A^H * A * v
1374            let mut new_v = vec![Complex64::new(0.0, 0.0); n];
1375            for i in 0..n {
1376                for j in 0..n {
1377                    for k in 0..n {
1378                        new_v[i] += matrix[[k, i]].conj() * matrix[[k, j]] * v[j];
1379                    }
1380                }
1381            }
1382
1383            // Normalize
1384            let norm = new_v
1385                .iter()
1386                .map(scirs2_core::Complex::norm_sqr)
1387                .sum::<f64>()
1388                .sqrt();
1389            if norm > 0.0 {
1390                for x in &mut new_v {
1391                    *x /= norm;
1392                }
1393            }
1394            v = new_v;
1395        }
1396
1397        // Estimate condition number (simplified)
1398        let estimated_largest_sv = v.iter().map(|x| x.norm()).sum::<f64>() / n as f64;
1399        let estimated_smallest_sv = 1.0 / estimated_largest_sv; // Very simplified
1400
1401        Ok((estimated_largest_sv / estimated_smallest_sv.max(1e-16)).min(1e16))
1402    }
1403
1404    /// Calculate spectral norm (largest singular value) of a matrix
1405    fn calculate_spectral_norm(&self, matrix: &Array2<Complex64>) -> QuantRS2Result<f64> {
1406        // Simplified spectral norm calculation
1407        // In practice, this would use proper SVD decomposition
1408        Ok(matrix.iter().map(|x| x.norm()).fold(0.0, f64::max))
1409    }
1410
1411    /// Estimate numerical rank of a matrix
1412    fn estimate_numerical_rank(&self, matrix: &Array2<Complex64>) -> usize {
1413        let tolerance = self.options.tolerance;
1414        let mut rank = 0;
1415
1416        for row in matrix.rows() {
1417            let row_norm = row
1418                .iter()
1419                .map(scirs2_core::Complex::norm_sqr)
1420                .sum::<f64>()
1421                .sqrt();
1422            if row_norm > tolerance {
1423                rank += 1;
1424            }
1425        }
1426
1427        rank
1428    }
1429
1430    /// Calculate statistical significance of the difference
1431    fn calculate_statistical_significance(
1432        &self,
1433        u1: &Array2<Complex64>,
1434        u2: &Array2<Complex64>,
1435        max_difference: f64,
1436    ) -> QuantRS2Result<f64> {
1437        // Simplified statistical test
1438        // In practice, this would use more sophisticated statistical methods
1439        let n = u1.len();
1440        let degrees_of_freedom = n - 1;
1441
1442        // Calculate t-statistic approximation
1443        let differences: Vec<f64> = u1
1444            .iter()
1445            .zip(u2.iter())
1446            .map(|(a, b)| (a - b).norm())
1447            .collect();
1448
1449        let mean_diff = differences.iter().sum::<f64>() / n as f64;
1450        let variance = differences
1451            .iter()
1452            .map(|d| (d - mean_diff).powi(2))
1453            .sum::<f64>()
1454            / degrees_of_freedom as f64;
1455        let std_error = (variance / n as f64).sqrt();
1456
1457        let t_stat = if std_error > 0.0 {
1458            mean_diff / std_error
1459        } else {
1460            0.0
1461        };
1462
1463        // Approximate p-value (very simplified)
1464        let p_value = 2.0 * (1.0 - (t_stat.abs() / (1.0 + t_stat.abs())));
1465
1466        Ok(p_value.clamp(0.0, 1.0))
1467    }
1468}
1469
1470/// Quick check if two circuits are structurally identical
1471#[must_use]
1472pub fn circuits_structurally_equal<const N: usize>(
1473    circuit1: &Circuit<N>,
1474    circuit2: &Circuit<N>,
1475) -> bool {
1476    let checker = EquivalenceChecker::default();
1477    checker
1478        .check_structural_equivalence(circuit1, circuit2)
1479        .map(|result| result.equivalent)
1480        .unwrap_or(false)
1481}
1482
1483/// Quick check if two circuits are equivalent (using default options)
1484pub fn circuits_equivalent<const N: usize>(
1485    circuit1: &Circuit<N>,
1486    circuit2: &Circuit<N>,
1487) -> QuantRS2Result<bool> {
1488    let mut checker = EquivalenceChecker::default();
1489    Ok(checker.check_equivalence(circuit1, circuit2)?.equivalent)
1490}
1491
1492/// Check equivalence using `SciRS2` numerical analysis with custom tolerance
1493pub fn circuits_scirs2_equivalent<const N: usize>(
1494    circuit1: &Circuit<N>,
1495    circuit2: &Circuit<N>,
1496    options: EquivalenceOptions,
1497) -> QuantRS2Result<EquivalenceResult> {
1498    let mut checker = EquivalenceChecker::new(options);
1499    checker.check_equivalence(circuit1, circuit2)
1500}
1501
1502/// Quick `SciRS2` numerical equivalence check with default enhanced options
1503pub fn circuits_scirs2_numerical_equivalent<const N: usize>(
1504    circuit1: &Circuit<N>,
1505    circuit2: &Circuit<N>,
1506) -> QuantRS2Result<EquivalenceResult> {
1507    let options = EquivalenceOptions {
1508        enable_adaptive_tolerance: true,
1509        enable_statistical_analysis: true,
1510        enable_stability_analysis: true,
1511        ..Default::default()
1512    };
1513
1514    let mut checker = EquivalenceChecker::new(options);
1515    checker.check_scirs2_numerical_equivalence(circuit1, circuit2)
1516}
1517
1518#[cfg(test)]
1519mod tests {
1520    use super::*;
1521    use quantrs2_core::gate::multi::CNOT;
1522    use quantrs2_core::gate::single::{Hadamard, PauliX, PauliZ};
1523
1524    #[test]
1525    fn test_structural_equivalence() {
1526        let mut circuit1 = Circuit::<2>::new();
1527        circuit1
1528            .add_gate(Hadamard { target: QubitId(0) })
1529            .expect("Failed to add Hadamard gate to circuit1");
1530        circuit1
1531            .add_gate(CNOT {
1532                control: QubitId(0),
1533                target: QubitId(1),
1534            })
1535            .expect("Failed to add CNOT gate to circuit1");
1536
1537        let mut circuit2 = Circuit::<2>::new();
1538        circuit2
1539            .add_gate(Hadamard { target: QubitId(0) })
1540            .expect("Failed to add Hadamard gate to circuit2");
1541        circuit2
1542            .add_gate(CNOT {
1543                control: QubitId(0),
1544                target: QubitId(1),
1545            })
1546            .expect("Failed to add CNOT gate to circuit2");
1547
1548        let checker = EquivalenceChecker::default();
1549        let result = checker
1550            .check_structural_equivalence(&circuit1, &circuit2)
1551            .expect("Structural equivalence check failed");
1552        assert!(result.equivalent);
1553    }
1554
1555    #[test]
1556    fn test_structural_non_equivalence() {
1557        let mut circuit1 = Circuit::<2>::new();
1558        circuit1
1559            .add_gate(Hadamard { target: QubitId(0) })
1560            .expect("Failed to add Hadamard gate to circuit1");
1561        circuit1
1562            .add_gate(CNOT {
1563                control: QubitId(0),
1564                target: QubitId(1),
1565            })
1566            .expect("Failed to add CNOT gate to circuit1");
1567
1568        let mut circuit2 = Circuit::<2>::new();
1569        circuit2
1570            .add_gate(PauliX { target: QubitId(0) })
1571            .expect("Failed to add PauliX gate to circuit2");
1572        circuit2
1573            .add_gate(CNOT {
1574                control: QubitId(0),
1575                target: QubitId(1),
1576            })
1577            .expect("Failed to add CNOT gate to circuit2");
1578
1579        let checker = EquivalenceChecker::default();
1580        let result = checker
1581            .check_structural_equivalence(&circuit1, &circuit2)
1582            .expect("Structural equivalence check failed");
1583        assert!(!result.equivalent);
1584    }
1585
1586    #[test]
1587    fn test_different_gate_count() {
1588        let mut circuit1 = Circuit::<2>::new();
1589        circuit1
1590            .add_gate(Hadamard { target: QubitId(0) })
1591            .expect("Failed to add Hadamard gate to circuit1");
1592
1593        let mut circuit2 = Circuit::<2>::new();
1594        circuit2
1595            .add_gate(Hadamard { target: QubitId(0) })
1596            .expect("Failed to add Hadamard gate to circuit2");
1597        circuit2
1598            .add_gate(PauliZ { target: QubitId(0) })
1599            .expect("Failed to add PauliZ gate to circuit2");
1600
1601        let checker = EquivalenceChecker::default();
1602        let result = checker
1603            .check_structural_equivalence(&circuit1, &circuit2)
1604            .expect("Structural equivalence check failed");
1605        assert!(!result.equivalent);
1606        assert!(result.details.contains("Different number of gates"));
1607    }
1608
1609    #[test]
1610    fn test_parametric_gate_equivalence_equal() {
1611        // Test that rotation gates with same parameters are considered equal
1612        let mut circuit1 = Circuit::<1>::new();
1613        circuit1
1614            .add_gate(RotationX {
1615                target: QubitId(0),
1616                theta: std::f64::consts::PI / 4.0,
1617            })
1618            .expect("Failed to add RotationX gate to circuit1");
1619
1620        let mut circuit2 = Circuit::<1>::new();
1621        circuit2
1622            .add_gate(RotationX {
1623                target: QubitId(0),
1624                theta: std::f64::consts::PI / 4.0,
1625            })
1626            .expect("Failed to add RotationX gate to circuit2");
1627
1628        let checker = EquivalenceChecker::default();
1629        let result = checker
1630            .check_structural_equivalence(&circuit1, &circuit2)
1631            .expect("Structural equivalence check failed");
1632        assert!(result.equivalent);
1633    }
1634
1635    #[test]
1636    fn test_parametric_gate_equivalence_different_params() {
1637        // Test that rotation gates with different parameters are not equal
1638        let mut circuit1 = Circuit::<1>::new();
1639        circuit1
1640            .add_gate(RotationX {
1641                target: QubitId(0),
1642                theta: std::f64::consts::PI / 4.0,
1643            })
1644            .expect("Failed to add RotationX gate to circuit1");
1645
1646        let mut circuit2 = Circuit::<1>::new();
1647        circuit2
1648            .add_gate(RotationX {
1649                target: QubitId(0),
1650                theta: std::f64::consts::PI / 2.0,
1651            })
1652            .expect("Failed to add RotationX gate to circuit2");
1653
1654        let checker = EquivalenceChecker::default();
1655        let result = checker
1656            .check_structural_equivalence(&circuit1, &circuit2)
1657            .expect("Structural equivalence check failed");
1658        assert!(!result.equivalent);
1659    }
1660
1661    #[test]
1662    fn test_parametric_gate_numerical_tolerance() {
1663        // Test that rotation gates within numerical tolerance are considered equal
1664        let mut circuit1 = Circuit::<1>::new();
1665        circuit1
1666            .add_gate(RotationY {
1667                target: QubitId(0),
1668                theta: 1.0,
1669            })
1670            .expect("Failed to add RotationY gate to circuit1");
1671
1672        let mut circuit2 = Circuit::<1>::new();
1673        circuit2
1674            .add_gate(RotationY {
1675                target: QubitId(0),
1676                theta: 1.0 + 1e-12, // Within default tolerance
1677            })
1678            .expect("Failed to add RotationY gate to circuit2");
1679
1680        let checker = EquivalenceChecker::default();
1681        let result = checker
1682            .check_structural_equivalence(&circuit1, &circuit2)
1683            .expect("Structural equivalence check failed");
1684        assert!(result.equivalent);
1685    }
1686
1687    #[test]
1688    fn test_controlled_rotation_equivalence() {
1689        // Test controlled rotation gate parameter checking
1690        let mut circuit1 = Circuit::<2>::new();
1691        circuit1
1692            .add_gate(CRZ {
1693                control: QubitId(0),
1694                target: QubitId(1),
1695                theta: std::f64::consts::PI,
1696            })
1697            .expect("Failed to add CRZ gate to circuit1");
1698
1699        let mut circuit2 = Circuit::<2>::new();
1700        circuit2
1701            .add_gate(CRZ {
1702                control: QubitId(0),
1703                target: QubitId(1),
1704                theta: std::f64::consts::PI,
1705            })
1706            .expect("Failed to add CRZ gate to circuit2");
1707
1708        let checker = EquivalenceChecker::default();
1709        let result = checker
1710            .check_structural_equivalence(&circuit1, &circuit2)
1711            .expect("Structural equivalence check failed");
1712        assert!(result.equivalent);
1713    }
1714}