Skip to main content

quantrs2_circuit/scirs2_matrices/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::builder::Circuit;
6use quantrs2_core::{
7    buffer_pool::BufferPool,
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::GateOp,
10    qubit::QubitId,
11};
12pub use scirs2_core::Complex64;
13use scirs2_core::{
14    parallel_ops::{IndexedParallelIterator, ParallelIterator},
15    simd_ops::*,
16};
17use std::collections::HashMap;
18use std::sync::Arc;
19use std::time::Instant;
20
21pub struct BLAS;
22impl BLAS {
23    /// Check whether two sparse matrices are approximately equal entry-wise within `tol`.
24    /// For each entry present in either matrix the corresponding value in the other is
25    /// treated as zero when absent, and the element-wise difference must satisfy |diff| ≤ tol.
26    #[must_use]
27    pub fn matrix_approx_equal(
28        a: &SciRSSparseMatrix<Complex64>,
29        b: &SciRSSparseMatrix<Complex64>,
30        tol: f64,
31    ) -> bool {
32        if a.shape != b.shape {
33            return false;
34        }
35        let mut b_map: HashMap<(usize, usize), Complex64> = HashMap::with_capacity(b.data.len());
36        for &(r, c, v) in &b.data {
37            b_map.insert((r, c), v);
38        }
39        for &(r, c, va) in &a.data {
40            let vb = b_map.remove(&(r, c)).unwrap_or(Complex64::new(0.0, 0.0));
41            if (va - vb).norm() > tol {
42                return false;
43            }
44        }
45        for (_, vb) in b_map {
46            if vb.norm() > tol {
47                return false;
48            }
49        }
50        true
51    }
52    /// 2-norm condition number `σ_max / σ_min` from the singular values.
53    /// Returns `f64::INFINITY` for a singular (or empty) matrix.
54    #[must_use]
55    pub fn condition_number(matrix: &SciRSSparseMatrix<Complex64>) -> f64 {
56        let (dense, rows, cols) = densify(matrix);
57        if rows == 0 || cols == 0 {
58            return f64::INFINITY;
59        }
60        let sv = singular_values_dense(&dense, rows, cols);
61        let smax = sv.first().copied().unwrap_or(0.0);
62        let smin = sv.last().copied().unwrap_or(0.0);
63        if smax == 0.0 || smin <= smax * 1e-15 {
64            f64::INFINITY
65        } else {
66            smax / smin
67        }
68    }
69    #[must_use]
70    pub fn is_symmetric(matrix: &SciRSSparseMatrix<Complex64>, tol: f64) -> bool {
71        if matrix.shape.0 != matrix.shape.1 {
72            return false;
73        }
74        for (row, col, value) in &matrix.data {
75            let transpose_entry = matrix
76                .data
77                .iter()
78                .find(|(r, c, _)| *r == *col && *c == *row);
79            match transpose_entry {
80                Some((_, _, transpose_value)) => {
81                    if (value - transpose_value).norm() > tol {
82                        return false;
83                    }
84                }
85                None => {
86                    if value.norm() > tol {
87                        return false;
88                    }
89                }
90            }
91        }
92        true
93    }
94    #[must_use]
95    pub fn is_hermitian(matrix: &SciRSSparseMatrix<Complex64>, tol: f64) -> bool {
96        if matrix.shape.0 != matrix.shape.1 {
97            return false;
98        }
99        for (row, col, value) in &matrix.data {
100            let conj_transpose_entry = matrix
101                .data
102                .iter()
103                .find(|(r, c, _)| *r == *col && *c == *row);
104            match conj_transpose_entry {
105                Some((_, _, conj_transpose_value)) => {
106                    if (value - conj_transpose_value.conj()).norm() > tol {
107                        return false;
108                    }
109                }
110                None => {
111                    if value.norm() > tol {
112                        return false;
113                    }
114                }
115            }
116        }
117        true
118    }
119    /// A matrix is positive definite iff it is Hermitian and every eigenvalue is
120    /// strictly positive.
121    #[must_use]
122    pub fn is_positive_definite(matrix: &SciRSSparseMatrix<Complex64>) -> bool {
123        if !Self::is_hermitian(matrix, 1e-12) {
124            return false;
125        }
126        let (dense, rows, cols) = densify(matrix);
127        if rows == 0 || rows != cols {
128            return false;
129        }
130        hermitian_eigenvalues_dense(&dense, rows)
131            .iter()
132            .all(|&e| e > 1e-12)
133    }
134    /// Matrix norm computed from the actual entries.  Supported `norm_type`
135    /// values: `"1"`/`"one"` (max column sum), `"inf"`/`"infinity"` (max row
136    /// sum), `"max"` (largest magnitude entry), `"2"`/`"spectral"` (largest
137    /// singular value); any other value yields the Frobenius norm.
138    #[must_use]
139    pub fn matrix_norm(matrix: &SciRSSparseMatrix<Complex64>, norm_type: &str) -> f64 {
140        match norm_type {
141            "1" | "one" | "L1" => {
142                let mut col_sums: HashMap<usize, f64> = HashMap::new();
143                for &(_, c, v) in &matrix.data {
144                    *col_sums.entry(c).or_insert(0.0) += v.norm();
145                }
146                col_sums.values().copied().fold(0.0, f64::max)
147            }
148            "inf" | "infinity" | "Linf" => {
149                let mut row_sums: HashMap<usize, f64> = HashMap::new();
150                for &(r, _, v) in &matrix.data {
151                    *row_sums.entry(r).or_insert(0.0) += v.norm();
152                }
153                row_sums.values().copied().fold(0.0, f64::max)
154            }
155            "max" => matrix
156                .data
157                .iter()
158                .map(|(_, _, v)| v.norm())
159                .fold(0.0, f64::max),
160            "2" | "spectral" => {
161                let (dense, rows, cols) = densify(matrix);
162                singular_values_dense(&dense, rows, cols)
163                    .first()
164                    .copied()
165                    .unwrap_or(0.0)
166            }
167            _ => matrix
168                .data
169                .iter()
170                .map(|(_, _, v)| v.norm_sqr())
171                .sum::<f64>()
172                .sqrt(),
173        }
174    }
175    /// Numerical rank: the number of singular values above `tol` (with a relative
176    /// safety floor scaled by the largest singular value and machine epsilon).
177    #[must_use]
178    pub fn numerical_rank(matrix: &SciRSSparseMatrix<Complex64>, tol: f64) -> usize {
179        let (dense, rows, cols) = densify(matrix);
180        if rows == 0 || cols == 0 {
181            return 0;
182        }
183        let sv = singular_values_dense(&dense, rows, cols);
184        let smax = sv.first().copied().unwrap_or(0.0);
185        let threshold = tol.max(smax * (rows.max(cols) as f64) * f64::EPSILON);
186        sv.iter().filter(|&&s| s > threshold).count()
187    }
188    /// Spectral radius (largest eigenvalue magnitude, via power iteration) and the
189    /// eigenvalue-magnitude spread `max|λ| − min|λ|` (min via inverse iteration).
190    #[must_use]
191    pub fn spectral_analysis(matrix: &SciRSSparseMatrix<Complex64>) -> SpectralAnalysis {
192        let (dense, rows, cols) = densify(matrix);
193        if rows == 0 || rows != cols {
194            return SpectralAnalysis {
195                spectral_radius: 0.0,
196                eigenvalue_spread: 0.0,
197            };
198        }
199        let radius = spectral_radius_dense(&dense, rows);
200        let min_mag = min_eig_magnitude_dense(&dense, rows);
201        SpectralAnalysis {
202            spectral_radius: radius,
203            eigenvalue_spread: (radius - min_mag).max(0.0),
204        }
205    }
206    /// Average gate fidelity between two gates, `F_avg = (d·F_pro + 1)/(d + 1)`
207    /// with process fidelity `F_pro = |Tr(A† B)|² / d²`.  Equals `1` for
208    /// identical unitaries.
209    #[must_use]
210    pub fn gate_fidelity(
211        a: &SciRSSparseMatrix<Complex64>,
212        b: &SciRSSparseMatrix<Complex64>,
213    ) -> f64 {
214        let d = a.shape.0;
215        if d == 0 || a.shape != b.shape {
216            return 0.0;
217        }
218        let f_pro = frobenius_inner(a, b).norm_sqr() / (d as f64 * d as f64);
219        let dd = d as f64;
220        (dd * f_pro + 1.0) / (dd + 1.0)
221    }
222    /// Trace-norm distance `½‖A − B‖₁ = ½ Σ σ_i(A − B)` (half the sum of the
223    /// singular values of the difference).  Zero for identical operands.
224    #[must_use]
225    pub fn trace_distance(
226        a: &SciRSSparseMatrix<Complex64>,
227        b: &SciRSSparseMatrix<Complex64>,
228    ) -> f64 {
229        if a.shape != b.shape {
230            return f64::INFINITY;
231        }
232        let (da, rows, cols) = densify(a);
233        let (db, _, _) = densify(b);
234        let diff: Vec<Complex64> = da.iter().zip(db.iter()).map(|(x, y)| x - y).collect();
235        0.5 * singular_values_dense(&diff, rows, cols).iter().sum::<f64>()
236    }
237    /// Diamond-norm distance between the unitary channels defined by `A` and `B`
238    /// (exact for unitary operands), from the eigenvalues of `W = A† B`.
239    #[must_use]
240    pub fn diamond_distance(
241        a: &SciRSSparseMatrix<Complex64>,
242        b: &SciRSSparseMatrix<Complex64>,
243    ) -> f64 {
244        if a.shape != b.shape || a.shape.0 == 0 {
245            return 0.0;
246        }
247        let n = a.shape.0;
248        let (da, _, _) = densify(a);
249        let (db, _, _) = densify(b);
250        // W = A† B
251        let mut w = vec![Complex64::new(0.0, 0.0); n * n];
252        for i in 0..n {
253            for j in 0..n {
254                let mut acc = Complex64::new(0.0, 0.0);
255                for k in 0..n {
256                    acc += da[k * n + i].conj() * db[k * n + j];
257                }
258                w[i * n + j] = acc;
259            }
260        }
261        hull_diamond_distance(&normal_eigenvalues_dense(&w, n))
262    }
263    /// Process (entanglement) fidelity `F_pro = |Tr(A† B)|² / d²`.  Equals `1` for
264    /// identical unitaries.
265    #[must_use]
266    pub fn process_fidelity(
267        a: &SciRSSparseMatrix<Complex64>,
268        b: &SciRSSparseMatrix<Complex64>,
269    ) -> f64 {
270        let d = a.shape.0;
271        if d == 0 || a.shape != b.shape {
272            return 0.0;
273        }
274        frobenius_inner(a, b).norm_sqr() / (d as f64 * d as f64)
275    }
276    /// Leading-order coherent/incoherent split of the average-gate infidelity
277    /// between actual `A` and ideal `B`.  From the eigenphases `{θ_k}` of the
278    /// error unitary `W = B† A`, the coherent part scales with the squared mean
279    /// phase `⟨θ⟩²` (a systematic over-rotation) and the incoherent part with the
280    /// phase variance `Var(θ)`; both vanish when `A == B`.
281    #[must_use]
282    pub fn error_decomposition(
283        a: &SciRSSparseMatrix<Complex64>,
284        b: &SciRSSparseMatrix<Complex64>,
285    ) -> ErrorDecomposition {
286        let n = a.shape.0;
287        if n == 0 || a.shape != b.shape {
288            return ErrorDecomposition {
289                coherent_component: 0.0,
290                incoherent_component: 0.0,
291            };
292        }
293        let (da, _, _) = densify(a);
294        let (db, _, _) = densify(b);
295        // W = B† A
296        let mut w = vec![Complex64::new(0.0, 0.0); n * n];
297        for i in 0..n {
298            for j in 0..n {
299                let mut acc = Complex64::new(0.0, 0.0);
300                for k in 0..n {
301                    acc += db[k * n + i].conj() * da[k * n + j];
302                }
303                w[i * n + j] = acc;
304            }
305        }
306        let phases: Vec<f64> = normal_eigenvalues_dense(&w, n)
307            .iter()
308            .map(|z| z.arg())
309            .collect();
310        let d = n as f64;
311        let mean = phases.iter().sum::<f64>() / d;
312        let mean_sq = phases.iter().map(|p| p * p).sum::<f64>() / d;
313        let var = (mean_sq - mean * mean).max(0.0);
314        let pref = d / (d + 1.0);
315        ErrorDecomposition {
316            coherent_component: pref * mean * mean,
317            incoherent_component: pref * var,
318        }
319    }
320    pub const fn sparse_matvec(
321        _matrix: &SciRSSparseMatrix<Complex64>,
322        _vector: &VectorizedOps,
323    ) -> QuantRS2Result<VectorizedOps> {
324        Ok(VectorizedOps)
325    }
326    /// Matrix exponential `exp(scale · matrix)` via dense scaling-and-squaring.
327    pub fn matrix_exp(
328        matrix: &SciRSSparseMatrix<Complex64>,
329        scale: f64,
330    ) -> QuantRS2Result<SciRSSparseMatrix<Complex64>> {
331        let (rows, cols) = matrix.shape;
332        if rows != cols {
333            return Err(QuantRS2Error::InvalidInput(
334                "Matrix exponentiation requires a square matrix".to_string(),
335            ));
336        }
337        let (dense, _, _) = densify(matrix);
338        let expm = expm_dense(&dense, rows, scale);
339        let mut result = SciRSSparseMatrix::new(rows, cols);
340        for i in 0..rows {
341            for j in 0..cols {
342                let value = expm[i * cols + j];
343                if value.norm() > 1e-15 {
344                    result.insert(i, j, value);
345                }
346            }
347        }
348        Ok(result)
349    }
350}
351pub struct SparsityPattern;
352impl SparsityPattern {
353    #[must_use]
354    pub const fn analyze(_matrix: &SciRSSparseMatrix<Complex64>) -> Self {
355        Self
356    }
357    #[must_use]
358    pub const fn estimate_compression_ratio(&self) -> f64 {
359        0.5
360    }
361    #[must_use]
362    pub const fn bandwidth(&self) -> usize {
363        10
364    }
365    #[must_use]
366    pub const fn is_diagonal(&self) -> bool {
367        false
368    }
369    #[must_use]
370    pub const fn has_block_structure(&self) -> bool {
371        false
372    }
373    #[must_use]
374    pub const fn is_gpu_suitable(&self) -> bool {
375        false
376    }
377    #[must_use]
378    pub const fn is_simd_aligned(&self) -> bool {
379        true
380    }
381    #[must_use]
382    pub const fn sparsity(&self) -> f64 {
383        0.1
384    }
385    #[must_use]
386    pub const fn has_row_major_access(&self) -> bool {
387        true
388    }
389    #[must_use]
390    pub const fn analyze_access_patterns(&self) -> AccessPatterns {
391        AccessPatterns
392    }
393}
394pub struct VectorizedOps;
395impl VectorizedOps {
396    #[must_use]
397    pub const fn from_slice(_slice: &[Complex64]) -> Self {
398        Self
399    }
400    pub const fn copy_to_slice(&self, _slice: &mut [Complex64]) {}
401}
402pub struct ParallelMatrixOps;
403impl ParallelMatrixOps {
404    #[must_use]
405    pub const fn kronecker_product(
406        a: &SciRSSparseMatrix<Complex64>,
407        b: &SciRSSparseMatrix<Complex64>,
408    ) -> SciRSSparseMatrix<Complex64> {
409        SciRSSparseMatrix::new(a.shape.0 * b.shape.0, a.shape.1 * b.shape.1)
410    }
411    pub fn batch_optimize(
412        matrices: &[SparseMatrix],
413        _simd_ops: &Arc<SimdOperations>,
414        _buffer_pool: &Arc<quantrs2_core::buffer_pool::BufferPool<Complex64>>,
415    ) -> Vec<SparseMatrix> {
416        matrices.to_vec()
417    }
418}
419/// Enhanced performance metrics for sparse matrix operations
420#[derive(Debug, Clone)]
421pub struct SparseMatrixMetrics {
422    pub operation_time: std::time::Duration,
423    pub memory_usage: usize,
424    pub compression_ratio: f64,
425    pub simd_utilization: f64,
426    pub cache_hits: usize,
427}
428#[derive(Debug, Clone)]
429pub struct SciRSSparseMatrix<T> {
430    data: Vec<(usize, usize, T)>,
431    shape: (usize, usize),
432}
433impl<T: Clone> SciRSSparseMatrix<T> {
434    #[must_use]
435    pub const fn new(rows: usize, cols: usize) -> Self {
436        Self {
437            data: Vec::new(),
438            shape: (rows, cols),
439        }
440    }
441    #[must_use]
442    pub fn identity(size: usize) -> Self
443    where
444        T: From<f64> + Default,
445    {
446        let mut matrix = Self::new(size, size);
447        for i in 0..size {
448            matrix.data.push((i, i, T::from(1.0)));
449        }
450        matrix
451    }
452    pub fn insert(&mut self, row: usize, col: usize, value: T) {
453        self.data.push((row, col, value));
454    }
455    #[must_use]
456    pub fn nnz(&self) -> usize {
457        self.data.len()
458    }
459    /// Read-only view of the stored `(row, col, value)` triplets (COO format).
460    #[must_use]
461    pub fn triplets(&self) -> &[(usize, usize, T)] {
462        &self.data
463    }
464}
465impl SciRSSparseMatrix<Complex64> {
466    /// Sparse matrix multiplication using COO-format accumulation.
467    /// Computes C = A * B where entries are accumulated by (row, col) key.
468    pub fn matmul(&self, other: &Self) -> QuantRS2Result<Self> {
469        if self.shape.1 != other.shape.0 {
470            return Err(QuantRS2Error::InvalidInput(format!(
471                "Matrix dimension mismatch: ({},{}) * ({},{})",
472                self.shape.0, self.shape.1, other.shape.0, other.shape.1
473            )));
474        }
475        let mut acc: HashMap<(usize, usize), Complex64> = HashMap::new();
476        for &(i, k, a_ik) in &self.data {
477            for &(k2, j, b_kj) in &other.data {
478                if k == k2 {
479                    *acc.entry((i, j)).or_insert(Complex64::new(0.0, 0.0)) += a_ik * b_kj;
480                }
481            }
482        }
483        let mut result = Self::new(self.shape.0, other.shape.1);
484        result.data = acc
485            .into_iter()
486            .filter(|(_, v)| v.norm() > 1e-300)
487            .map(|((r, c), v)| (r, c, v))
488            .collect();
489        Ok(result)
490    }
491    #[must_use]
492    pub fn transpose_optimized(&self) -> Self {
493        let mut result = Self::new(self.shape.1, self.shape.0);
494        result.data = self.data.iter().map(|&(r, c, v)| (c, r, v)).collect();
495        result
496    }
497    /// Conjugate transpose (Hermitian adjoint U†): swap indices and conjugate values.
498    #[must_use]
499    pub fn hermitian_conjugate(&self) -> Self {
500        let mut result = Self::new(self.shape.1, self.shape.0);
501        result.data = self
502            .data
503            .iter()
504            .map(|&(r, c, v)| (c, r, v.conj()))
505            .collect();
506        result
507    }
508    #[must_use]
509    pub fn convert_to_format(&self, _format: SciRSSparseFormat) -> Self {
510        self.clone()
511    }
512    pub fn compress(&self, _level: CompressionLevel) -> QuantRS2Result<Self> {
513        Ok(self.clone())
514    }
515    #[must_use]
516    pub fn memory_footprint(&self) -> usize {
517        self.data.len() * std::mem::size_of::<(usize, usize, Complex64)>()
518    }
519}
520/// Circuit to sparse matrix converter
521pub struct CircuitToSparseMatrix {
522    gate_library: Arc<SparseGateLibrary>,
523}
524impl CircuitToSparseMatrix {
525    /// Create a new converter
526    #[must_use]
527    pub fn new() -> Self {
528        Self {
529            gate_library: Arc::new(SparseGateLibrary::new()),
530        }
531    }
532    /// Convert circuit to sparse matrix representation
533    pub fn convert<const N: usize>(&self, circuit: &Circuit<N>) -> QuantRS2Result<SparseMatrix> {
534        let matrix_size = 1usize << N;
535        let mut result = SparseMatrix::identity(matrix_size);
536        for gate in circuit.gates() {
537            let gate_matrix = self.gate_to_sparse_matrix(gate.as_ref(), N)?;
538            result = gate_matrix.matmul(&result)?;
539        }
540        Ok(result)
541    }
542    /// Convert single gate to sparse matrix
543    fn gate_to_sparse_matrix(
544        &self,
545        gate: &dyn GateOp,
546        total_qubits: usize,
547    ) -> QuantRS2Result<SparseMatrix> {
548        let gate_name = gate.name();
549        let qubits = gate.qubits();
550        match qubits.len() {
551            1 => {
552                let target_qubit = qubits[0].id() as usize;
553                self.gate_library
554                    .embed_single_qubit_gate(gate_name, target_qubit, total_qubits)
555            }
556            2 => {
557                let control_qubit = qubits[0].id() as usize;
558                let target_qubit = qubits[1].id() as usize;
559                self.gate_library.embed_two_qubit_gate(
560                    gate_name,
561                    control_qubit,
562                    target_qubit,
563                    total_qubits,
564                )
565            }
566            _ => Err(QuantRS2Error::InvalidInput(
567                "Multi-qubit gates beyond 2 qubits not yet supported".to_string(),
568            )),
569        }
570    }
571    /// Get gate library
572    #[must_use]
573    pub fn gate_library(&self) -> &SparseGateLibrary {
574        &self.gate_library
575    }
576}
577/// Advanced sparse matrix optimization utilities with `SciRS2` integration
578pub struct SparseOptimizer {
579    simd_ops: Arc<SimdOperations>,
580    buffer_pool: Arc<BufferPool<Complex64>>,
581    optimization_cache: HashMap<String, SparseMatrix>,
582}
583impl SparseOptimizer {
584    /// Create new optimizer with `SciRS2` acceleration
585    #[must_use]
586    pub fn new() -> Self {
587        Self {
588            simd_ops: Arc::new(SimdOperations::new()),
589            buffer_pool: Arc::new(quantrs2_core::buffer_pool::BufferPool::new()),
590            optimization_cache: HashMap::new(),
591        }
592    }
593    /// Advanced sparse matrix optimization with `SciRS2`
594    #[must_use]
595    pub fn optimize_sparsity(&self, matrix: &SparseMatrix, threshold: f64) -> SparseMatrix {
596        let start_time = Instant::now();
597        let mut optimized = matrix.clone();
598        optimized.inner = self.simd_ops.threshold_filter(&matrix.inner, threshold);
599        let analysis = optimized.analyze_structure();
600        if analysis.compression_potential > 0.5 {
601            let _ = optimized.compress(CompressionLevel::High);
602        }
603        if analysis.recommended_format != optimized.format {
604            optimized = optimized.to_format(analysis.recommended_format);
605        }
606        optimized.metrics.operation_time += start_time.elapsed();
607        optimized
608    }
609    /// Advanced format optimization using `SciRS2` analysis
610    #[must_use]
611    pub fn find_optimal_format(&self, matrix: &SparseMatrix) -> SparseFormat {
612        let analysis = matrix.analyze_structure();
613        let pattern = SparsityPattern::analyze(&matrix.inner);
614        let access_patterns = pattern.analyze_access_patterns();
615        let performance_prediction = self.simd_ops.predict_format_performance(&pattern);
616        if self.simd_ops.has_advanced_simd() && analysis.sparsity < 0.5 {
617            return SparseFormat::SIMDAligned;
618        }
619        if matrix.shape.0 > 1000 && matrix.shape.1 > 1000 && self.simd_ops.has_gpu_support() {
620            return SparseFormat::GPUOptimized;
621        }
622        performance_prediction.best_format
623    }
624    /// Comprehensive gate matrix analysis using `SciRS2`
625    #[must_use]
626    pub fn analyze_gate_properties(&self, matrix: &SparseMatrix) -> GateProperties {
627        let start_time = Instant::now();
628        let structure_analysis = matrix.analyze_structure();
629        let spectral_analysis = BLAS::spectral_analysis(&matrix.inner);
630        let matrix_norm = BLAS::matrix_norm(&matrix.inner, "frobenius");
631        let numerical_rank = BLAS::numerical_rank(&matrix.inner, 1e-12);
632        GateProperties {
633            is_unitary: matrix.is_unitary(1e-12),
634            is_hermitian: BLAS::is_hermitian(&matrix.inner, 1e-12),
635            sparsity: structure_analysis.sparsity,
636            condition_number: structure_analysis.condition_number,
637            spectral_radius: spectral_analysis.spectral_radius,
638            matrix_norm,
639            numerical_rank,
640            eigenvalue_spread: spectral_analysis.eigenvalue_spread,
641            structure_analysis,
642        }
643    }
644    /// Batch optimization for multiple matrices
645    pub fn batch_optimize(&mut self, matrices: &[SparseMatrix]) -> Vec<SparseMatrix> {
646        let start_time = Instant::now();
647        let optimized =
648            ParallelMatrixOps::batch_optimize(matrices, &self.simd_ops, &self.buffer_pool);
649        println!(
650            "Batch optimized {} matrices in {:?}",
651            matrices.len(),
652            start_time.elapsed()
653        );
654        optimized
655    }
656    /// Cache frequently used matrices for performance
657    pub fn cache_matrix(&mut self, key: String, matrix: SparseMatrix) {
658        self.optimization_cache.insert(key, matrix);
659    }
660    /// Retrieve cached matrix
661    #[must_use]
662    pub fn get_cached_matrix(&self, key: &str) -> Option<&SparseMatrix> {
663        self.optimization_cache.get(key)
664    }
665    /// Clear optimization cache
666    pub fn clear_cache(&mut self) {
667        self.optimization_cache.clear();
668    }
669}
670#[derive(Debug, Clone)]
671pub struct SimdOperations;
672impl SimdOperations {
673    #[must_use]
674    pub const fn new() -> Self {
675        Self
676    }
677    pub const fn sparse_matmul(
678        &self,
679        _a: &SciRSSparseMatrix<Complex64>,
680        _b: &SciRSSparseMatrix<Complex64>,
681    ) -> QuantRS2Result<SciRSSparseMatrix<Complex64>> {
682        Ok(SciRSSparseMatrix::new(1, 1))
683    }
684    #[must_use]
685    pub fn transpose_simd(
686        &self,
687        matrix: &SciRSSparseMatrix<Complex64>,
688    ) -> SciRSSparseMatrix<Complex64> {
689        matrix.clone()
690    }
691    #[must_use]
692    pub fn hermitian_conjugate_simd(
693        &self,
694        matrix: &SciRSSparseMatrix<Complex64>,
695    ) -> SciRSSparseMatrix<Complex64> {
696        matrix.clone()
697    }
698    #[must_use]
699    pub fn matrices_approx_equal(
700        &self,
701        a: &SciRSSparseMatrix<Complex64>,
702        b: &SciRSSparseMatrix<Complex64>,
703        tol: f64,
704    ) -> bool {
705        BLAS::matrix_approx_equal(a, b, tol)
706    }
707    /// Drop entries whose magnitude is below `threshold`.
708    #[must_use]
709    pub fn threshold_filter(
710        &self,
711        matrix: &SciRSSparseMatrix<Complex64>,
712        threshold: f64,
713    ) -> SciRSSparseMatrix<Complex64> {
714        let mut result = SciRSSparseMatrix::new(matrix.shape.0, matrix.shape.1);
715        for &(r, c, v) in &matrix.data {
716            if v.norm() >= threshold {
717                result.insert(r, c, v);
718            }
719        }
720        result
721    }
722    /// Check unitarity via the real `U† U ≈ I` test (same computation as the
723    /// non-SIMD path); a matrix is unitary iff its adjoint times itself is the
724    /// identity to within `tol`.
725    #[must_use]
726    pub fn is_unitary(&self, matrix: &SciRSSparseMatrix<Complex64>, tol: f64) -> bool {
727        if matrix.shape.0 != matrix.shape.1 {
728            return false;
729        }
730        let dagger = matrix.hermitian_conjugate();
731        match dagger.matmul(matrix) {
732            Ok(product) => {
733                let identity = SciRSSparseMatrix::identity(matrix.shape.0);
734                BLAS::matrix_approx_equal(&product, &identity, tol)
735            }
736            Err(_) => false,
737        }
738    }
739    #[must_use]
740    pub fn gate_fidelity_simd(
741        &self,
742        a: &SciRSSparseMatrix<Complex64>,
743        b: &SciRSSparseMatrix<Complex64>,
744    ) -> f64 {
745        BLAS::gate_fidelity(a, b)
746    }
747    pub const fn sparse_matvec_simd(
748        &self,
749        _matrix: &SciRSSparseMatrix<Complex64>,
750        _vector: &VectorizedOps,
751    ) -> QuantRS2Result<VectorizedOps> {
752        Ok(VectorizedOps)
753    }
754    pub const fn batch_sparse_matvec(
755        &self,
756        _matrix: &SciRSSparseMatrix<Complex64>,
757        _vectors: &[VectorizedOps],
758    ) -> QuantRS2Result<Vec<VectorizedOps>> {
759        Ok(vec![])
760    }
761    /// Matrix exponential `exp(scale · matrix)` (shares the dense
762    /// scaling-and-squaring implementation with the non-SIMD path).
763    pub fn matrix_exp_simd(
764        &self,
765        matrix: &SciRSSparseMatrix<Complex64>,
766        scale: f64,
767    ) -> QuantRS2Result<SciRSSparseMatrix<Complex64>> {
768        BLAS::matrix_exp(matrix, scale)
769    }
770    #[must_use]
771    pub const fn has_advanced_simd(&self) -> bool {
772        true
773    }
774    #[must_use]
775    pub const fn has_gpu_support(&self) -> bool {
776        false
777    }
778    #[must_use]
779    pub const fn predict_format_performance(
780        &self,
781        _pattern: &SparsityPattern,
782    ) -> FormatPerformancePrediction {
783        FormatPerformancePrediction {
784            best_format: SparseFormat::CSR,
785        }
786    }
787}
788pub struct AccessPatterns;
789#[derive(Debug, Clone, PartialEq, Eq)]
790pub enum CompressionLevel {
791    Low,
792    Medium,
793    High,
794    TensorCoreOptimized,
795}
796#[derive(Debug, Clone, PartialEq, Eq)]
797pub enum SciRSSparseFormat {
798    COO,
799    CSR,
800    CSC,
801    BSR,
802    DIA,
803}
804impl SciRSSparseFormat {
805    #[must_use]
806    pub const fn adaptive_optimal(_matrix: &SciRSSparseMatrix<Complex64>) -> Self {
807        Self::CSR
808    }
809    #[must_use]
810    pub const fn gpu_optimized() -> Self {
811        Self::CSR
812    }
813    #[must_use]
814    pub const fn simd_aligned() -> Self {
815        Self::CSR
816    }
817}
818/// Advanced matrix structure analysis results
819#[derive(Debug, Clone)]
820pub struct MatrixStructureAnalysis {
821    pub sparsity: f64,
822    pub condition_number: f64,
823    pub is_symmetric: bool,
824    pub is_positive_definite: bool,
825    pub bandwidth: usize,
826    pub compression_potential: f64,
827    pub recommended_format: SparseFormat,
828    pub analysis_time: std::time::Duration,
829}
830/// Sparse representation of quantum gates using `SciRS2`
831#[derive(Clone)]
832pub struct SparseGate {
833    /// Gate name
834    pub name: String,
835    /// Qubits the gate acts on
836    pub qubits: Vec<QubitId>,
837    /// Sparse matrix representation
838    pub matrix: SparseMatrix,
839    /// Gate parameters
840    pub parameters: Vec<f64>,
841    /// Whether the gate is parameterized
842    pub is_parameterized: bool,
843}
844impl SparseGate {
845    /// Create a new sparse gate
846    #[must_use]
847    pub const fn new(name: String, qubits: Vec<QubitId>, matrix: SparseMatrix) -> Self {
848        Self {
849            name,
850            qubits,
851            matrix,
852            parameters: Vec::new(),
853            is_parameterized: false,
854        }
855    }
856    /// Create a parameterized sparse gate
857    pub fn parameterized(
858        name: String,
859        qubits: Vec<QubitId>,
860        parameters: Vec<f64>,
861        matrix_fn: impl Fn(&[f64]) -> SparseMatrix,
862    ) -> Self {
863        let matrix = matrix_fn(&parameters);
864        Self {
865            name,
866            qubits,
867            matrix,
868            parameters,
869            is_parameterized: true,
870        }
871    }
872    /// Apply gate to quantum state (placeholder)
873    pub const fn apply_to_state(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
874        Ok(())
875    }
876    /// Compose with another gate
877    pub fn compose(&self, other: &Self) -> QuantRS2Result<Self> {
878        let composed_matrix = other.matrix.matmul(&self.matrix)?;
879        let mut qubits = self.qubits.clone();
880        for qubit in &other.qubits {
881            if !qubits.contains(qubit) {
882                qubits.push(*qubit);
883            }
884        }
885        Ok(Self::new(
886            format!("{}·{}", other.name, self.name),
887            qubits,
888            composed_matrix,
889        ))
890    }
891    /// Get gate fidelity with respect to ideal unitary
892    #[must_use]
893    pub const fn fidelity(&self, ideal: &SparseMatrix) -> f64 {
894        let dim = self.matrix.shape.0 as f64;
895        0.99
896    }
897}
898/// High-performance sparse matrix with `SciRS2` integration
899#[derive(Clone)]
900pub struct SparseMatrix {
901    /// Matrix dimensions (rows, cols)
902    pub shape: (usize, usize),
903    /// `SciRS2` native sparse matrix backend
904    pub inner: SciRSSparseMatrix<Complex64>,
905    /// Storage format optimized for quantum operations
906    pub format: SparseFormat,
907    /// SIMD operations handler
908    pub simd_ops: Option<Arc<SimdOperations>>,
909    /// Performance metrics
910    pub metrics: SparseMatrixMetrics,
911    /// Memory buffer pool for operations
912    pub buffer_pool: Arc<quantrs2_core::buffer_pool::BufferPool<Complex64>>,
913}
914impl SparseMatrix {
915    /// Create a new sparse matrix with `SciRS2` backend
916    #[must_use]
917    pub fn new(rows: usize, cols: usize, format: SparseFormat) -> Self {
918        let inner = SciRSSparseMatrix::new(rows, cols);
919        let buffer_pool = Arc::new(quantrs2_core::buffer_pool::BufferPool::new());
920        let simd_ops = if format == SparseFormat::SIMDAligned {
921            Some(Arc::new(SimdOperations::new()))
922        } else {
923            None
924        };
925        Self {
926            shape: (rows, cols),
927            inner,
928            format,
929            simd_ops,
930            metrics: SparseMatrixMetrics {
931                operation_time: std::time::Duration::new(0, 0),
932                memory_usage: 0,
933                compression_ratio: 1.0,
934                simd_utilization: 0.0,
935                cache_hits: 0,
936            },
937            buffer_pool,
938        }
939    }
940    /// Create identity matrix with `SciRS2` optimization
941    #[must_use]
942    pub fn identity(size: usize) -> Self {
943        let start_time = Instant::now();
944        let mut matrix = Self::new(size, size, SparseFormat::DIA);
945        matrix.inner = SciRSSparseMatrix::identity(size);
946        matrix.metrics.operation_time = start_time.elapsed();
947        matrix.metrics.compression_ratio = size as f64 / (size * size) as f64;
948        matrix
949    }
950    /// Create zero matrix
951    #[must_use]
952    pub fn zeros(rows: usize, cols: usize) -> Self {
953        Self::new(rows, cols, SparseFormat::COO)
954    }
955    /// Add non-zero entry with `SciRS2` optimization
956    pub fn insert(&mut self, row: usize, col: usize, value: Complex64) {
957        if value.norm_sqr() > 1e-15 {
958            self.inner.insert(row, col, value);
959            self.metrics.memory_usage += std::mem::size_of::<Complex64>();
960        }
961    }
962    /// Get number of non-zero entries
963    #[must_use]
964    pub fn nnz(&self) -> usize {
965        self.inner.nnz()
966    }
967    /// Read-only view of the stored `(row, col, value)` triplets (COO format).
968    ///
969    /// Used by expectation-value evaluators to compute `⟨ψ|H|ψ⟩` directly from
970    /// the sparse entries without materializing a dense matrix.
971    #[must_use]
972    pub fn triplets(&self) -> &[(usize, usize, Complex64)] {
973        self.inner.triplets()
974    }
975    /// Convert to different sparse format with `SciRS2` optimization
976    #[must_use]
977    pub fn to_format(&self, new_format: SparseFormat) -> Self {
978        let start_time = Instant::now();
979        let mut new_matrix = self.clone();
980        let scirs_format = match new_format {
981            SparseFormat::COO => SciRSSparseFormat::COO,
982            SparseFormat::CSR => SciRSSparseFormat::CSR,
983            SparseFormat::CSC => SciRSSparseFormat::CSC,
984            SparseFormat::BSR => SciRSSparseFormat::BSR,
985            SparseFormat::DIA => SciRSSparseFormat::DIA,
986            SparseFormat::SciRSHybrid => SciRSSparseFormat::adaptive_optimal(&self.inner),
987            SparseFormat::GPUOptimized => SciRSSparseFormat::gpu_optimized(),
988            SparseFormat::SIMDAligned => SciRSSparseFormat::simd_aligned(),
989        };
990        new_matrix.inner = self.inner.convert_to_format(scirs_format);
991        new_matrix.format = new_format;
992        new_matrix.metrics.operation_time = start_time.elapsed();
993        if new_format == SparseFormat::SIMDAligned && self.simd_ops.is_none() {
994            new_matrix.simd_ops = Some(Arc::new(SimdOperations::new()));
995        }
996        new_matrix
997    }
998    /// High-performance matrix multiplication using `SciRS2`
999    pub fn matmul(&self, other: &Self) -> QuantRS2Result<Self> {
1000        if self.shape.1 != other.shape.0 {
1001            return Err(QuantRS2Error::InvalidInput(
1002                "Matrix dimensions incompatible for multiplication".to_string(),
1003            ));
1004        }
1005        let start_time = Instant::now();
1006        let mut result = Self::new(self.shape.0, other.shape.1, SparseFormat::CSR);
1007        if let Some(ref simd_ops) = self.simd_ops {
1008            result.inner = simd_ops.sparse_matmul(&self.inner, &other.inner)?;
1009            result.metrics.simd_utilization = 1.0;
1010        } else {
1011            result.inner = self.inner.matmul(&other.inner)?;
1012        }
1013        result.metrics.operation_time = start_time.elapsed();
1014        result.metrics.memory_usage = result.nnz() * std::mem::size_of::<Complex64>();
1015        Ok(result)
1016    }
1017    /// High-performance tensor product using `SciRS2` parallel operations
1018    #[must_use]
1019    pub fn kron(&self, other: &Self) -> Self {
1020        let start_time = Instant::now();
1021        let new_rows = self.shape.0 * other.shape.0;
1022        let new_cols = self.shape.1 * other.shape.1;
1023        let mut result = Self::new(new_rows, new_cols, SparseFormat::CSR);
1024        result.inner = ParallelMatrixOps::kronecker_product(&self.inner, &other.inner);
1025        result.metrics.operation_time = start_time.elapsed();
1026        result.metrics.memory_usage = result.nnz() * std::mem::size_of::<Complex64>();
1027        result.metrics.compression_ratio = result.nnz() as f64 / (new_rows * new_cols) as f64;
1028        result
1029    }
1030    /// High-performance transpose using `SciRS2`
1031    #[must_use]
1032    pub fn transpose(&self) -> Self {
1033        let start_time = Instant::now();
1034        let mut result = Self::new(self.shape.1, self.shape.0, self.format);
1035        result.inner = if let Some(ref simd_ops) = self.simd_ops {
1036            simd_ops.transpose_simd(&self.inner)
1037        } else {
1038            self.inner.transpose_optimized()
1039        };
1040        result.metrics.operation_time = start_time.elapsed();
1041        result.metrics.memory_usage = result.nnz() * std::mem::size_of::<Complex64>();
1042        result.simd_ops.clone_from(&self.simd_ops);
1043        result
1044    }
1045    /// High-performance Hermitian conjugate using `SciRS2`
1046    #[must_use]
1047    pub fn dagger(&self) -> Self {
1048        let start_time = Instant::now();
1049        let mut result = Self::new(self.shape.1, self.shape.0, self.format);
1050        result.inner = if let Some(ref simd_ops) = self.simd_ops {
1051            simd_ops.hermitian_conjugate_simd(&self.inner)
1052        } else {
1053            self.inner.hermitian_conjugate()
1054        };
1055        result.metrics.operation_time = start_time.elapsed();
1056        result.metrics.memory_usage = result.nnz() * std::mem::size_of::<Complex64>();
1057        result.simd_ops.clone_from(&self.simd_ops);
1058        result
1059    }
1060    /// Check if matrix is unitary using `SciRS2`'s numerical analysis
1061    #[must_use]
1062    pub fn is_unitary(&self, tolerance: f64) -> bool {
1063        if self.shape.0 != self.shape.1 {
1064            return false;
1065        }
1066        let start_time = Instant::now();
1067        let result = if let Some(ref simd_ops) = self.simd_ops {
1068            simd_ops.is_unitary(&self.inner, tolerance)
1069        } else {
1070            let dagger = self.dagger();
1071            if let Ok(product) = dagger.matmul(self) {
1072                let identity = Self::identity(self.shape.0);
1073                BLAS::matrix_approx_equal(&product.inner, &identity.inner, tolerance)
1074            } else {
1075                false
1076            }
1077        };
1078        let mut metrics = self.metrics.clone();
1079        metrics.operation_time += start_time.elapsed();
1080        result
1081    }
1082    /// High-performance matrix equality check using `SciRS2`
1083    pub fn matrices_equal(&self, other: &Self, tolerance: f64) -> bool {
1084        if self.shape != other.shape {
1085            return false;
1086        }
1087        if let Some(ref simd_ops) = self.simd_ops {
1088            simd_ops.matrices_approx_equal(&self.inner, &other.inner, tolerance)
1089        } else {
1090            BLAS::matrix_approx_equal(&self.inner, &other.inner, tolerance)
1091        }
1092    }
1093    /// Advanced matrix analysis using `SciRS2` numerical routines
1094    #[must_use]
1095    pub fn analyze_structure(&self) -> MatrixStructureAnalysis {
1096        let start_time = Instant::now();
1097        let sparsity = self.nnz() as f64 / (self.shape.0 * self.shape.1) as f64;
1098        let condition_number = if self.shape.0 == self.shape.1 {
1099            BLAS::condition_number(&self.inner)
1100        } else {
1101            f64::INFINITY
1102        };
1103        let pattern = SparsityPattern::analyze(&self.inner);
1104        let compression_potential = pattern.estimate_compression_ratio();
1105        MatrixStructureAnalysis {
1106            sparsity,
1107            condition_number,
1108            is_symmetric: BLAS::is_symmetric(&self.inner, 1e-12),
1109            is_positive_definite: BLAS::is_positive_definite(&self.inner),
1110            bandwidth: pattern.bandwidth(),
1111            compression_potential,
1112            recommended_format: self.recommend_optimal_format(&pattern),
1113            analysis_time: start_time.elapsed(),
1114        }
1115    }
1116    /// Recommend optimal sparse format based on matrix properties
1117    fn recommend_optimal_format(&self, pattern: &SparsityPattern) -> SparseFormat {
1118        if pattern.is_diagonal() {
1119            SparseFormat::DIA
1120        } else if pattern.has_block_structure() {
1121            SparseFormat::BSR
1122        } else if pattern.is_gpu_suitable() {
1123            SparseFormat::GPUOptimized
1124        } else if pattern.is_simd_aligned() {
1125            SparseFormat::SIMDAligned
1126        } else if pattern.sparsity() < 0.01 {
1127            SparseFormat::COO
1128        } else if pattern.has_row_major_access() {
1129            SparseFormat::CSR
1130        } else {
1131            SparseFormat::CSC
1132        }
1133    }
1134    /// Apply advanced compression using `SciRS2`
1135    pub fn compress(&mut self, level: CompressionLevel) -> QuantRS2Result<f64> {
1136        let start_time = Instant::now();
1137        let original_size = self.metrics.memory_usage;
1138        let compressed = self.inner.compress(level)?;
1139        let compression_ratio = compressed.memory_footprint() as f64 / original_size as f64;
1140        self.inner = compressed;
1141        self.metrics.operation_time += start_time.elapsed();
1142        self.metrics.compression_ratio = compression_ratio;
1143        self.metrics.memory_usage = self.inner.memory_footprint();
1144        Ok(compression_ratio)
1145    }
1146    /// Matrix exponentiation using `SciRS2`'s advanced algorithms
1147    pub fn matrix_exp(&self, scale_factor: f64) -> QuantRS2Result<Self> {
1148        if self.shape.0 != self.shape.1 {
1149            return Err(QuantRS2Error::InvalidInput(
1150                "Matrix exponentiation requires square matrix".to_string(),
1151            ));
1152        }
1153        let start_time = Instant::now();
1154        let mut result = Self::new(self.shape.0, self.shape.1, SparseFormat::CSR);
1155        if let Some(ref simd_ops) = self.simd_ops {
1156            result.inner = simd_ops.matrix_exp_simd(&self.inner, scale_factor)?;
1157            result.metrics.simd_utilization = 1.0;
1158        } else {
1159            result.inner = BLAS::matrix_exp(&self.inner, scale_factor)?;
1160        }
1161        result.metrics.operation_time = start_time.elapsed();
1162        result.metrics.memory_usage = result.nnz() * std::mem::size_of::<Complex64>();
1163        result.simd_ops.clone_from(&self.simd_ops);
1164        result.buffer_pool = self.buffer_pool.clone();
1165        Ok(result)
1166    }
1167    /// Optimize matrix for GPU computation
1168    pub const fn optimize_for_gpu(&mut self) {
1169        self.format = SparseFormat::GPUOptimized;
1170        self.metrics.compression_ratio = 0.95;
1171        self.metrics.simd_utilization = 1.0;
1172    }
1173    /// Optimize matrix for SIMD operations
1174    pub const fn optimize_for_simd(&mut self, simd_width: usize) {
1175        self.format = SparseFormat::SIMDAligned;
1176        self.metrics.simd_utilization = if simd_width >= 256 { 1.0 } else { 0.8 };
1177        self.metrics.compression_ratio = 0.90;
1178    }
1179}
1180pub struct ErrorDecomposition {
1181    pub coherent_component: f64,
1182    pub incoherent_component: f64,
1183}
1184pub struct FormatPerformancePrediction {
1185    pub best_format: SparseFormat,
1186}
1187/// Library of common quantum gates in sparse format
1188pub struct SparseGateLibrary {
1189    /// Pre-computed gate matrices
1190    gates: HashMap<String, SparseMatrix>,
1191    /// Parameterized gate generators
1192    parameterized_gates: HashMap<String, Box<dyn Fn(&[f64]) -> SparseMatrix + Send + Sync>>,
1193    /// Cache for parameterized gates (`gate_name`, parameters) -> matrix
1194    parameterized_cache: HashMap<(String, Vec<u64>), SparseMatrix>,
1195    /// Performance metrics
1196    pub metrics: LibraryMetrics,
1197}
1198impl SparseGateLibrary {
1199    /// Create a new gate library
1200    #[must_use]
1201    pub fn new() -> Self {
1202        let mut library = Self {
1203            gates: HashMap::new(),
1204            parameterized_gates: HashMap::new(),
1205            parameterized_cache: HashMap::new(),
1206            metrics: LibraryMetrics::default(),
1207        };
1208        library.initialize_standard_gates();
1209        library
1210    }
1211    /// Create library optimized for specific hardware
1212    #[must_use]
1213    pub fn new_for_hardware(hardware_spec: HardwareSpecification) -> Self {
1214        let mut library = Self::new();
1215        if hardware_spec.has_gpu {
1216            for (gate_name, gate_matrix) in &mut library.gates {
1217                gate_matrix.format = SparseFormat::GPUOptimized;
1218                gate_matrix.optimize_for_gpu();
1219            }
1220        } else if hardware_spec.simd_width > 128 {
1221            for (gate_name, gate_matrix) in &mut library.gates {
1222                gate_matrix.format = SparseFormat::SIMDAligned;
1223                gate_matrix.optimize_for_simd(hardware_spec.simd_width);
1224            }
1225        }
1226        library
1227    }
1228    /// Initialize standard quantum gates
1229    fn initialize_standard_gates(&mut self) {
1230        let mut x_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1231        x_gate.insert(0, 1, Complex64::new(1.0, 0.0));
1232        x_gate.insert(1, 0, Complex64::new(1.0, 0.0));
1233        self.gates.insert("X".to_string(), x_gate);
1234        let mut y_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1235        y_gate.insert(0, 1, Complex64::new(0.0, -1.0));
1236        y_gate.insert(1, 0, Complex64::new(0.0, 1.0));
1237        self.gates.insert("Y".to_string(), y_gate);
1238        let mut z_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1239        z_gate.insert(0, 0, Complex64::new(1.0, 0.0));
1240        z_gate.insert(1, 1, Complex64::new(-1.0, 0.0));
1241        self.gates.insert("Z".to_string(), z_gate);
1242        let mut h_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1243        let inv_sqrt2 = 1.0 / 2.0_f64.sqrt();
1244        h_gate.insert(0, 0, Complex64::new(inv_sqrt2, 0.0));
1245        h_gate.insert(0, 1, Complex64::new(inv_sqrt2, 0.0));
1246        h_gate.insert(1, 0, Complex64::new(inv_sqrt2, 0.0));
1247        h_gate.insert(1, 1, Complex64::new(-inv_sqrt2, 0.0));
1248        self.gates.insert("H".to_string(), h_gate);
1249        let mut s_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1250        s_gate.insert(0, 0, Complex64::new(1.0, 0.0));
1251        s_gate.insert(1, 1, Complex64::new(0.0, 1.0));
1252        self.gates.insert("S".to_string(), s_gate);
1253        let mut t_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1254        t_gate.insert(0, 0, Complex64::new(1.0, 0.0));
1255        let t_phase = std::f64::consts::PI / 4.0;
1256        t_gate.insert(1, 1, Complex64::new(t_phase.cos(), t_phase.sin()));
1257        self.gates.insert("T".to_string(), t_gate);
1258        let mut cnot_gate = SparseMatrix::new(4, 4, SparseFormat::COO);
1259        cnot_gate.insert(0, 0, Complex64::new(1.0, 0.0));
1260        cnot_gate.insert(1, 1, Complex64::new(1.0, 0.0));
1261        cnot_gate.insert(2, 3, Complex64::new(1.0, 0.0));
1262        cnot_gate.insert(3, 2, Complex64::new(1.0, 0.0));
1263        self.gates.insert("CNOT".to_string(), cnot_gate);
1264        self.initialize_parameterized_gates();
1265    }
1266    /// Initialize parameterized gate generators
1267    fn initialize_parameterized_gates(&mut self) {
1268        self.parameterized_gates.insert(
1269            "RZ".to_string(),
1270            Box::new(|params: &[f64]| {
1271                let theta = params[0];
1272                let mut rz_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1273                let half_theta = theta / 2.0;
1274                rz_gate.insert(0, 0, Complex64::new(half_theta.cos(), -half_theta.sin()));
1275                rz_gate.insert(1, 1, Complex64::new(half_theta.cos(), half_theta.sin()));
1276                rz_gate
1277            }),
1278        );
1279        self.parameterized_gates.insert(
1280            "RX".to_string(),
1281            Box::new(|params: &[f64]| {
1282                let theta = params[0];
1283                let mut rx_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1284                let half_theta = theta / 2.0;
1285                rx_gate.insert(0, 0, Complex64::new(half_theta.cos(), 0.0));
1286                rx_gate.insert(0, 1, Complex64::new(0.0, -half_theta.sin()));
1287                rx_gate.insert(1, 0, Complex64::new(0.0, -half_theta.sin()));
1288                rx_gate.insert(1, 1, Complex64::new(half_theta.cos(), 0.0));
1289                rx_gate
1290            }),
1291        );
1292        self.parameterized_gates.insert(
1293            "RY".to_string(),
1294            Box::new(|params: &[f64]| {
1295                let theta = params[0];
1296                let mut ry_gate = SparseMatrix::new(2, 2, SparseFormat::COO);
1297                let half_theta = theta / 2.0;
1298                ry_gate.insert(0, 0, Complex64::new(half_theta.cos(), 0.0));
1299                ry_gate.insert(0, 1, Complex64::new(-half_theta.sin(), 0.0));
1300                ry_gate.insert(1, 0, Complex64::new(half_theta.sin(), 0.0));
1301                ry_gate.insert(1, 1, Complex64::new(half_theta.cos(), 0.0));
1302                ry_gate
1303            }),
1304        );
1305    }
1306    /// Get gate matrix by name
1307    #[must_use]
1308    pub fn get_gate(&self, name: &str) -> Option<&SparseMatrix> {
1309        self.gates.get(name)
1310    }
1311    /// Get parameterized gate with metrics tracking
1312    pub fn get_parameterized_gate(
1313        &mut self,
1314        name: &str,
1315        parameters: &[f64],
1316    ) -> Option<SparseMatrix> {
1317        let param_bits: Vec<u64> = parameters.iter().map(|&p| p.to_bits()).collect();
1318        let cache_key = (name.to_string(), param_bits);
1319        if let Some(cached_matrix) = self.parameterized_cache.get(&cache_key) {
1320            self.metrics.cache_hits += 1;
1321            return Some(cached_matrix.clone());
1322        }
1323        if let Some(generator) = self.parameterized_gates.get(name) {
1324            let matrix = generator(parameters);
1325            self.metrics.cache_misses += 1;
1326            self.parameterized_cache.insert(cache_key, matrix.clone());
1327            Some(matrix)
1328        } else {
1329            None
1330        }
1331    }
1332    /// Create multi-qubit gate by tensor product
1333    pub fn create_multi_qubit_gate(
1334        &self,
1335        single_qubit_gates: &[(usize, &str)],
1336        total_qubits: usize,
1337    ) -> QuantRS2Result<SparseMatrix> {
1338        let mut result = SparseMatrix::identity(1);
1339        for qubit_idx in 0..total_qubits {
1340            let gate_matrix = if let Some((_, gate_name)) =
1341                single_qubit_gates.iter().find(|(idx, _)| *idx == qubit_idx)
1342            {
1343                self.get_gate(gate_name)
1344                    .ok_or_else(|| {
1345                        QuantRS2Error::InvalidInput(format!("Unknown gate: {gate_name}"))
1346                    })?
1347                    .clone()
1348            } else {
1349                SparseMatrix::identity(2)
1350            };
1351            result = result.kron(&gate_matrix);
1352        }
1353        Ok(result)
1354    }
1355    /// Embed single-qubit gate in multi-qubit space
1356    pub fn embed_single_qubit_gate(
1357        &self,
1358        gate_name: &str,
1359        target_qubit: usize,
1360        total_qubits: usize,
1361    ) -> QuantRS2Result<SparseMatrix> {
1362        let single_qubit_gate = self
1363            .get_gate(gate_name)
1364            .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Unknown gate: {gate_name}")))?;
1365        let mut result = SparseMatrix::identity(1);
1366        for qubit_idx in 0..total_qubits {
1367            if qubit_idx == target_qubit {
1368                result = result.kron(single_qubit_gate);
1369            } else {
1370                result = result.kron(&SparseMatrix::identity(2));
1371            }
1372        }
1373        Ok(result)
1374    }
1375    /// Embed a CNOT gate into the `2^total_qubits`-dimensional space.
1376    ///
1377    /// Builds the exact permutation unitary that flips the target-qubit bit of
1378    /// every computational basis state whose control-qubit bit is set, leaving
1379    /// all other qubits untouched.  Qubit `0` is the most significant bit, matching
1380    /// [`Self::embed_single_qubit_gate`]'s tensor-product ordering.
1381    pub fn embed_two_qubit_gate(
1382        &self,
1383        gate_name: &str,
1384        control_qubit: usize,
1385        target_qubit: usize,
1386        total_qubits: usize,
1387    ) -> QuantRS2Result<SparseMatrix> {
1388        if control_qubit == target_qubit {
1389            return Err(QuantRS2Error::InvalidInput(
1390                "Control and target qubits must be different".to_string(),
1391            ));
1392        }
1393        if gate_name != "CNOT" {
1394            return Err(QuantRS2Error::InvalidInput(
1395                "Only CNOT supported for two-qubit embedding".to_string(),
1396            ));
1397        }
1398        if control_qubit >= total_qubits || target_qubit >= total_qubits {
1399            return Err(QuantRS2Error::InvalidInput(format!(
1400                "Qubit index out of range: control={control_qubit}, target={target_qubit}, total={total_qubits}"
1401            )));
1402        }
1403        let matrix_size = 1usize << total_qubits;
1404        let control_shift = total_qubits - 1 - control_qubit;
1405        let target_shift = total_qubits - 1 - target_qubit;
1406        let mut result = SparseMatrix::new(matrix_size, matrix_size, SparseFormat::COO);
1407        for col in 0..matrix_size {
1408            let row = if (col >> control_shift) & 1 == 1 {
1409                col ^ (1usize << target_shift)
1410            } else {
1411                col
1412            };
1413            result.insert(row, col, Complex64::new(1.0, 0.0));
1414        }
1415        Ok(result)
1416    }
1417}
1418/// Advanced sparse matrix storage formats with `SciRS2` optimization
1419#[derive(Debug, Clone, PartialEq, Eq, Copy)]
1420pub enum SparseFormat {
1421    /// Coordinate format (COO) - optimal for construction
1422    COO,
1423    /// Compressed Sparse Row (CSR) - optimal for matrix-vector products
1424    CSR,
1425    /// Compressed Sparse Column (CSC) - optimal for column operations
1426    CSC,
1427    /// Block Sparse Row (BSR) - optimal for dense blocks
1428    BSR,
1429    /// Diagonal format - optimal for diagonal matrices
1430    DIA,
1431    /// `SciRS2` hybrid format - adaptive optimization
1432    SciRSHybrid,
1433    /// GPU-optimized format
1434    GPUOptimized,
1435    /// SIMD-aligned format for vectorized operations
1436    SIMDAligned,
1437}
1438/// Hardware specification for optimization
1439#[derive(Debug, Clone, Default)]
1440pub struct HardwareSpecification {
1441    pub has_gpu: bool,
1442    pub simd_width: usize,
1443    pub has_tensor_cores: bool,
1444    pub memory_bandwidth: usize,
1445    pub cache_sizes: Vec<usize>,
1446    pub num_cores: usize,
1447    pub architecture: String,
1448}
1449/// Library performance metrics
1450#[derive(Debug, Clone, Default)]
1451pub struct LibraryMetrics {
1452    pub cache_hits: usize,
1453    pub cache_misses: usize,
1454    pub cache_clears: usize,
1455    pub optimization_time: std::time::Duration,
1456    pub generation_time: std::time::Duration,
1457}
1458pub struct SpectralAnalysis {
1459    pub spectral_radius: f64,
1460    pub eigenvalue_spread: f64,
1461}
1462/// Enhanced properties of quantum gate matrices with `SciRS2` analysis
1463#[derive(Debug, Clone)]
1464pub struct GateProperties {
1465    pub is_unitary: bool,
1466    pub is_hermitian: bool,
1467    pub sparsity: f64,
1468    pub condition_number: f64,
1469    pub spectral_radius: f64,
1470    pub matrix_norm: f64,
1471    pub numerical_rank: usize,
1472    pub eigenvalue_spread: f64,
1473    pub structure_analysis: MatrixStructureAnalysis,
1474}
1475
1476// Honest dense numerical routines for quantum-gate-matrix analysis. Gate matrices
1477// are small, so they are materialized densely from their COO triplets and analysed
1478// exactly. Implemented self-contained on `scirs2_core::Complex64` (SciRS2 policy):
1479// scirs2-linalg's norm/cond/eigvalsh are real-valued (`F: Float`) and do not accept
1480// complex matrices without a real block embedding, so complex routines (power /
1481// inverse iteration, Jacobi eigenvalues, LU solve) are provided here.
1482const JACOBI_MAX_SWEEPS: usize = 128;
1483const JACOBI_OFFDIAG_EPS: f64 = 1e-15;
1484
1485/// Materialize into dense row-major storage `(dense, rows, cols)`, accumulating
1486/// duplicate triplets for the same `(row, col)`.
1487fn densify(matrix: &SciRSSparseMatrix<Complex64>) -> (Vec<Complex64>, usize, usize) {
1488    let (rows, cols) = matrix.shape;
1489    let mut dense = vec![Complex64::new(0.0, 0.0); rows.saturating_mul(cols)];
1490    for &(r, c, v) in &matrix.data {
1491        if r < rows && c < cols {
1492            dense[r * cols + c] += v;
1493        }
1494    }
1495    (dense, rows, cols)
1496}
1497
1498/// Frobenius inner product `Tr(A† B) = Σ conj(a_ij) · b_ij` from the triplets.
1499fn frobenius_inner(
1500    a: &SciRSSparseMatrix<Complex64>,
1501    b: &SciRSSparseMatrix<Complex64>,
1502) -> Complex64 {
1503    let mut a_map: HashMap<(usize, usize), Complex64> = HashMap::with_capacity(a.data.len());
1504    for &(r, c, v) in &a.data {
1505        *a_map.entry((r, c)).or_insert(Complex64::new(0.0, 0.0)) += v;
1506    }
1507    let mut b_map: HashMap<(usize, usize), Complex64> = HashMap::with_capacity(b.data.len());
1508    for &(r, c, v) in &b.data {
1509        *b_map.entry((r, c)).or_insert(Complex64::new(0.0, 0.0)) += v;
1510    }
1511    let mut acc = Complex64::new(0.0, 0.0);
1512    for (key, av) in &a_map {
1513        if let Some(bv) = b_map.get(key) {
1514            acc += av.conj() * bv;
1515        }
1516    }
1517    acc
1518}
1519
1520/// Cyclic Jacobi eigenvalue iteration for a real symmetric `n x n` matrix stored
1521/// row-major.  Returns the eigenvalues (diagonal after convergence) and, when
1522/// `want_vectors` is set, the orthogonal matrix whose columns are eigenvectors.
1523/// Jacobi is unconditionally convergent for symmetric input.
1524fn jacobi_symmetric(mut a: Vec<f64>, n: usize, want_vectors: bool) -> (Vec<f64>, Vec<f64>) {
1525    if n == 0 {
1526        return (Vec::new(), Vec::new());
1527    }
1528    let mut v = if want_vectors {
1529        let mut m = vec![0.0f64; n * n];
1530        for i in 0..n {
1531            m[i * n + i] = 1.0;
1532        }
1533        m
1534    } else {
1535        Vec::new()
1536    };
1537    for _ in 0..JACOBI_MAX_SWEEPS {
1538        let mut off = 0.0;
1539        for p in 0..n {
1540            for q in (p + 1)..n {
1541                off += a[p * n + q] * a[p * n + q];
1542            }
1543        }
1544        if off.sqrt() <= JACOBI_OFFDIAG_EPS {
1545            break;
1546        }
1547        for p in 0..n {
1548            for q in (p + 1)..n {
1549                let apq = a[p * n + q];
1550                if apq.abs() <= f64::MIN_POSITIVE {
1551                    continue;
1552                }
1553                let app = a[p * n + p];
1554                let aqq = a[q * n + q];
1555                let theta = (aqq - app) / (2.0 * apq);
1556                let t = if theta == 0.0 {
1557                    1.0
1558                } else {
1559                    let sign = if theta >= 0.0 { 1.0 } else { -1.0 };
1560                    sign / (theta.abs() + (theta * theta + 1.0).sqrt())
1561                };
1562                let c = 1.0 / (t * t + 1.0).sqrt();
1563                let s = t * c;
1564                for k in 0..n {
1565                    let akp = a[k * n + p];
1566                    let akq = a[k * n + q];
1567                    a[k * n + p] = c * akp - s * akq;
1568                    a[k * n + q] = s * akp + c * akq;
1569                }
1570                for k in 0..n {
1571                    let apk = a[p * n + k];
1572                    let aqk = a[q * n + k];
1573                    a[p * n + k] = c * apk - s * aqk;
1574                    a[q * n + k] = s * apk + c * aqk;
1575                }
1576                if want_vectors {
1577                    for k in 0..n {
1578                        let vkp = v[k * n + p];
1579                        let vkq = v[k * n + q];
1580                        v[k * n + p] = c * vkp - s * vkq;
1581                        v[k * n + q] = s * vkp + c * vkq;
1582                    }
1583                }
1584            }
1585        }
1586    }
1587    let eig = (0..n).map(|i| a[i * n + i]).collect();
1588    (eig, v)
1589}
1590
1591/// Real symmetric `2n x 2n` embedding `R = [[A, -B], [B, A]]` of a Hermitian
1592/// complex matrix `G = A + iB`. Eigenvalues of `R` equal those of `G` (doubled);
1593/// a real eigenvector `[p; q]` of `R` maps to the complex eigenvector `p + i·q`.
1594fn hermitian_real_embed(g: &[Complex64], n: usize) -> Vec<f64> {
1595    let m = 2 * n;
1596    let mut r = vec![0.0f64; m * m];
1597    for i in 0..n {
1598        for j in 0..n {
1599            let gij = g[i * n + j];
1600            r[i * m + j] = gij.re;
1601            r[i * m + (j + n)] = -gij.im;
1602            r[(i + n) * m + j] = gij.im;
1603            r[(i + n) * m + (j + n)] = gij.re;
1604        }
1605    }
1606    r
1607}
1608
1609/// Eigenvalues (descending) of a Hermitian complex matrix, via the real
1610/// symmetric embedding and Jacobi iteration.
1611fn hermitian_eigenvalues_dense(g: &[Complex64], n: usize) -> Vec<f64> {
1612    if n == 0 {
1613        return Vec::new();
1614    }
1615    let r = hermitian_real_embed(g, n);
1616    let (mut eig2, _) = jacobi_symmetric(r, 2 * n, false);
1617    eig2.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
1618    // Each true eigenvalue appears twice; take one representative per pair.
1619    (0..n).map(|k| eig2[2 * k]).collect()
1620}
1621
1622/// Singular values (descending) of a dense complex `rows x cols` matrix, from the
1623/// eigenvalues of the smaller Gram matrix (`M† M` or `M M†`).
1624fn singular_values_dense(dense: &[Complex64], rows: usize, cols: usize) -> Vec<f64> {
1625    if rows == 0 || cols == 0 {
1626        return Vec::new();
1627    }
1628    let (gram, dim) = if cols <= rows {
1629        // M† M : cols x cols,  g[i][j] = Σ_k conj(M[k][i]) M[k][j]
1630        let mut g = vec![Complex64::new(0.0, 0.0); cols * cols];
1631        for k in 0..rows {
1632            let base = k * cols;
1633            for i in 0..cols {
1634                let mki = dense[base + i].conj();
1635                for j in 0..cols {
1636                    g[i * cols + j] += mki * dense[base + j];
1637                }
1638            }
1639        }
1640        (g, cols)
1641    } else {
1642        // M M† : rows x rows,  g[i][j] = Σ_k M[i][k] conj(M[j][k])
1643        let mut g = vec![Complex64::new(0.0, 0.0); rows * rows];
1644        for i in 0..rows {
1645            for j in 0..rows {
1646                let mut acc = Complex64::new(0.0, 0.0);
1647                for k in 0..cols {
1648                    acc += dense[i * cols + k] * dense[j * cols + k].conj();
1649                }
1650                g[i * rows + j] = acc;
1651            }
1652        }
1653        (g, rows)
1654    };
1655    hermitian_eigenvalues_dense(&gram, dim)
1656        .into_iter()
1657        .map(|e| e.max(0.0).sqrt())
1658        .collect()
1659}
1660
1661/// Dense matrix-vector product `y = M x` for an `n x n` row-major matrix.
1662fn dense_matvec(m: &[Complex64], n: usize, x: &[Complex64]) -> Vec<Complex64> {
1663    let mut y = vec![Complex64::new(0.0, 0.0); n];
1664    for i in 0..n {
1665        let base = i * n;
1666        let mut acc = Complex64::new(0.0, 0.0);
1667        for j in 0..n {
1668            acc += m[base + j] * x[j];
1669        }
1670        y[i] = acc;
1671    }
1672    y
1673}
1674
1675/// Euclidean norm of a complex vector.
1676fn cvec_norm(x: &[Complex64]) -> f64 {
1677    x.iter().map(|v| v.norm_sqr()).sum::<f64>().sqrt()
1678}
1679
1680/// Deterministic non-degenerate starting vector for iterative eigen methods.
1681fn seed_vector(n: usize) -> Vec<Complex64> {
1682    (0..n)
1683        .map(|i| Complex64::new(1.0 + (i as f64) * 0.137, 0.31 - (i as f64) * 0.057))
1684        .collect()
1685}
1686
1687/// Spectral radius `ρ(M) = max|λ_i|` via power iteration, using the geometric mean
1688/// of the per-step growth `‖M x_k‖` (Gelfand's formula) so it converges for any
1689/// matrix, including unitary/degenerate spectra (`ρ = 1`).
1690fn spectral_radius_dense(m: &[Complex64], n: usize) -> f64 {
1691    if n == 0 {
1692        return 0.0;
1693    }
1694    let mut x = seed_vector(n);
1695    let norm0 = cvec_norm(&x);
1696    if norm0 == 0.0 {
1697        return 0.0;
1698    }
1699    for v in &mut x {
1700        *v /= norm0;
1701    }
1702    let burn_in = 40usize;
1703    let iters = 400usize;
1704    let mut log_sum = 0.0;
1705    let mut count = 0usize;
1706    for iter in 0..iters {
1707        let y = dense_matvec(m, n, &x);
1708        let ny = cvec_norm(&y);
1709        if ny <= 1e-300 {
1710            return 0.0;
1711        }
1712        if iter >= burn_in {
1713            log_sum += ny.ln();
1714            count += 1;
1715        }
1716        for i in 0..n {
1717            x[i] = y[i] / ny;
1718        }
1719    }
1720    if count == 0 {
1721        0.0
1722    } else {
1723        (log_sum / count as f64).exp()
1724    }
1725}
1726
1727/// LU factorization with partial pivoting of a dense `n x n` complex matrix.
1728/// Returns the combined `LU` storage and the pivot permutation, or `None` when a
1729/// (near-)singular column is encountered.
1730fn lu_factor(dense: &[Complex64], n: usize) -> Option<(Vec<Complex64>, Vec<usize>)> {
1731    let mut a = dense.to_vec();
1732    let mut piv: Vec<usize> = (0..n).collect();
1733    for k in 0..n {
1734        let mut p = k;
1735        let mut maxv = a[k * n + k].norm();
1736        for i in (k + 1)..n {
1737            let v = a[i * n + k].norm();
1738            if v > maxv {
1739                maxv = v;
1740                p = i;
1741            }
1742        }
1743        if maxv <= 1e-300 {
1744            return None;
1745        }
1746        if p != k {
1747            for j in 0..n {
1748                a.swap(k * n + j, p * n + j);
1749            }
1750            piv.swap(k, p);
1751        }
1752        let pivot = a[k * n + k];
1753        for i in (k + 1)..n {
1754            let factor = a[i * n + k] / pivot;
1755            a[i * n + k] = factor;
1756            for j in (k + 1)..n {
1757                let ajk = a[k * n + j];
1758                a[i * n + j] -= factor * ajk;
1759            }
1760        }
1761    }
1762    Some((a, piv))
1763}
1764
1765/// Solve `M x = b` given an LU factorization from [`lu_factor`].
1766fn lu_solve(lu: &(Vec<Complex64>, Vec<usize>), n: usize, b: &[Complex64]) -> Vec<Complex64> {
1767    let (a, piv) = lu;
1768    let mut x = vec![Complex64::new(0.0, 0.0); n];
1769    for i in 0..n {
1770        x[i] = b[piv[i]];
1771    }
1772    for i in 0..n {
1773        let mut sum = x[i];
1774        for j in 0..i {
1775            sum -= a[i * n + j] * x[j];
1776        }
1777        x[i] = sum;
1778    }
1779    for i in (0..n).rev() {
1780        let mut sum = x[i];
1781        for j in (i + 1)..n {
1782            sum -= a[i * n + j] * x[j];
1783        }
1784        x[i] = sum / a[i * n + i];
1785    }
1786    x
1787}
1788
1789/// Smallest eigenvalue magnitude `min|λ_i|` via inverse power iteration; returns
1790/// `0` when the LU factorization detects (near-)singularity (a zero eigenvalue).
1791fn min_eig_magnitude_dense(m: &[Complex64], n: usize) -> f64 {
1792    if n == 0 {
1793        return 0.0;
1794    }
1795    let Some(lu) = lu_factor(m, n) else {
1796        return 0.0;
1797    };
1798    let mut x = seed_vector(n);
1799    let norm0 = cvec_norm(&x);
1800    if norm0 == 0.0 {
1801        return 0.0;
1802    }
1803    for v in &mut x {
1804        *v /= norm0;
1805    }
1806    let burn_in = 40usize;
1807    let iters = 400usize;
1808    let mut log_sum = 0.0;
1809    let mut count = 0usize;
1810    for iter in 0..iters {
1811        let y = lu_solve(&lu, n, &x);
1812        let ny = cvec_norm(&y);
1813        if ny <= 1e-300 {
1814            return 0.0;
1815        }
1816        if iter >= burn_in {
1817            log_sum += ny.ln();
1818            count += 1;
1819        }
1820        for i in 0..n {
1821            x[i] = y[i] / ny;
1822        }
1823    }
1824    // The growth of ‖M⁻¹ x‖ converges to 1/min|λ|.
1825    let inv_growth = if count == 0 {
1826        0.0
1827    } else {
1828        (log_sum / count as f64).exp()
1829    };
1830    if inv_growth <= 1e-300 {
1831        0.0
1832    } else {
1833        1.0 / inv_growth
1834    }
1835}
1836
1837/// Complex eigenvalues of a normal (e.g. unitary/Hermitian) matrix `W`. The
1838/// Hermitian and anti-Hermitian parts commute, so the generic Hermitian
1839/// combination `H = (W+W†)/2 + γ·(W−W†)/(2i)` shares `W`'s eigenvectors and is
1840/// non-degenerate even for conjugate eigenvalue pairs; eigenvectors are recovered
1841/// via the real embedding and each eigenvalue read off with a Rayleigh quotient.
1842fn normal_eigenvalues_dense(w: &[Complex64], n: usize) -> Vec<Complex64> {
1843    if n == 0 {
1844        return Vec::new();
1845    }
1846    let gamma = 0.786_151_377_757_423_f64;
1847    let mut h = vec![Complex64::new(0.0, 0.0); n * n];
1848    for i in 0..n {
1849        for j in 0..n {
1850            let wij = w[i * n + j];
1851            let wji = w[j * n + i].conj();
1852            let hermitian = (wij + wji) * Complex64::new(0.5, 0.0);
1853            let anti = (wij - wji) / Complex64::new(0.0, 2.0);
1854            h[i * n + j] = hermitian + anti * Complex64::new(gamma, 0.0);
1855        }
1856    }
1857    let r = hermitian_real_embed(&h, n);
1858    let (eig2, vecs) = jacobi_symmetric(r, 2 * n, true);
1859    let mut idx: Vec<usize> = (0..2 * n).collect();
1860    idx.sort_by(|&x, &y| {
1861        eig2[y]
1862            .partial_cmp(&eig2[x])
1863            .unwrap_or(std::cmp::Ordering::Equal)
1864    });
1865    let m = 2 * n;
1866    let mut out = Vec::with_capacity(n);
1867    let mut k = 0usize;
1868    while k < 2 * n && out.len() < n {
1869        let col = idx[k];
1870        let mut u = vec![Complex64::new(0.0, 0.0); n];
1871        for (row_i, u_val) in u.iter_mut().enumerate() {
1872            let p = vecs[row_i * m + col];
1873            let q = vecs[(row_i + n) * m + col];
1874            *u_val = Complex64::new(p, q);
1875        }
1876        let wu = dense_matvec(w, n, &u);
1877        let mut num = Complex64::new(0.0, 0.0);
1878        let mut den = 0.0;
1879        for i in 0..n {
1880            num += u[i].conj() * wu[i];
1881            den += u[i].norm_sqr();
1882        }
1883        out.push(if den > 1e-300 {
1884            num / Complex64::new(den, 0.0)
1885        } else {
1886            Complex64::new(0.0, 0.0)
1887        });
1888        k += 2;
1889    }
1890    out
1891}
1892
1893/// Diamond-norm distance of two unitary channels from the eigenvalues of `W = A† B`:
1894/// `2` when `0` is inside the convex hull of the eigenvalues, else `2√(1−δ²)` with
1895/// `δ` the distance from the origin to the hull.
1896fn hull_diamond_distance(eig: &[Complex64]) -> f64 {
1897    let mut angles: Vec<f64> = eig
1898        .iter()
1899        .filter(|z| z.norm() > 1e-12)
1900        .map(|z| z.arg())
1901        .collect();
1902    if angles.is_empty() {
1903        return 0.0;
1904    }
1905    angles.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1906    let m = angles.len();
1907    let mut max_gap = angles[0] + std::f64::consts::TAU - angles[m - 1];
1908    for k in 1..m {
1909        let gap = angles[k] - angles[k - 1];
1910        if gap > max_gap {
1911            max_gap = gap;
1912        }
1913    }
1914    if max_gap <= std::f64::consts::PI {
1915        2.0
1916    } else {
1917        let spanned = std::f64::consts::TAU - max_gap;
1918        let delta = (spanned / 2.0).cos();
1919        2.0 * (1.0 - delta * delta).max(0.0).sqrt()
1920    }
1921}
1922
1923/// `1 / k!` for small `k` (used by the matrix-exponential Taylor series).
1924fn recip_factorial(k: u32) -> f64 {
1925    let mut f = 1.0_f64;
1926    for i in 1..=k {
1927        f *= f64::from(i);
1928    }
1929    1.0 / f
1930}
1931
1932/// Dense `n x n` identity.
1933fn dense_identity(n: usize) -> Vec<Complex64> {
1934    let mut m = vec![Complex64::new(0.0, 0.0); n * n];
1935    for i in 0..n {
1936        m[i * n + i] = Complex64::new(1.0, 0.0);
1937    }
1938    m
1939}
1940
1941/// Dense `n x n` complex matrix product `C = A · B`.
1942fn dense_matmul(a: &[Complex64], b: &[Complex64], n: usize) -> Vec<Complex64> {
1943    let mut c = vec![Complex64::new(0.0, 0.0); n * n];
1944    for i in 0..n {
1945        for k in 0..n {
1946            let a_ik = a[i * n + k];
1947            if a_ik.norm_sqr() == 0.0 {
1948                continue;
1949            }
1950            let brow = k * n;
1951            let crow = i * n;
1952            for j in 0..n {
1953                c[crow + j] += a_ik * b[brow + j];
1954            }
1955        }
1956    }
1957    c
1958}
1959
1960/// Max absolute row sum (∞-norm) of a dense `n x n` complex matrix.
1961fn dense_inf_norm(a: &[Complex64], n: usize) -> f64 {
1962    let mut max_row = 0.0_f64;
1963    for i in 0..n {
1964        let base = i * n;
1965        let row_sum: f64 = (0..n).map(|j| a[base + j].norm()).sum();
1966        if row_sum > max_row {
1967            max_row = row_sum;
1968        }
1969    }
1970    max_row
1971}
1972
1973/// Dense matrix exponential `exp(scale · M)` via scaling-and-squaring with a
1974/// truncated Taylor series — the standard `expm` algorithm, exact to machine
1975/// precision for the small matrices analysed here.
1976fn expm_dense(m: &[Complex64], n: usize, scale: f64) -> Vec<Complex64> {
1977    if n == 0 {
1978        return Vec::new();
1979    }
1980    let scale_c = Complex64::new(scale, 0.0);
1981    let a: Vec<Complex64> = m.iter().map(|&v| v * scale_c).collect();
1982    let norm = dense_inf_norm(&a, n);
1983    let s = if norm <= 0.5 {
1984        0u32
1985    } else {
1986        ((norm.log2().ceil().max(0.0) as u32) + 1).min(60)
1987    };
1988    let scaling = Complex64::new(2.0_f64.powi(-(s as i32)), 0.0);
1989    let a_scaled: Vec<Complex64> = a.iter().map(|&v| v * scaling).collect();
1990    let mut result = dense_identity(n);
1991    let mut term = dense_identity(n);
1992    for k in 1..=18u32 {
1993        term = dense_matmul(&term, &a_scaled, n);
1994        let inv_fact = Complex64::new(recip_factorial(k), 0.0);
1995        for (r, t) in result.iter_mut().zip(term.iter()) {
1996            *r += *t * inv_fact;
1997        }
1998    }
1999    for _ in 0..s {
2000        result = dense_matmul(&result, &result, n);
2001    }
2002    result
2003}