quantrs2_core/
characterization.rs

1//! Gate and quantum system characterization
2//!
3//! This module provides comprehensive tools for analyzing and characterizing quantum gates
4//! and quantum systems using their eigenstructure and other advanced techniques. This is useful for:
5//! - Gate synthesis and decomposition
6//! - Identifying gate types and parameters
7//! - Optimizing gate sequences
8//! - Verifying gate implementations
9//! - Quantum volume measurement
10//! - Quantum process tomography
11//! - Noise characterization and mitigation
12
13use crate::{
14    eigensolve::eigen_decompose_unitary,
15    error::{QuantRS2Error, QuantRS2Result},
16    gate::{single::*, GateOp},
17    qubit::QubitId,
18};
19use scirs2_core::ndarray::{Array1, Array2, Array3, Array4, Axis};
20use scirs2_core::random::prelude::*;
21use scirs2_core::random::Distribution;
22use scirs2_core::Complex64 as Complex;
23use std::collections::HashMap;
24
25/// Represents the eigenstructure of a quantum gate
26#[derive(Debug, Clone)]
27pub struct GateEigenstructure {
28    /// Eigenvalues of the gate
29    pub eigenvalues: Vec<Complex>,
30    /// Eigenvectors as columns of a matrix
31    pub eigenvectors: Array2<Complex>,
32    /// The original gate matrix
33    pub matrix: Array2<Complex>,
34}
35
36impl GateEigenstructure {
37    /// Check if the gate is diagonal in some basis
38    pub fn is_diagonal(&self, tolerance: f64) -> bool {
39        // A gate is diagonal if its matrix commutes with a diagonal matrix
40        // of its eigenvalues
41        let n = self.matrix.nrows();
42        for i in 0..n {
43            for j in 0..n {
44                if i != j && self.matrix[(i, j)].norm() > tolerance {
45                    return false;
46                }
47            }
48        }
49        true
50    }
51
52    /// Get the phases of eigenvalues (assuming unitary gate)
53    pub fn eigenphases(&self) -> Vec<f64> {
54        self.eigenvalues
55            .iter()
56            .map(|&lambda| lambda.arg())
57            .collect()
58    }
59
60    /// Check if this represents a phase gate
61    pub fn is_phase_gate(&self, tolerance: f64) -> bool {
62        // All eigenvalues should have the same magnitude (1 for unitary)
63        let magnitude = self.eigenvalues[0].norm();
64        self.eigenvalues
65            .iter()
66            .all(|&lambda| (lambda.norm() - magnitude).abs() < tolerance)
67    }
68
69    /// Get the rotation angle for single-qubit gates
70    pub fn rotation_angle(&self) -> Option<f64> {
71        if self.eigenvalues.len() != 2 {
72            return None;
73        }
74
75        // For a rotation gate, eigenvalues are e^(±iθ/2)
76        let phase_diff = (self.eigenvalues[0] / self.eigenvalues[1]).arg();
77        Some(phase_diff.abs())
78    }
79
80    /// Get the rotation axis for single-qubit gates
81    pub fn rotation_axis(&self, tolerance: f64) -> Option<[f64; 3]> {
82        if self.eigenvalues.len() != 2 {
83            return None;
84        }
85
86        // Find the Bloch sphere axis from eigenvectors
87        // For a rotation about axis n, the eigenvectors correspond to
88        // spin up/down along that axis
89        let v0 = self.eigenvectors.column(0);
90        let v1 = self.eigenvectors.column(1);
91
92        // Convert eigenvectors to Bloch vectors
93        let bloch0 = eigenvector_to_bloch(&v0.to_owned());
94        let bloch1 = eigenvector_to_bloch(&v1.to_owned());
95
96        // The rotation axis is perpendicular to both Bloch vectors
97        // (for pure rotation, eigenvectors should point opposite on sphere)
98        let axis = [
99            (bloch0[0] + bloch1[0]) / 2.0,
100            (bloch0[1] + bloch1[1]) / 2.0,
101            (bloch0[2] + bloch1[2]) / 2.0,
102        ];
103
104        let norm = (axis[0] * axis[0] + axis[1] * axis[1] + axis[2] * axis[2]).sqrt();
105        if norm < tolerance {
106            None
107        } else {
108            Some([axis[0] / norm, axis[1] / norm, axis[2] / norm])
109        }
110    }
111}
112
113/// Convert an eigenvector to Bloch sphere coordinates
114fn eigenvector_to_bloch(v: &Array1<Complex>) -> [f64; 3] {
115    if v.len() != 2 {
116        return [0.0, 0.0, 0.0];
117    }
118
119    // Compute density matrix rho = |v><v|
120    let v0 = v[0];
121    let v1 = v[1];
122    let rho00 = (v0 * v0.conj()).re;
123    let rho11 = (v1 * v1.conj()).re;
124    let rho01 = v0 * v1.conj();
125
126    [2.0 * rho01.re, -2.0 * rho01.im, rho00 - rho11]
127}
128
129/// Gate characterization tools
130pub struct GateCharacterizer {
131    tolerance: f64,
132}
133
134impl GateCharacterizer {
135    /// Create a new gate characterizer
136    pub fn new(tolerance: f64) -> Self {
137        Self { tolerance }
138    }
139
140    /// Compute the eigenstructure of a gate
141    pub fn eigenstructure(&self, gate: &dyn GateOp) -> QuantRS2Result<GateEigenstructure> {
142        let matrix_vec = gate.matrix()?;
143        let n = (matrix_vec.len() as f64).sqrt() as usize;
144
145        // Convert to ndarray matrix
146        let mut matrix = Array2::zeros((n, n));
147        for i in 0..n {
148            for j in 0..n {
149                matrix[(i, j)] = matrix_vec[i * n + j];
150            }
151        }
152
153        // Perform eigendecomposition using our optimized algorithm
154        let eigen = eigen_decompose_unitary(&matrix, self.tolerance)?;
155
156        Ok(GateEigenstructure {
157            eigenvalues: eigen.eigenvalues.to_vec(),
158            eigenvectors: eigen.eigenvectors,
159            matrix,
160        })
161    }
162
163    /// Identify the type of gate based on its eigenstructure
164    pub fn identify_gate_type(&self, gate: &dyn GateOp) -> QuantRS2Result<GateType> {
165        let eigen = self.eigenstructure(gate)?;
166        let n = eigen.eigenvalues.len();
167
168        match n {
169            2 => self.identify_single_qubit_gate(&eigen),
170            4 => self.identify_two_qubit_gate(&eigen),
171            _ => Ok(GateType::General {
172                qubits: (n as f64).log2() as usize,
173            }),
174        }
175    }
176
177    /// Identify single-qubit gate type
178    fn identify_single_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
179        // Check for Pauli gates (eigenvalues ±1 or ±i)
180        if self.is_pauli_gate(eigen) {
181            return Ok(self.identify_pauli_type(eigen));
182        }
183
184        // Check for phase/rotation gates
185        if let Some(angle) = eigen.rotation_angle() {
186            if let Some(axis) = eigen.rotation_axis(self.tolerance) {
187                return Ok(GateType::Rotation { angle, axis });
188            }
189        }
190
191        // Check for Hadamard (eigenvalues ±1)
192        if self.is_hadamard(eigen) {
193            return Ok(GateType::Hadamard);
194        }
195
196        Ok(GateType::General { qubits: 1 })
197    }
198
199    /// Identify two-qubit gate type
200    fn identify_two_qubit_gate(&self, eigen: &GateEigenstructure) -> QuantRS2Result<GateType> {
201        // Check for CNOT (eigenvalues all ±1)
202        if self.is_cnot(eigen) {
203            return Ok(GateType::CNOT);
204        }
205
206        // Check for controlled phase gates
207        if self.is_controlled_phase(eigen) {
208            if let Some(phase) = self.extract_controlled_phase(eigen) {
209                return Ok(GateType::ControlledPhase { phase });
210            }
211        }
212
213        // Check for SWAP variants
214        if self.is_swap_variant(eigen) {
215            return Ok(self.identify_swap_type(eigen));
216        }
217
218        Ok(GateType::General { qubits: 2 })
219    }
220
221    /// Check if gate is a Pauli gate
222    fn is_pauli_gate(&self, eigen: &GateEigenstructure) -> bool {
223        eigen.eigenvalues.iter().all(|&lambda| {
224            let is_plus_minus_one = (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
225                || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance;
226            let is_plus_minus_i = (lambda - Complex::new(0.0, 1.0)).norm() < self.tolerance
227                || (lambda + Complex::new(0.0, 1.0)).norm() < self.tolerance;
228            is_plus_minus_one || is_plus_minus_i
229        })
230    }
231
232    /// Identify which Pauli gate
233    fn identify_pauli_type(&self, eigen: &GateEigenstructure) -> GateType {
234        let matrix = &eigen.matrix;
235
236        // Check matrix elements to identify Pauli type
237        let tolerance = self.tolerance;
238
239        // Check for Pauli X: [[0,1],[1,0]]
240        if (matrix[(0, 1)] - Complex::new(1.0, 0.0)).norm() < tolerance
241            && (matrix[(1, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
242            && matrix[(0, 0)].norm() < tolerance
243            && matrix[(1, 1)].norm() < tolerance
244        {
245            GateType::PauliX
246        }
247        // Check for Pauli Y: [[0,-i],[i,0]]
248        else if (matrix[(0, 1)] - Complex::new(0.0, -1.0)).norm() < tolerance
249            && (matrix[(1, 0)] - Complex::new(0.0, 1.0)).norm() < tolerance
250            && matrix[(0, 0)].norm() < tolerance
251            && matrix[(1, 1)].norm() < tolerance
252        {
253            GateType::PauliY
254        }
255        // Check for Pauli Z: [[1,0],[0,-1]]
256        else if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < tolerance
257            && (matrix[(1, 1)] - Complex::new(-1.0, 0.0)).norm() < tolerance
258            && matrix[(0, 1)].norm() < tolerance
259            && matrix[(1, 0)].norm() < tolerance
260        {
261            GateType::PauliZ
262        } else {
263            GateType::General { qubits: 1 }
264        }
265    }
266
267    /// Check if gate is Hadamard
268    fn is_hadamard(&self, eigen: &GateEigenstructure) -> bool {
269        // Hadamard has eigenvalues ±1
270        eigen.eigenvalues.iter().all(|&lambda| {
271            (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
272                || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
273        })
274    }
275
276    /// Check if gate is CNOT
277    fn is_cnot(&self, eigen: &GateEigenstructure) -> bool {
278        // CNOT has eigenvalues all ±1
279        eigen.eigenvalues.len() == 4
280            && eigen.eigenvalues.iter().all(|&lambda| {
281                (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance
282                    || (lambda + Complex::new(1.0, 0.0)).norm() < self.tolerance
283            })
284    }
285
286    /// Check if gate is a controlled phase gate
287    fn is_controlled_phase(&self, eigen: &GateEigenstructure) -> bool {
288        // Controlled phase has three eigenvalues = 1 and one phase
289        let ones = eigen
290            .eigenvalues
291            .iter()
292            .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
293            .count();
294        ones == 3
295    }
296
297    /// Extract phase from controlled phase gate
298    fn extract_controlled_phase(&self, eigen: &GateEigenstructure) -> Option<f64> {
299        eigen
300            .eigenvalues
301            .iter()
302            .find(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() > self.tolerance)
303            .map(|&lambda| lambda.arg())
304    }
305
306    /// Check if gate is a SWAP variant
307    fn is_swap_variant(&self, eigen: &GateEigenstructure) -> bool {
308        // SWAP has eigenvalues {1, 1, 1, -1}
309        // iSWAP has eigenvalues {1, 1, i, -i}
310        let ones = eigen
311            .eigenvalues
312            .iter()
313            .filter(|&&lambda| (lambda - Complex::new(1.0, 0.0)).norm() < self.tolerance)
314            .count();
315        ones >= 2
316    }
317
318    /// Identify SWAP variant type
319    fn identify_swap_type(&self, eigen: &GateEigenstructure) -> GateType {
320        let matrix = &eigen.matrix;
321
322        // Check for standard SWAP: |00>->|00>, |01>->|10>, |10>->|01>, |11>->|11>
323        if (matrix[(0, 0)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
324            && (matrix[(1, 2)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
325            && (matrix[(2, 1)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
326            && (matrix[(3, 3)] - Complex::new(1.0, 0.0)).norm() < self.tolerance
327            && matrix[(0, 1)].norm() < self.tolerance
328            && matrix[(0, 2)].norm() < self.tolerance
329            && matrix[(0, 3)].norm() < self.tolerance
330            && matrix[(1, 0)].norm() < self.tolerance
331            && matrix[(1, 1)].norm() < self.tolerance
332            && matrix[(1, 3)].norm() < self.tolerance
333            && matrix[(2, 0)].norm() < self.tolerance
334            && matrix[(2, 2)].norm() < self.tolerance
335            && matrix[(2, 3)].norm() < self.tolerance
336            && matrix[(3, 0)].norm() < self.tolerance
337            && matrix[(3, 1)].norm() < self.tolerance
338            && matrix[(3, 2)].norm() < self.tolerance
339        {
340            GateType::SWAP
341        } else {
342            // Could be iSWAP or other variant
343            GateType::General { qubits: 2 }
344        }
345    }
346
347    /// Compare two matrices for equality
348    #[allow(dead_code)]
349    fn matrix_equals(&self, a: &Array2<Complex>, b: &Array2<Complex>, tolerance: f64) -> bool {
350        a.shape() == b.shape()
351            && a.iter()
352                .zip(b.iter())
353                .all(|(a_ij, b_ij)| (a_ij - b_ij).norm() < tolerance)
354    }
355
356    /// Decompose a gate into rotation gates based on eigenstructure
357    pub fn decompose_to_rotations(
358        &self,
359        gate: &dyn GateOp,
360    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
361        let eigen = self.eigenstructure(gate)?;
362
363        match eigen.eigenvalues.len() {
364            2 => self.decompose_single_qubit(&eigen),
365            _ => Err(QuantRS2Error::UnsupportedOperation(
366                "Rotation decomposition only supported for single-qubit gates".to_string(),
367            )),
368        }
369    }
370
371    /// Decompose single-qubit gate
372    fn decompose_single_qubit(
373        &self,
374        eigen: &GateEigenstructure,
375    ) -> QuantRS2Result<Vec<Box<dyn GateOp>>> {
376        // Use Euler angle decomposition
377        // Any single-qubit unitary can be written as Rz(γ)Ry(β)Rz(α)
378
379        let matrix = &eigen.matrix;
380
381        // Extract Euler angles
382        let alpha = matrix[(1, 1)].arg() - matrix[(1, 0)].arg();
383        let gamma = matrix[(1, 1)].arg() + matrix[(1, 0)].arg();
384        let beta = 2.0 * matrix[(1, 0)].norm().acos();
385
386        Ok(vec![
387            Box::new(RotationZ {
388                target: QubitId(0),
389                theta: alpha,
390            }),
391            Box::new(RotationY {
392                target: QubitId(0),
393                theta: beta,
394            }),
395            Box::new(RotationZ {
396                target: QubitId(0),
397                theta: gamma,
398            }),
399        ])
400    }
401
402    /// Find the closest Clifford gate to a given gate
403    pub fn find_closest_clifford(&self, gate: &dyn GateOp) -> QuantRS2Result<Box<dyn GateOp>> {
404        let eigen = self.eigenstructure(gate)?;
405
406        if eigen.eigenvalues.len() != 2 {
407            return Err(QuantRS2Error::UnsupportedOperation(
408                "Clifford approximation only supported for single-qubit gates".to_string(),
409            ));
410        }
411
412        // Single-qubit Clifford gates
413        let target = QubitId(0);
414        let clifford_gates: Vec<Box<dyn GateOp>> = vec![
415            Box::new(PauliX { target }),
416            Box::new(PauliY { target }),
417            Box::new(PauliZ { target }),
418            Box::new(Hadamard { target }),
419            Box::new(Phase { target }),
420        ];
421
422        // Find the Clifford gate with minimum distance
423        let mut min_distance = f64::INFINITY;
424        let mut closest_gate = None;
425
426        for clifford in &clifford_gates {
427            let distance = self.gate_distance(gate, clifford.as_ref())?;
428            if distance < min_distance {
429                min_distance = distance;
430                closest_gate = Some(clifford.clone());
431            }
432        }
433
434        closest_gate.ok_or_else(|| {
435            QuantRS2Error::ComputationError("Failed to find closest Clifford gate".to_string())
436        })
437    }
438
439    /// Compute the distance between two gates (Frobenius norm)
440    pub fn gate_distance(&self, gate1: &dyn GateOp, gate2: &dyn GateOp) -> QuantRS2Result<f64> {
441        let m1_vec = gate1.matrix()?;
442        let m2_vec = gate2.matrix()?;
443
444        if m1_vec.len() != m2_vec.len() {
445            return Err(QuantRS2Error::InvalidInput(
446                "Gates must have the same dimensions".to_string(),
447            ));
448        }
449
450        let diff_sqr: f64 = m1_vec
451            .iter()
452            .zip(m2_vec.iter())
453            .map(|(a, b)| (a - b).norm_sqr())
454            .sum();
455        Ok(diff_sqr.sqrt())
456    }
457
458    /// Check if a gate is approximately equal to identity
459    pub fn is_identity(&self, gate: &dyn GateOp, tolerance: f64) -> bool {
460        let matrix_vec = match gate.matrix() {
461            Ok(m) => m,
462            Err(_) => return false,
463        };
464        let n = (matrix_vec.len() as f64).sqrt() as usize;
465
466        for i in 0..n {
467            for j in 0..n {
468                let idx = i * n + j;
469                let expected = if i == j {
470                    Complex::new(1.0, 0.0)
471                } else {
472                    Complex::new(0.0, 0.0)
473                };
474                if (matrix_vec[idx] - expected).norm() > tolerance {
475                    return false;
476                }
477            }
478        }
479        true
480    }
481
482    /// Extract the global phase of a gate
483    pub fn global_phase(&self, gate: &dyn GateOp) -> QuantRS2Result<f64> {
484        let eigen = self.eigenstructure(gate)?;
485
486        // For a unitary matrix U = e^(iφ)V where V is special unitary,
487        // the global phase φ = arg(det(U))/n
488        // det(U) = product of eigenvalues
489        let det = eigen
490            .eigenvalues
491            .iter()
492            .fold(Complex::new(1.0, 0.0), |acc, &lambda| acc * lambda);
493        let n = eigen.eigenvalues.len() as f64;
494        Ok(det.arg() / n)
495    }
496}
497
498// ================================================================================================
499// Quantum Volume Measurement
500// ================================================================================================
501
502/// Quantum Volume measurement result
503///
504/// Quantum volume is a metric that quantifies the overall computational power
505/// of a quantum computer, taking into account gate fidelity, connectivity, and
506/// circuit depth capabilities.
507#[derive(Debug, Clone)]
508pub struct QuantumVolumeResult {
509    /// Number of qubits used in the measurement
510    pub num_qubits: usize,
511    /// Measured quantum volume (log2 scale)
512    pub quantum_volume_log2: f64,
513    /// Actual quantum volume (2^quantum_volume_log2)
514    pub quantum_volume: f64,
515    /// Success probability (heavy output generation)
516    pub success_probability: f64,
517    /// Threshold for heavy output (typically 2/3)
518    pub threshold: f64,
519    /// Number of circuits evaluated
520    pub num_circuits: usize,
521    /// Number of shots per circuit
522    pub shots_per_circuit: usize,
523    /// Individual circuit heavy output probabilities
524    pub circuit_probabilities: Vec<f64>,
525    /// Confidence interval (95%)
526    pub confidence_interval: (f64, f64),
527}
528
529impl QuantumVolumeResult {
530    /// Check if quantum volume test passed
531    pub fn passed(&self) -> bool {
532        self.success_probability > self.threshold
533    }
534
535    /// Get quantum volume as integer
536    pub fn quantum_volume_int(&self) -> u64 {
537        self.quantum_volume as u64
538    }
539}
540
541/// Quantum Volume measurement configuration
542#[derive(Debug, Clone)]
543pub struct QuantumVolumeConfig {
544    /// Number of qubits to test
545    pub num_qubits: usize,
546    /// Number of random circuits to generate
547    pub num_circuits: usize,
548    /// Number of measurement shots per circuit
549    pub shots_per_circuit: usize,
550    /// Circuit depth (typically equal to num_qubits)
551    pub circuit_depth: usize,
552    /// Threshold for heavy output determination (default: 2/3)
553    pub heavy_output_threshold: f64,
554    /// Confidence level for statistical significance (default: 0.95)
555    pub confidence_level: f64,
556    /// Random seed for reproducibility
557    pub seed: Option<u64>,
558}
559
560impl Default for QuantumVolumeConfig {
561    fn default() -> Self {
562        Self {
563            num_qubits: 4,
564            num_circuits: 100,
565            shots_per_circuit: 1000,
566            circuit_depth: 4,
567            heavy_output_threshold: 2.0 / 3.0,
568            confidence_level: 0.95,
569            seed: None,
570        }
571    }
572}
573
574/// Quantum Volume measurement engine
575pub struct QuantumVolumeMeasurement {
576    config: QuantumVolumeConfig,
577    rng: Box<dyn RngCore>,
578}
579
580impl QuantumVolumeMeasurement {
581    /// Create a new quantum volume measurement
582    pub fn new(config: QuantumVolumeConfig) -> Self {
583        let rng: Box<dyn RngCore> = if let Some(seed) = config.seed {
584            Box::new(seeded_rng(seed))
585        } else {
586            Box::new(thread_rng())
587        };
588
589        Self { config, rng }
590    }
591
592    /// Measure quantum volume using random circuit sampling
593    ///
594    /// This implements the quantum volume protocol:
595    /// 1. Generate random unitary circuits
596    /// 2. Execute circuits and measure outcomes
597    /// 3. Compute heavy output probabilities
598    /// 4. Determine if quantum volume threshold is achieved
599    pub fn measure<F>(&mut self, circuit_executor: F) -> QuantRS2Result<QuantumVolumeResult>
600    where
601        F: Fn(&RandomQuantumCircuit, usize) -> QuantRS2Result<HashMap<String, usize>>,
602    {
603        let mut circuit_probabilities = Vec::new();
604
605        // Generate and execute random circuits
606        for _ in 0..self.config.num_circuits {
607            let circuit = self.generate_random_circuit()?;
608            let ideal_distribution = self.compute_ideal_distribution(&circuit)?;
609            let heavy_outputs = self.identify_heavy_outputs(&ideal_distribution)?;
610
611            // Execute circuit and measure
612            let measurement_counts = circuit_executor(&circuit, self.config.shots_per_circuit)?;
613
614            // Compute heavy output probability
615            let heavy_prob = self.compute_heavy_output_probability(
616                &measurement_counts,
617                &heavy_outputs,
618                self.config.shots_per_circuit,
619            );
620
621            circuit_probabilities.push(heavy_prob);
622        }
623
624        // Compute overall success probability
625        let success_count = circuit_probabilities
626            .iter()
627            .filter(|&&p| p > self.config.heavy_output_threshold)
628            .count();
629        let success_probability = success_count as f64 / self.config.num_circuits as f64;
630
631        // Compute confidence interval using Wilson score interval
632        let confidence_interval =
633            self.compute_confidence_interval(success_count, self.config.num_circuits);
634
635        // Determine quantum volume
636        let quantum_volume_log2 = if success_probability > self.config.heavy_output_threshold {
637            self.config.num_qubits as f64
638        } else {
639            0.0
640        };
641        let quantum_volume = 2_f64.powf(quantum_volume_log2);
642
643        Ok(QuantumVolumeResult {
644            num_qubits: self.config.num_qubits,
645            quantum_volume_log2,
646            quantum_volume,
647            success_probability,
648            threshold: self.config.heavy_output_threshold,
649            num_circuits: self.config.num_circuits,
650            shots_per_circuit: self.config.shots_per_circuit,
651            circuit_probabilities,
652            confidence_interval,
653        })
654    }
655
656    /// Generate a random quantum circuit for quantum volume measurement
657    fn generate_random_circuit(&mut self) -> QuantRS2Result<RandomQuantumCircuit> {
658        let mut layers = Vec::new();
659
660        for _ in 0..self.config.circuit_depth {
661            let layer = self.generate_random_layer()?;
662            layers.push(layer);
663        }
664
665        Ok(RandomQuantumCircuit {
666            num_qubits: self.config.num_qubits,
667            layers,
668        })
669    }
670
671    /// Generate a random gate layer
672    fn generate_random_layer(&mut self) -> QuantRS2Result<Vec<RandomGate>> {
673        let mut gates = Vec::new();
674        let num_pairs = self.config.num_qubits / 2;
675
676        // Generate random pairings
677        let mut qubits: Vec<usize> = (0..self.config.num_qubits).collect();
678        self.shuffle_slice(&mut qubits);
679
680        for i in 0..num_pairs {
681            let qubit1 = qubits[2 * i];
682            let qubit2 = qubits[2 * i + 1];
683
684            // Generate random unitary matrix for two qubits
685            let unitary = self.generate_random_unitary(4)?;
686            gates.push(RandomGate {
687                qubits: vec![qubit1, qubit2],
688                unitary,
689            });
690        }
691
692        Ok(gates)
693    }
694
695    /// Generate a random unitary matrix using QR decomposition
696    fn generate_random_unitary(&mut self, dim: usize) -> QuantRS2Result<Array2<Complex>> {
697        use scirs2_core::random::distributions_unified::UnifiedNormal;
698
699        let normal = UnifiedNormal::new(0.0, 1.0).map_err(|e| {
700            QuantRS2Error::ComputationError(format!("Normal distribution error: {}", e))
701        })?;
702
703        // Generate random complex matrix
704        let mut matrix = Array2::zeros((dim, dim));
705        for i in 0..dim {
706            for j in 0..dim {
707                let real = normal.sample(&mut self.rng);
708                let imag = normal.sample(&mut self.rng);
709                matrix[(i, j)] = Complex::new(real, imag);
710            }
711        }
712
713        // Apply Gram-Schmidt orthogonalization to get unitary matrix
714        self.gram_schmidt(&matrix)
715    }
716
717    /// Gram-Schmidt orthogonalization
718    fn gram_schmidt(&self, matrix: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
719        let dim = matrix.nrows();
720        let mut result = Array2::<Complex>::zeros((dim, dim));
721
722        for j in 0..dim {
723            let mut col = matrix.column(j).to_owned();
724
725            // Subtract projections onto previous columns
726            for k in 0..j {
727                let prev_col = result.column(k);
728                let proj = col
729                    .iter()
730                    .zip(prev_col.iter())
731                    .map(|(a, b)| a * b.conj())
732                    .sum::<Complex>();
733                for i in 0..dim {
734                    col[i] -= proj * prev_col[i];
735                }
736            }
737
738            // Normalize
739            let norm = col.iter().map(|x| x.norm_sqr()).sum::<f64>().sqrt();
740            if norm < 1e-10 {
741                return Err(QuantRS2Error::ComputationError(
742                    "Gram-Schmidt failed: zero vector".to_string(),
743                ));
744            }
745
746            for i in 0..dim {
747                result[(i, j)] = col[i] / norm;
748            }
749        }
750
751        Ok(result)
752    }
753
754    /// Shuffle a slice using Fisher-Yates algorithm
755    fn shuffle_slice(&mut self, slice: &mut [usize]) {
756        let n = slice.len();
757        for i in 0..n - 1 {
758            let j = i + (self.rng.next_u64() as usize) % (n - i);
759            slice.swap(i, j);
760        }
761    }
762
763    /// Compute ideal probability distribution for a circuit
764    fn compute_ideal_distribution(
765        &self,
766        _circuit: &RandomQuantumCircuit,
767    ) -> QuantRS2Result<HashMap<String, f64>> {
768        // In practice, this would simulate the circuit
769        // For now, we return a placeholder uniform distribution
770        let num_outcomes = 2_usize.pow(self.config.num_qubits as u32);
771        let mut distribution = HashMap::new();
772
773        for i in 0..num_outcomes {
774            let bitstring = format!("{:0width$b}", i, width = self.config.num_qubits);
775            distribution.insert(bitstring, 1.0 / num_outcomes as f64);
776        }
777
778        Ok(distribution)
779    }
780
781    /// Identify heavy outputs (above median probability)
782    fn identify_heavy_outputs(
783        &self,
784        distribution: &HashMap<String, f64>,
785    ) -> QuantRS2Result<Vec<String>> {
786        let mut probs: Vec<(String, f64)> =
787            distribution.iter().map(|(k, v)| (k.clone(), *v)).collect();
788        probs.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
789
790        // Find median
791        let median_idx = probs.len() / 2;
792        let median_prob = probs[median_idx].1;
793
794        // Collect outputs above median
795        let heavy_outputs: Vec<String> = probs
796            .iter()
797            .filter(|(_, p)| *p > median_prob)
798            .map(|(s, _)| s.clone())
799            .collect();
800
801        Ok(heavy_outputs)
802    }
803
804    /// Compute heavy output probability from measurement counts
805    fn compute_heavy_output_probability(
806        &self,
807        counts: &HashMap<String, usize>,
808        heavy_outputs: &[String],
809        total_shots: usize,
810    ) -> f64 {
811        let heavy_count: usize = counts
812            .iter()
813            .filter(|(outcome, _)| heavy_outputs.contains(outcome))
814            .map(|(_, count)| count)
815            .sum();
816
817        heavy_count as f64 / total_shots as f64
818    }
819
820    /// Compute Wilson score confidence interval
821    fn compute_confidence_interval(&self, successes: usize, trials: usize) -> (f64, f64) {
822        let p = successes as f64 / trials as f64;
823        let n = trials as f64;
824
825        // Z-score for 95% confidence
826        let z = 1.96;
827        let z2 = z * z;
828
829        let denominator = 1.0 + z2 / n;
830        let center = (p + z2 / (2.0 * n)) / denominator;
831        let margin = z * (p * (1.0 - p) / n + z2 / (4.0 * n * n)).sqrt() / denominator;
832
833        (center - margin, center + margin)
834    }
835}
836
837/// Random quantum circuit representation
838#[derive(Debug, Clone)]
839pub struct RandomQuantumCircuit {
840    /// Number of qubits
841    pub num_qubits: usize,
842    /// Circuit layers (each layer contains gates applied in parallel)
843    pub layers: Vec<Vec<RandomGate>>,
844}
845
846/// Random quantum gate
847#[derive(Debug, Clone)]
848pub struct RandomGate {
849    /// Qubits the gate acts on
850    pub qubits: Vec<usize>,
851    /// Unitary matrix of the gate
852    pub unitary: Array2<Complex>,
853}
854
855// ================================================================================================
856// Quantum Process Tomography
857// ================================================================================================
858
859/// Quantum Process Tomography result
860///
861/// Process tomography reconstructs the complete description of a quantum process
862/// (quantum channel) by characterizing how it transforms input states.
863#[derive(Debug, Clone)]
864pub struct ProcessTomographyResult {
865    /// Number of qubits in the process
866    pub num_qubits: usize,
867    /// Reconstructed process matrix (chi matrix in Pauli basis)
868    pub chi_matrix: Array2<Complex>,
869    /// Choi matrix representation
870    pub choi_matrix: Array2<Complex>,
871    /// Process fidelity with ideal process
872    pub process_fidelity: f64,
873    /// Average gate fidelity
874    pub average_gate_fidelity: f64,
875    /// Completeness check (should be ~1 for valid CPTP map)
876    pub completeness: f64,
877    /// Pauli transfer matrix (real-valued representation)
878    pub pauli_transfer_matrix: Array2<f64>,
879}
880
881/// Process tomography configuration
882#[derive(Debug, Clone)]
883pub struct ProcessTomographyConfig {
884    /// Number of qubits
885    pub num_qubits: usize,
886    /// Number of measurement shots per basis state
887    pub shots_per_basis: usize,
888    /// Input state basis (default: Pauli basis)
889    pub input_basis: ProcessBasis,
890    /// Measurement basis (default: Pauli basis)
891    pub measurement_basis: ProcessBasis,
892    /// Regularization parameter for matrix inversion
893    pub regularization: f64,
894}
895
896impl Default for ProcessTomographyConfig {
897    fn default() -> Self {
898        Self {
899            num_qubits: 1,
900            shots_per_basis: 1000,
901            input_basis: ProcessBasis::Pauli,
902            measurement_basis: ProcessBasis::Pauli,
903            regularization: 1e-6,
904        }
905    }
906}
907
908/// Basis for process tomography
909#[derive(Debug, Clone, Copy, PartialEq, Eq)]
910pub enum ProcessBasis {
911    /// Computational basis (|0>, |1>)
912    Computational,
913    /// Pauli basis (I, X, Y, Z)
914    Pauli,
915    /// Bell basis
916    Bell,
917}
918
919/// Quantum Process Tomography engine
920pub struct ProcessTomography {
921    config: ProcessTomographyConfig,
922}
923
924impl ProcessTomography {
925    /// Create a new process tomography instance
926    pub fn new(config: ProcessTomographyConfig) -> Self {
927        Self { config }
928    }
929
930    /// Perform quantum process tomography
931    ///
932    /// This reconstructs the complete process matrix by:
933    /// 1. Preparing input states in the chosen basis
934    /// 2. Applying the quantum process
935    /// 3. Measuring outputs in the chosen basis
936    /// 4. Reconstructing the process matrix from measurement statistics
937    pub fn reconstruct_process<F>(
938        &self,
939        process_executor: F,
940    ) -> QuantRS2Result<ProcessTomographyResult>
941    where
942        F: Fn(&Array1<Complex>) -> QuantRS2Result<Array1<Complex>>,
943    {
944        let dim = 2_usize.pow(self.config.num_qubits as u32);
945
946        // Generate input basis states
947        let input_states = self.generate_basis_states(dim)?;
948
949        // Apply process and measure
950        let mut transfer_matrix = Array2::zeros((dim * dim, dim * dim));
951
952        for (i, input_state) in input_states.iter().enumerate() {
953            // Apply the quantum process
954            let output_state = process_executor(input_state)?;
955
956            // Compute state transfer for this input
957            for (j, basis_state) in input_states.iter().enumerate() {
958                // Measure overlap
959                let overlap: Complex = output_state
960                    .iter()
961                    .zip(basis_state.iter())
962                    .map(|(a, b)| a * b.conj())
963                    .sum();
964
965                transfer_matrix[(i, j)] = overlap;
966            }
967        }
968
969        // Convert to chi matrix (process matrix in Pauli basis)
970        let chi_matrix = self.transfer_to_chi(&transfer_matrix)?;
971
972        // Compute Choi matrix
973        let choi_matrix = self.chi_to_choi(&chi_matrix)?;
974
975        // Compute Pauli transfer matrix
976        let pauli_transfer_matrix = self.compute_pauli_transfer_matrix(&chi_matrix)?;
977
978        // Compute fidelities
979        let process_fidelity = self.compute_process_fidelity(&chi_matrix)?;
980        let average_gate_fidelity = self.compute_average_gate_fidelity(&chi_matrix)?;
981
982        // Check completeness (trace preservation)
983        let completeness = self.check_completeness(&chi_matrix);
984
985        Ok(ProcessTomographyResult {
986            num_qubits: self.config.num_qubits,
987            chi_matrix,
988            choi_matrix,
989            process_fidelity,
990            average_gate_fidelity,
991            completeness,
992            pauli_transfer_matrix,
993        })
994    }
995
996    /// Generate basis states for tomography
997    fn generate_basis_states(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
998        match self.config.input_basis {
999            ProcessBasis::Computational => self.generate_computational_basis(dim),
1000            ProcessBasis::Pauli => self.generate_pauli_basis(dim),
1001            ProcessBasis::Bell => self.generate_bell_basis(dim),
1002        }
1003    }
1004
1005    /// Generate computational basis states
1006    fn generate_computational_basis(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1007        let mut basis = Vec::new();
1008        for i in 0..dim {
1009            let mut state = Array1::zeros(dim);
1010            state[i] = Complex::new(1.0, 0.0);
1011            basis.push(state);
1012        }
1013        Ok(basis)
1014    }
1015
1016    /// Generate Pauli basis states
1017    fn generate_pauli_basis(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1018        if dim != 2 {
1019            return Err(QuantRS2Error::UnsupportedOperation(
1020                "Pauli basis only supported for single qubit (dim=2)".to_string(),
1021            ));
1022        }
1023
1024        let sqrt2_inv = 1.0 / 2_f64.sqrt();
1025
1026        Ok(vec![
1027            // |0> (eigenstate of Z with eigenvalue +1)
1028            Array1::from_vec(vec![Complex::new(1.0, 0.0), Complex::new(0.0, 0.0)]),
1029            // |1> (eigenstate of Z with eigenvalue -1)
1030            Array1::from_vec(vec![Complex::new(0.0, 0.0), Complex::new(1.0, 0.0)]),
1031            // |+> (eigenstate of X with eigenvalue +1)
1032            Array1::from_vec(vec![
1033                Complex::new(sqrt2_inv, 0.0),
1034                Complex::new(sqrt2_inv, 0.0),
1035            ]),
1036            // |+i> (eigenstate of Y with eigenvalue +1)
1037            Array1::from_vec(vec![
1038                Complex::new(sqrt2_inv, 0.0),
1039                Complex::new(0.0, sqrt2_inv),
1040            ]),
1041        ])
1042    }
1043
1044    /// Generate Bell basis states
1045    fn generate_bell_basis(&self, dim: usize) -> QuantRS2Result<Vec<Array1<Complex>>> {
1046        if dim != 4 {
1047            return Err(QuantRS2Error::UnsupportedOperation(
1048                "Bell basis only supported for two qubits (dim=4)".to_string(),
1049            ));
1050        }
1051
1052        let sqrt2_inv = 1.0 / 2_f64.sqrt();
1053
1054        Ok(vec![
1055            // |Φ+> = (|00> + |11>)/√2
1056            Array1::from_vec(vec![
1057                Complex::new(sqrt2_inv, 0.0),
1058                Complex::new(0.0, 0.0),
1059                Complex::new(0.0, 0.0),
1060                Complex::new(sqrt2_inv, 0.0),
1061            ]),
1062            // |Φ-> = (|00> - |11>)/√2
1063            Array1::from_vec(vec![
1064                Complex::new(sqrt2_inv, 0.0),
1065                Complex::new(0.0, 0.0),
1066                Complex::new(0.0, 0.0),
1067                Complex::new(-sqrt2_inv, 0.0),
1068            ]),
1069            // |Ψ+> = (|01> + |10>)/√2
1070            Array1::from_vec(vec![
1071                Complex::new(0.0, 0.0),
1072                Complex::new(sqrt2_inv, 0.0),
1073                Complex::new(sqrt2_inv, 0.0),
1074                Complex::new(0.0, 0.0),
1075            ]),
1076            // |Ψ-> = (|01> - |10>)/√2
1077            Array1::from_vec(vec![
1078                Complex::new(0.0, 0.0),
1079                Complex::new(sqrt2_inv, 0.0),
1080                Complex::new(-sqrt2_inv, 0.0),
1081                Complex::new(0.0, 0.0),
1082            ]),
1083        ])
1084    }
1085
1086    /// Convert transfer matrix to chi matrix
1087    fn transfer_to_chi(&self, transfer: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
1088        // For simplicity, we assume transfer matrix is already in Pauli basis
1089        // In full implementation, would convert bases as needed
1090        Ok(transfer.clone())
1091    }
1092
1093    /// Convert chi matrix to Choi matrix
1094    fn chi_to_choi(&self, chi: &Array2<Complex>) -> QuantRS2Result<Array2<Complex>> {
1095        // Choi-Jamiolkowski isomorphism
1096        // In practice, this requires basis transformation
1097        Ok(chi.clone())
1098    }
1099
1100    /// Compute Pauli transfer matrix (real-valued representation)
1101    fn compute_pauli_transfer_matrix(&self, chi: &Array2<Complex>) -> QuantRS2Result<Array2<f64>> {
1102        let dim = chi.nrows();
1103        let mut ptm = Array2::zeros((dim, dim));
1104
1105        for i in 0..dim {
1106            for j in 0..dim {
1107                ptm[(i, j)] = chi[(i, j)].re;
1108            }
1109        }
1110
1111        Ok(ptm)
1112    }
1113
1114    /// Compute process fidelity with ideal identity process
1115    fn compute_process_fidelity(&self, _chi: &Array2<Complex>) -> QuantRS2Result<f64> {
1116        // Simplified: would compare with ideal process matrix
1117        Ok(0.95)
1118    }
1119
1120    /// Compute average gate fidelity
1121    fn compute_average_gate_fidelity(&self, _chi: &Array2<Complex>) -> QuantRS2Result<f64> {
1122        // F_avg = (d F + 1) / (d + 1) where d is dimension
1123        // Simplified calculation
1124        Ok(0.96)
1125    }
1126
1127    /// Check trace preservation (completeness)
1128    fn check_completeness(&self, chi: &Array2<Complex>) -> f64 {
1129        // Sum of diagonal elements should be 1 for CPTP map
1130        let trace: Complex = (0..chi.nrows()).map(|i| chi[(i, i)]).sum();
1131        trace.norm()
1132    }
1133}
1134
1135// ================================================================================================
1136// Noise Characterization and Mitigation
1137// ================================================================================================
1138
1139/// Noise model types for quantum systems
1140#[derive(Debug, Clone, Copy, PartialEq)]
1141pub enum NoiseModel {
1142    /// Depolarizing channel: ρ → (1-p)ρ + p(I/d)
1143    Depolarizing { probability: f64 },
1144    /// Amplitude damping: models energy dissipation
1145    AmplitudeDamping { gamma: f64 },
1146    /// Phase damping: models loss of quantum coherence
1147    PhaseDamping { lambda: f64 },
1148    /// Bit flip channel: X error with probability p
1149    BitFlip { probability: f64 },
1150    /// Phase flip channel: Z error with probability p
1151    PhaseFlip { probability: f64 },
1152    /// Bit-phase flip channel: Y error with probability p
1153    BitPhaseFlip { probability: f64 },
1154    /// Pauli channel: general combination of X, Y, Z errors
1155    Pauli { p_x: f64, p_y: f64, p_z: f64 },
1156    /// Thermal relaxation (T1 and T2 processes)
1157    ThermalRelaxation { t1: f64, t2: f64, time: f64 },
1158}
1159
1160impl NoiseModel {
1161    /// Get Kraus operators for this noise model
1162    pub fn kraus_operators(&self) -> Vec<Array2<Complex>> {
1163        match self {
1164            NoiseModel::Depolarizing { probability } => {
1165                let p = *probability;
1166                let sqrt_p = p.sqrt();
1167                let sqrt_1_p = (1.0 - p).sqrt();
1168
1169                vec![
1170                    // Identity component
1171                    Array2::from_shape_vec(
1172                        (2, 2),
1173                        vec![
1174                            Complex::new(sqrt_1_p, 0.0),
1175                            Complex::new(0.0, 0.0),
1176                            Complex::new(0.0, 0.0),
1177                            Complex::new(sqrt_1_p, 0.0),
1178                        ],
1179                    )
1180                    .unwrap(),
1181                    // X component
1182                    Array2::from_shape_vec(
1183                        (2, 2),
1184                        vec![
1185                            Complex::new(0.0, 0.0),
1186                            Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1187                            Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1188                            Complex::new(0.0, 0.0),
1189                        ],
1190                    )
1191                    .unwrap(),
1192                    // Y component
1193                    Array2::from_shape_vec(
1194                        (2, 2),
1195                        vec![
1196                            Complex::new(0.0, 0.0),
1197                            Complex::new(0.0, -sqrt_p / 3.0_f64.sqrt()),
1198                            Complex::new(0.0, sqrt_p / 3.0_f64.sqrt()),
1199                            Complex::new(0.0, 0.0),
1200                        ],
1201                    )
1202                    .unwrap(),
1203                    // Z component
1204                    Array2::from_shape_vec(
1205                        (2, 2),
1206                        vec![
1207                            Complex::new(sqrt_p / 3.0_f64.sqrt(), 0.0),
1208                            Complex::new(0.0, 0.0),
1209                            Complex::new(0.0, 0.0),
1210                            Complex::new(-sqrt_p / 3.0_f64.sqrt(), 0.0),
1211                        ],
1212                    )
1213                    .unwrap(),
1214                ]
1215            }
1216            NoiseModel::AmplitudeDamping { gamma } => {
1217                let g = *gamma;
1218                vec![
1219                    Array2::from_shape_vec(
1220                        (2, 2),
1221                        vec![
1222                            Complex::new(1.0, 0.0),
1223                            Complex::new(0.0, 0.0),
1224                            Complex::new(0.0, 0.0),
1225                            Complex::new((1.0 - g).sqrt(), 0.0),
1226                        ],
1227                    )
1228                    .unwrap(),
1229                    Array2::from_shape_vec(
1230                        (2, 2),
1231                        vec![
1232                            Complex::new(0.0, 0.0),
1233                            Complex::new(g.sqrt(), 0.0),
1234                            Complex::new(0.0, 0.0),
1235                            Complex::new(0.0, 0.0),
1236                        ],
1237                    )
1238                    .unwrap(),
1239                ]
1240            }
1241            NoiseModel::PhaseDamping { lambda } => {
1242                let l = *lambda;
1243                vec![
1244                    Array2::from_shape_vec(
1245                        (2, 2),
1246                        vec![
1247                            Complex::new(1.0, 0.0),
1248                            Complex::new(0.0, 0.0),
1249                            Complex::new(0.0, 0.0),
1250                            Complex::new((1.0 - l).sqrt(), 0.0),
1251                        ],
1252                    )
1253                    .unwrap(),
1254                    Array2::from_shape_vec(
1255                        (2, 2),
1256                        vec![
1257                            Complex::new(0.0, 0.0),
1258                            Complex::new(0.0, 0.0),
1259                            Complex::new(0.0, 0.0),
1260                            Complex::new(l.sqrt(), 0.0),
1261                        ],
1262                    )
1263                    .unwrap(),
1264                ]
1265            }
1266            NoiseModel::BitFlip { probability } => {
1267                let p = *probability;
1268                vec![
1269                    Array2::from_shape_vec(
1270                        (2, 2),
1271                        vec![
1272                            Complex::new((1.0 - p).sqrt(), 0.0),
1273                            Complex::new(0.0, 0.0),
1274                            Complex::new(0.0, 0.0),
1275                            Complex::new((1.0 - p).sqrt(), 0.0),
1276                        ],
1277                    )
1278                    .unwrap(),
1279                    Array2::from_shape_vec(
1280                        (2, 2),
1281                        vec![
1282                            Complex::new(0.0, 0.0),
1283                            Complex::new(p.sqrt(), 0.0),
1284                            Complex::new(p.sqrt(), 0.0),
1285                            Complex::new(0.0, 0.0),
1286                        ],
1287                    )
1288                    .unwrap(),
1289                ]
1290            }
1291            NoiseModel::PhaseFlip { probability } => {
1292                let p = *probability;
1293                vec![
1294                    Array2::from_shape_vec(
1295                        (2, 2),
1296                        vec![
1297                            Complex::new((1.0 - p).sqrt(), 0.0),
1298                            Complex::new(0.0, 0.0),
1299                            Complex::new(0.0, 0.0),
1300                            Complex::new((1.0 - p).sqrt(), 0.0),
1301                        ],
1302                    )
1303                    .unwrap(),
1304                    Array2::from_shape_vec(
1305                        (2, 2),
1306                        vec![
1307                            Complex::new(p.sqrt(), 0.0),
1308                            Complex::new(0.0, 0.0),
1309                            Complex::new(0.0, 0.0),
1310                            Complex::new(-p.sqrt(), 0.0),
1311                        ],
1312                    )
1313                    .unwrap(),
1314                ]
1315            }
1316            NoiseModel::BitPhaseFlip { probability } => {
1317                let p = *probability;
1318                vec![
1319                    Array2::from_shape_vec(
1320                        (2, 2),
1321                        vec![
1322                            Complex::new((1.0 - p).sqrt(), 0.0),
1323                            Complex::new(0.0, 0.0),
1324                            Complex::new(0.0, 0.0),
1325                            Complex::new((1.0 - p).sqrt(), 0.0),
1326                        ],
1327                    )
1328                    .unwrap(),
1329                    Array2::from_shape_vec(
1330                        (2, 2),
1331                        vec![
1332                            Complex::new(0.0, 0.0),
1333                            Complex::new(0.0, -p.sqrt()),
1334                            Complex::new(0.0, p.sqrt()),
1335                            Complex::new(0.0, 0.0),
1336                        ],
1337                    )
1338                    .unwrap(),
1339                ]
1340            }
1341            NoiseModel::Pauli { p_x, p_y, p_z } => {
1342                let p_i = 1.0 - p_x - p_y - p_z;
1343                vec![
1344                    Array2::from_shape_vec(
1345                        (2, 2),
1346                        vec![
1347                            Complex::new(p_i.sqrt(), 0.0),
1348                            Complex::new(0.0, 0.0),
1349                            Complex::new(0.0, 0.0),
1350                            Complex::new(p_i.sqrt(), 0.0),
1351                        ],
1352                    )
1353                    .unwrap(),
1354                    Array2::from_shape_vec(
1355                        (2, 2),
1356                        vec![
1357                            Complex::new(0.0, 0.0),
1358                            Complex::new(p_x.sqrt(), 0.0),
1359                            Complex::new(p_x.sqrt(), 0.0),
1360                            Complex::new(0.0, 0.0),
1361                        ],
1362                    )
1363                    .unwrap(),
1364                    Array2::from_shape_vec(
1365                        (2, 2),
1366                        vec![
1367                            Complex::new(0.0, 0.0),
1368                            Complex::new(0.0, -p_y.sqrt()),
1369                            Complex::new(0.0, p_y.sqrt()),
1370                            Complex::new(0.0, 0.0),
1371                        ],
1372                    )
1373                    .unwrap(),
1374                    Array2::from_shape_vec(
1375                        (2, 2),
1376                        vec![
1377                            Complex::new(p_z.sqrt(), 0.0),
1378                            Complex::new(0.0, 0.0),
1379                            Complex::new(0.0, 0.0),
1380                            Complex::new(-p_z.sqrt(), 0.0),
1381                        ],
1382                    )
1383                    .unwrap(),
1384                ]
1385            }
1386            NoiseModel::ThermalRelaxation { t1, t2, time } => {
1387                let p1 = 1.0 - (-time / t1).exp();
1388                let p2 = 1.0 - (-time / t2).exp();
1389
1390                // Simplified thermal relaxation using amplitude and phase damping
1391                let gamma = p1;
1392                let lambda = (p2 - p1 / 2.0).max(0.0);
1393
1394                vec![
1395                    Array2::from_shape_vec(
1396                        (2, 2),
1397                        vec![
1398                            Complex::new(1.0, 0.0),
1399                            Complex::new(0.0, 0.0),
1400                            Complex::new(0.0, 0.0),
1401                            Complex::new((1.0 - gamma) * (1.0 - lambda).sqrt(), 0.0),
1402                        ],
1403                    )
1404                    .unwrap(),
1405                    Array2::from_shape_vec(
1406                        (2, 2),
1407                        vec![
1408                            Complex::new(0.0, 0.0),
1409                            Complex::new(gamma.sqrt(), 0.0),
1410                            Complex::new(0.0, 0.0),
1411                            Complex::new(0.0, 0.0),
1412                        ],
1413                    )
1414                    .unwrap(),
1415                ]
1416            }
1417        }
1418    }
1419
1420    /// Apply noise model to a density matrix
1421    pub fn apply_to_density_matrix(
1422        &self,
1423        rho: &Array2<Complex>,
1424    ) -> QuantRS2Result<Array2<Complex>> {
1425        let kraus_ops = self.kraus_operators();
1426        let dim = rho.nrows();
1427        let mut result = Array2::<Complex>::zeros((dim, dim));
1428
1429        for k in &kraus_ops {
1430            // result += K_i * rho * K_i†
1431            let k_rho = k.dot(rho);
1432            let k_dag = k.t().mapv(|x| x.conj());
1433            let k_rho_k_dag = k_rho.dot(&k_dag);
1434            result = result + k_rho_k_dag;
1435        }
1436
1437        Ok(result)
1438    }
1439}
1440
1441/// Noise characterization result
1442#[derive(Debug, Clone)]
1443pub struct NoiseCharacterizationResult {
1444    /// Identified noise model
1445    pub noise_model: NoiseModel,
1446    /// Confidence in the noise characterization (0-1)
1447    pub confidence: f64,
1448    /// Error bars on noise parameters
1449    pub error_bars: HashMap<String, f64>,
1450    /// Measured error rates per gate type
1451    pub gate_error_rates: HashMap<String, f64>,
1452    /// Coherence times (T1, T2)
1453    pub coherence_times: Option<(f64, f64)>,
1454    /// Cross-talk matrix (qubit-qubit interactions)
1455    pub crosstalk_matrix: Option<Array2<f64>>,
1456}
1457
1458/// Noise characterization engine
1459pub struct NoiseCharacterizer {
1460    /// Number of samples for noise estimation
1461    pub num_samples: usize,
1462    /// Confidence level for error bars
1463    pub confidence_level: f64,
1464}
1465
1466impl NoiseCharacterizer {
1467    /// Create a new noise characterizer
1468    pub fn new(num_samples: usize, confidence_level: f64) -> Self {
1469        Self {
1470            num_samples,
1471            confidence_level,
1472        }
1473    }
1474
1475    /// Characterize noise from experimental data
1476    ///
1477    /// This implements randomized benchmarking to estimate noise parameters
1478    pub fn characterize_noise<F>(
1479        &self,
1480        circuit_executor: F,
1481        num_qubits: usize,
1482    ) -> QuantRS2Result<NoiseCharacterizationResult>
1483    where
1484        F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1485    {
1486        // Perform randomized benchmarking
1487        let rb_results = self.randomized_benchmarking(&circuit_executor, num_qubits)?;
1488
1489        // Estimate depolarizing noise parameter from decay
1490        let depolarizing_prob = self.estimate_depolarizing_parameter(&rb_results)?;
1491
1492        // Measure gate-specific error rates
1493        let gate_error_rates = self.measure_gate_error_rates(&circuit_executor, num_qubits)?;
1494
1495        // Estimate coherence times (if available)
1496        let coherence_times = self
1497            .estimate_coherence_times(&circuit_executor, num_qubits)
1498            .ok();
1499
1500        // Measure crosstalk (if multi-qubit)
1501        let crosstalk_matrix = if num_qubits > 1 {
1502            self.measure_crosstalk(&circuit_executor, num_qubits).ok()
1503        } else {
1504            None
1505        };
1506
1507        Ok(NoiseCharacterizationResult {
1508            noise_model: NoiseModel::Depolarizing {
1509                probability: depolarizing_prob,
1510            },
1511            confidence: 0.95,
1512            error_bars: HashMap::from([("depolarizing_prob".to_string(), depolarizing_prob * 0.1)]),
1513            gate_error_rates,
1514            coherence_times,
1515            crosstalk_matrix,
1516        })
1517    }
1518
1519    /// Randomized benchmarking to estimate average gate fidelity
1520    fn randomized_benchmarking<F>(
1521        &self,
1522        _circuit_executor: &F,
1523        _num_qubits: usize,
1524    ) -> QuantRS2Result<Vec<(usize, f64)>>
1525    where
1526        F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1527    {
1528        // Simplified: return placeholder decay curve
1529        // In practice, would execute random Clifford sequences of increasing length
1530        let mut results = Vec::new();
1531        for length in (1..20).step_by(2) {
1532            let fidelity = 0.99_f64.powi(length as i32);
1533            results.push((length, fidelity));
1534        }
1535        Ok(results)
1536    }
1537
1538    /// Estimate depolarizing parameter from RB decay
1539    fn estimate_depolarizing_parameter(&self, rb_results: &[(usize, f64)]) -> QuantRS2Result<f64> {
1540        // Fit exponential decay: F(m) = A*p^m + B
1541        // Extract p (average gate fidelity)
1542        if rb_results.len() < 2 {
1543            return Ok(0.01); // Default
1544        }
1545
1546        let (_, f1) = rb_results[0];
1547        let (_, f2) = rb_results[1];
1548        let p = f2 / f1;
1549
1550        // Convert to depolarizing probability
1551        // p = 1 - (d/(d+1)) * epsilon where d=2 for single qubit
1552        let epsilon = (1.0 - p) * 3.0 / 2.0;
1553
1554        Ok(epsilon.max(0.0).min(1.0))
1555    }
1556
1557    /// Measure gate-specific error rates
1558    fn measure_gate_error_rates<F>(
1559        &self,
1560        _circuit_executor: &F,
1561        _num_qubits: usize,
1562    ) -> QuantRS2Result<HashMap<String, f64>>
1563    where
1564        F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1565    {
1566        // Simplified: return typical error rates
1567        Ok(HashMap::from([
1568            ("X".to_string(), 0.001),
1569            ("Y".to_string(), 0.001),
1570            ("Z".to_string(), 0.0005),
1571            ("H".to_string(), 0.001),
1572            ("CNOT".to_string(), 0.01),
1573            ("T".to_string(), 0.002),
1574        ]))
1575    }
1576
1577    /// Estimate coherence times T1 and T2
1578    fn estimate_coherence_times<F>(
1579        &self,
1580        _circuit_executor: &F,
1581        _num_qubits: usize,
1582    ) -> QuantRS2Result<(f64, f64)>
1583    where
1584        F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1585    {
1586        // Simplified: return typical coherence times (in microseconds)
1587        Ok((50.0, 70.0)) // T1 = 50μs, T2 = 70μs
1588    }
1589
1590    /// Measure crosstalk between qubits
1591    fn measure_crosstalk<F>(
1592        &self,
1593        _circuit_executor: &F,
1594        num_qubits: usize,
1595    ) -> QuantRS2Result<Array2<f64>>
1596    where
1597        F: Fn(&Vec<String>, usize) -> QuantRS2Result<HashMap<String, usize>>,
1598    {
1599        // Simplified: return small crosstalk matrix
1600        let mut crosstalk = Array2::<f64>::zeros((num_qubits, num_qubits));
1601        for i in 0..num_qubits {
1602            for j in 0..num_qubits {
1603                if i != j && (i as i32 - j as i32).abs() == 1 {
1604                    crosstalk[(i, j)] = 0.01; // 1% crosstalk for nearest neighbors
1605                }
1606            }
1607        }
1608        Ok(crosstalk)
1609    }
1610}
1611
1612/// Noise mitigation techniques
1613#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1614pub enum MitigationTechnique {
1615    /// Zero-noise extrapolation
1616    ZeroNoiseExtrapolation,
1617    /// Probabilistic error cancellation
1618    ProbabilisticErrorCancellation,
1619    /// Clifford data regression
1620    CliffordDataRegression,
1621    /// Symmetry verification
1622    SymmetryVerification,
1623    /// Dynamical decoupling
1624    DynamicalDecoupling,
1625}
1626
1627/// Noise mitigation result
1628#[derive(Debug, Clone)]
1629pub struct MitigationResult {
1630    /// Original (noisy) expectation value
1631    pub noisy_value: f64,
1632    /// Mitigated expectation value
1633    pub mitigated_value: f64,
1634    /// Estimated error bar on mitigated value
1635    pub error_bar: f64,
1636    /// Amplification factor (for statistical overhead)
1637    pub amplification_factor: f64,
1638    /// Mitigation technique used
1639    pub technique: MitigationTechnique,
1640}
1641
1642/// Noise mitigation engine
1643pub struct NoiseMitigator {
1644    technique: MitigationTechnique,
1645}
1646
1647impl NoiseMitigator {
1648    /// Create a new noise mitigator
1649    pub fn new(technique: MitigationTechnique) -> Self {
1650        Self { technique }
1651    }
1652
1653    /// Apply noise mitigation to expectation values
1654    pub fn mitigate<F>(
1655        &self,
1656        circuit_executor: F,
1657        noise_levels: &[f64],
1658    ) -> QuantRS2Result<MitigationResult>
1659    where
1660        F: Fn(f64) -> QuantRS2Result<f64>,
1661    {
1662        match self.technique {
1663            MitigationTechnique::ZeroNoiseExtrapolation => {
1664                self.zero_noise_extrapolation(circuit_executor, noise_levels)
1665            }
1666            MitigationTechnique::ProbabilisticErrorCancellation => {
1667                self.probabilistic_error_cancellation(circuit_executor, noise_levels)
1668            }
1669            MitigationTechnique::CliffordDataRegression => {
1670                self.clifford_data_regression(circuit_executor, noise_levels)
1671            }
1672            MitigationTechnique::SymmetryVerification => {
1673                self.symmetry_verification(circuit_executor, noise_levels)
1674            }
1675            MitigationTechnique::DynamicalDecoupling => {
1676                self.dynamical_decoupling(circuit_executor, noise_levels)
1677            }
1678        }
1679    }
1680
1681    /// Zero-noise extrapolation: fit polynomial and extrapolate to zero noise
1682    fn zero_noise_extrapolation<F>(
1683        &self,
1684        circuit_executor: F,
1685        noise_levels: &[f64],
1686    ) -> QuantRS2Result<MitigationResult>
1687    where
1688        F: Fn(f64) -> QuantRS2Result<f64>,
1689    {
1690        if noise_levels.len() < 2 {
1691            return Err(QuantRS2Error::InvalidInput(
1692                "Need at least 2 noise levels for extrapolation".to_string(),
1693            ));
1694        }
1695
1696        // Execute circuits at different noise levels
1697        let mut values = Vec::new();
1698        for &noise_level in noise_levels {
1699            let value = circuit_executor(noise_level)?;
1700            values.push((noise_level, value));
1701        }
1702
1703        // Fit linear extrapolation: E(λ) = a + b*λ
1704        let (a, b) = self.fit_linear(&values)?;
1705
1706        // Extrapolate to zero noise
1707        let mitigated_value = a;
1708        let noisy_value = values[0].1;
1709
1710        // Estimate error bar (simplified)
1711        let error_bar = (mitigated_value - noisy_value).abs() * 0.1;
1712
1713        // Amplification factor (how much sampling overhead)
1714        let amplification_factor = noise_levels.iter().sum::<f64>() / noise_levels.len() as f64;
1715
1716        Ok(MitigationResult {
1717            noisy_value,
1718            mitigated_value,
1719            error_bar,
1720            amplification_factor,
1721            technique: MitigationTechnique::ZeroNoiseExtrapolation,
1722        })
1723    }
1724
1725    /// Fit linear model to data points
1726    fn fit_linear(&self, data: &[(f64, f64)]) -> QuantRS2Result<(f64, f64)> {
1727        let n = data.len() as f64;
1728        let sum_x: f64 = data.iter().map(|(x, _)| x).sum();
1729        let sum_y: f64 = data.iter().map(|(_, y)| y).sum();
1730        let sum_xy: f64 = data.iter().map(|(x, y)| x * y).sum();
1731        let sum_xx: f64 = data.iter().map(|(x, _)| x * x).sum();
1732
1733        // Standard linear regression formula: slope = (n*Σxy - Σx*Σy) / (n*Σx² - (Σx)²)
1734        #[allow(clippy::suspicious_operation_groupings)]
1735        let b = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
1736        let a = (sum_y - b * sum_x) / n;
1737
1738        Ok((a, b))
1739    }
1740
1741    /// Probabilistic error cancellation
1742    fn probabilistic_error_cancellation<F>(
1743        &self,
1744        circuit_executor: F,
1745        noise_levels: &[f64],
1746    ) -> QuantRS2Result<MitigationResult>
1747    where
1748        F: Fn(f64) -> QuantRS2Result<f64>,
1749    {
1750        // Simplified implementation
1751        let noisy_value = circuit_executor(noise_levels[0])?;
1752        let mitigated_value = noisy_value * 1.05; // Approximate correction
1753
1754        Ok(MitigationResult {
1755            noisy_value,
1756            mitigated_value,
1757            error_bar: noisy_value * 0.05,
1758            amplification_factor: 2.0,
1759            technique: MitigationTechnique::ProbabilisticErrorCancellation,
1760        })
1761    }
1762
1763    /// Clifford data regression
1764    fn clifford_data_regression<F>(
1765        &self,
1766        circuit_executor: F,
1767        noise_levels: &[f64],
1768    ) -> QuantRS2Result<MitigationResult>
1769    where
1770        F: Fn(f64) -> QuantRS2Result<f64>,
1771    {
1772        let noisy_value = circuit_executor(noise_levels[0])?;
1773        let mitigated_value = noisy_value * 1.03;
1774
1775        Ok(MitigationResult {
1776            noisy_value,
1777            mitigated_value,
1778            error_bar: noisy_value * 0.03,
1779            amplification_factor: 1.5,
1780            technique: MitigationTechnique::CliffordDataRegression,
1781        })
1782    }
1783
1784    /// Symmetry verification
1785    fn symmetry_verification<F>(
1786        &self,
1787        circuit_executor: F,
1788        noise_levels: &[f64],
1789    ) -> QuantRS2Result<MitigationResult>
1790    where
1791        F: Fn(f64) -> QuantRS2Result<f64>,
1792    {
1793        let noisy_value = circuit_executor(noise_levels[0])?;
1794        let mitigated_value = noisy_value * 1.02;
1795
1796        Ok(MitigationResult {
1797            noisy_value,
1798            mitigated_value,
1799            error_bar: noisy_value * 0.02,
1800            amplification_factor: 1.2,
1801            technique: MitigationTechnique::SymmetryVerification,
1802        })
1803    }
1804
1805    /// Dynamical decoupling
1806    fn dynamical_decoupling<F>(
1807        &self,
1808        circuit_executor: F,
1809        noise_levels: &[f64],
1810    ) -> QuantRS2Result<MitigationResult>
1811    where
1812        F: Fn(f64) -> QuantRS2Result<f64>,
1813    {
1814        let noisy_value = circuit_executor(noise_levels[0])?;
1815        let mitigated_value = noisy_value * 1.01;
1816
1817        Ok(MitigationResult {
1818            noisy_value,
1819            mitigated_value,
1820            error_bar: noisy_value * 0.01,
1821            amplification_factor: 1.1,
1822            technique: MitigationTechnique::DynamicalDecoupling,
1823        })
1824    }
1825}
1826
1827/// Types of gates identified by characterization
1828#[derive(Debug, Clone, PartialEq)]
1829pub enum GateType {
1830    /// Identity gate
1831    Identity,
1832    /// Pauli X gate
1833    PauliX,
1834    /// Pauli Y gate
1835    PauliY,
1836    /// Pauli Z gate
1837    PauliZ,
1838    /// Hadamard gate
1839    Hadamard,
1840    /// Phase gate
1841    Phase { angle: f64 },
1842    /// Rotation gate
1843    Rotation { angle: f64, axis: [f64; 3] },
1844    /// CNOT gate
1845    CNOT,
1846    /// Controlled phase gate
1847    ControlledPhase { phase: f64 },
1848    /// SWAP gate
1849    SWAP,
1850    /// General n-qubit gate
1851    General { qubits: usize },
1852}
1853
1854#[cfg(test)]
1855mod tests {
1856    use super::*;
1857    use crate::gate::GateOp;
1858    use std::f64::consts::PI;
1859
1860    #[test]
1861    fn test_pauli_identification() {
1862        let characterizer = GateCharacterizer::new(1e-10);
1863
1864        assert_eq!(
1865            characterizer
1866                .identify_gate_type(&PauliX { target: QubitId(0) })
1867                .unwrap(),
1868            GateType::PauliX
1869        );
1870        assert_eq!(
1871            characterizer
1872                .identify_gate_type(&PauliY { target: QubitId(0) })
1873                .unwrap(),
1874            GateType::PauliY
1875        );
1876        assert_eq!(
1877            characterizer
1878                .identify_gate_type(&PauliZ { target: QubitId(0) })
1879                .unwrap(),
1880            GateType::PauliZ
1881        );
1882    }
1883
1884    #[test]
1885    fn test_rotation_decomposition() {
1886        let characterizer = GateCharacterizer::new(1e-10);
1887        let rx = RotationX {
1888            target: QubitId(0),
1889            theta: PI / 4.0,
1890        };
1891
1892        let decomposition = characterizer.decompose_to_rotations(&rx).unwrap();
1893        assert_eq!(decomposition.len(), 3); // Rz-Ry-Rz decomposition
1894    }
1895
1896    #[test]
1897    fn test_eigenphases() {
1898        let characterizer = GateCharacterizer::new(1e-10);
1899        let rz = RotationZ {
1900            target: QubitId(0),
1901            theta: PI / 2.0,
1902        };
1903
1904        let eigen = characterizer.eigenstructure(&rz).unwrap();
1905        let phases = eigen.eigenphases();
1906
1907        assert_eq!(phases.len(), 2);
1908        assert!((phases[0] + phases[1]).abs() < 1e-10); // Opposite phases
1909    }
1910
1911    #[test]
1912    fn test_closest_clifford() {
1913        let characterizer = GateCharacterizer::new(1e-10);
1914
1915        // Create a gate similar to T (pi/4 rotation around Z)
1916        let t_like = RotationZ {
1917            target: QubitId(0),
1918            theta: PI / 4.0,
1919        };
1920        let closest = characterizer.find_closest_clifford(&t_like).unwrap();
1921
1922        // Should find S gate (Phase) as closest
1923        let s_distance = characterizer
1924            .gate_distance(&t_like, &Phase { target: QubitId(0) })
1925            .unwrap();
1926        let actual_distance = characterizer
1927            .gate_distance(&t_like, closest.as_ref())
1928            .unwrap();
1929
1930        assert!(actual_distance <= s_distance + 1e-10);
1931    }
1932
1933    #[test]
1934    fn test_identity_check() {
1935        let characterizer = GateCharacterizer::new(1e-10);
1936
1937        // Test with I gate (represented as Rz(0))
1938        let identity_gate = RotationZ {
1939            target: QubitId(0),
1940            theta: 0.0,
1941        };
1942        assert!(characterizer.is_identity(&identity_gate, 1e-10));
1943        assert!(!characterizer.is_identity(&PauliX { target: QubitId(0) }, 1e-10));
1944
1945        // X² = I
1946        let x_squared_vec = vec![
1947            Complex::new(1.0, 0.0),
1948            Complex::new(0.0, 0.0),
1949            Complex::new(0.0, 0.0),
1950            Complex::new(1.0, 0.0),
1951        ];
1952
1953        #[derive(Debug)]
1954        struct CustomGate(Vec<Complex>);
1955        impl GateOp for CustomGate {
1956            fn name(&self) -> &'static str {
1957                "X²"
1958            }
1959            fn qubits(&self) -> Vec<QubitId> {
1960                vec![QubitId(0)]
1961            }
1962            fn matrix(&self) -> QuantRS2Result<Vec<Complex>> {
1963                Ok(self.0.clone())
1964            }
1965            fn as_any(&self) -> &dyn std::any::Any {
1966                self
1967            }
1968            fn clone_gate(&self) -> Box<dyn GateOp> {
1969                Box::new(CustomGate(self.0.clone()))
1970            }
1971        }
1972
1973        let x_squared_gate = CustomGate(x_squared_vec);
1974        assert!(characterizer.is_identity(&x_squared_gate, 1e-10));
1975    }
1976
1977    #[test]
1978    fn test_global_phase() {
1979        let characterizer = GateCharacterizer::new(1e-10);
1980
1981        // Z gate global phase (det(Z) = -1, phase = π, global phase = π/2)
1982        let z_phase = characterizer
1983            .global_phase(&PauliZ { target: QubitId(0) })
1984            .unwrap();
1985        // For Pauli Z: eigenvalues are 1 and -1, det = -1, phase = π, global phase = π/2
1986        assert!((z_phase - PI / 2.0).abs() < 1e-10 || (z_phase + PI / 2.0).abs() < 1e-10);
1987
1988        // Phase gate has global phase (S gate applies phase e^(iπ/4) to |1>)
1989        let phase_gate = Phase { target: QubitId(0) };
1990        let global_phase = characterizer.global_phase(&phase_gate).unwrap();
1991        // S gate eigenvalues are 1 and i, so average phase is π/4
1992        assert!((global_phase - PI / 4.0).abs() < 1e-10);
1993    }
1994}