quantrs2_sim/
open_quantum_systems.rs

1//! Open quantum system simulation with Lindblad master equations.
2//!
3//! This module provides comprehensive simulation capabilities for open quantum systems,
4//! including master equation evolution, Kraus operators, noise channels, and
5//! process tomography for realistic quantum device modeling.
6
7use crate::prelude::SimulatorError;
8use ndarray::{Array1, Array2, ArrayView1};
9use num_complex::Complex64;
10use scirs2_core::parallel_ops::*;
11use std::collections::HashMap;
12
13use crate::error::Result;
14use crate::scirs2_integration::SciRS2Backend;
15
16/// Lindblad master equation simulator for open quantum systems
17pub struct LindladSimulator {
18    /// Number of qubits
19    num_qubits: usize,
20    /// Current density matrix
21    density_matrix: Array2<Complex64>,
22    /// Lindblad operators
23    lindblad_ops: Vec<LindladOperator>,
24    /// System Hamiltonian
25    hamiltonian: Option<Array2<Complex64>>,
26    /// Time step for evolution
27    time_step: f64,
28    /// Integration method
29    integration_method: IntegrationMethod,
30    /// SciRS2 backend for linear algebra
31    backend: Option<SciRS2Backend>,
32}
33
34/// Lindblad operator with coefficient
35#[derive(Debug, Clone)]
36pub struct LindladOperator {
37    /// Operator matrix
38    pub operator: Array2<Complex64>,
39    /// Collapse rate
40    pub rate: f64,
41    /// Optional label for the operator
42    pub label: Option<String>,
43}
44
45/// Integration methods for master equation
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub enum IntegrationMethod {
48    /// Explicit Euler method (fast, less accurate)
49    Euler,
50    /// 4th order Runge-Kutta (balanced)
51    RungeKutta4,
52    /// Adaptive Runge-Kutta with error control
53    AdaptiveRK,
54    /// Matrix exponential (exact for time-independent systems)
55    MatrixExponential,
56}
57
58impl LindladSimulator {
59    /// Create new Lindblad simulator
60    pub fn new(num_qubits: usize) -> Result<Self> {
61        let dim = 1 << num_qubits;
62        let mut density_matrix = Array2::zeros((dim, dim));
63        density_matrix[[0, 0]] = Complex64::new(1.0, 0.0); // |0⟩⟨0|
64
65        Ok(Self {
66            num_qubits,
67            density_matrix,
68            lindblad_ops: Vec::new(),
69            hamiltonian: None,
70            time_step: 0.01,
71            integration_method: IntegrationMethod::RungeKutta4,
72            backend: None,
73        })
74    }
75
76    /// Initialize with SciRS2 backend
77    pub fn with_scirs2_backend(mut self) -> Result<Self> {
78        self.backend = Some(SciRS2Backend::new());
79        Ok(self)
80    }
81
82    /// Set initial density matrix
83    pub fn set_density_matrix(&mut self, rho: Array2<Complex64>) -> Result<()> {
84        let dim = 1 << self.num_qubits;
85        if rho.shape() != [dim, dim] {
86            return Err(SimulatorError::DimensionMismatch(format!(
87                "Expected {}x{} density matrix",
88                dim, dim
89            )));
90        }
91
92        // Verify trace normalization
93        let trace: Complex64 = rho.diag().iter().sum();
94        if (trace.re - 1.0).abs() > 1e-10 || trace.im.abs() > 1e-10 {
95            return Err(SimulatorError::InvalidInput(format!(
96                "Density matrix not normalized: trace = {}",
97                trace
98            )));
99        }
100
101        self.density_matrix = rho;
102        Ok(())
103    }
104
105    /// Initialize from pure state vector
106    pub fn from_state_vector(&mut self, psi: &ArrayView1<Complex64>) -> Result<()> {
107        let dim = 1 << self.num_qubits;
108        if psi.len() != dim {
109            return Err(SimulatorError::DimensionMismatch(format!(
110                "Expected state vector of length {}",
111                dim
112            )));
113        }
114
115        // Create density matrix |ψ⟩⟨ψ|
116        let mut rho = Array2::zeros((dim, dim));
117        for i in 0..dim {
118            for j in 0..dim {
119                rho[[i, j]] = psi[i] * psi[j].conj();
120            }
121        }
122
123        self.density_matrix = rho;
124        Ok(())
125    }
126
127    /// Add Lindblad operator
128    pub fn add_lindblad_operator(&mut self, operator: LindladOperator) -> Result<()> {
129        let dim = 1 << self.num_qubits;
130        if operator.operator.shape() != [dim, dim] {
131            return Err(SimulatorError::DimensionMismatch(format!(
132                "Operator must be {}x{}",
133                dim, dim
134            )));
135        }
136
137        self.lindblad_ops.push(operator);
138        Ok(())
139    }
140
141    /// Set system Hamiltonian
142    pub fn set_hamiltonian(&mut self, h: Array2<Complex64>) -> Result<()> {
143        let dim = 1 << self.num_qubits;
144        if h.shape() != [dim, dim] {
145            return Err(SimulatorError::DimensionMismatch(format!(
146                "Hamiltonian must be {}x{}",
147                dim, dim
148            )));
149        }
150
151        self.hamiltonian = Some(h);
152        Ok(())
153    }
154
155    /// Set time step for integration
156    pub fn set_time_step(&mut self, dt: f64) {
157        self.time_step = dt;
158    }
159
160    /// Set integration method
161    pub fn set_integration_method(&mut self, method: IntegrationMethod) {
162        self.integration_method = method;
163    }
164
165    /// Evolve system for given time
166    pub fn evolve(&mut self, total_time: f64) -> Result<EvolutionResult> {
167        let num_steps = (total_time / self.time_step).ceil() as usize;
168        let actual_dt = total_time / num_steps as f64;
169
170        let mut times = Vec::with_capacity(num_steps + 1);
171        let mut densities = Vec::new();
172        let mut purities = Vec::with_capacity(num_steps + 1);
173        let mut traces = Vec::with_capacity(num_steps + 1);
174
175        // Record initial state
176        times.push(0.0);
177        purities.push(self.purity());
178        traces.push(self.trace().re);
179
180        // Store initial density if requested (for small systems)
181        if self.num_qubits <= 4 {
182            densities.push(self.density_matrix.clone());
183        }
184
185        // Time evolution loop
186        for step in 0..num_steps {
187            match self.integration_method {
188                IntegrationMethod::Euler => {
189                    self.euler_step(actual_dt)?;
190                }
191                IntegrationMethod::RungeKutta4 => {
192                    self.runge_kutta4_step(actual_dt)?;
193                }
194                IntegrationMethod::AdaptiveRK => {
195                    self.adaptive_rk_step(actual_dt)?;
196                }
197                IntegrationMethod::MatrixExponential => {
198                    self.matrix_exponential_step(actual_dt)?;
199                }
200            }
201
202            let current_time = (step + 1) as f64 * actual_dt;
203            times.push(current_time);
204            purities.push(self.purity());
205            traces.push(self.trace().re);
206
207            if self.num_qubits <= 4 {
208                densities.push(self.density_matrix.clone());
209            }
210        }
211
212        Ok(EvolutionResult {
213            times,
214            densities,
215            purities,
216            traces,
217            final_density: self.density_matrix.clone(),
218        })
219    }
220
221    /// Single Euler integration step
222    fn euler_step(&mut self, dt: f64) -> Result<()> {
223        let derivative = self.compute_lindblad_derivative()?;
224
225        // ρ(t + dt) = ρ(t) + dt * dρ/dt
226        for ((i, j), drho_dt) in derivative.indexed_iter() {
227            self.density_matrix[[i, j]] += dt * drho_dt;
228        }
229
230        // Renormalize to maintain trace = 1
231        self.renormalize();
232
233        Ok(())
234    }
235
236    /// Single 4th order Runge-Kutta step
237    fn runge_kutta4_step(&mut self, dt: f64) -> Result<()> {
238        let rho0 = self.density_matrix.clone();
239
240        // k1 = f(t, y)
241        let k1 = self.compute_lindblad_derivative()?;
242
243        // k2 = f(t + dt/2, y + dt*k1/2)
244        self.density_matrix = &rho0 + &(&k1 * (dt / 2.0));
245        let k2 = self.compute_lindblad_derivative()?;
246
247        // k3 = f(t + dt/2, y + dt*k2/2)
248        self.density_matrix = &rho0 + &(&k2 * (dt / 2.0));
249        let k3 = self.compute_lindblad_derivative()?;
250
251        // k4 = f(t + dt, y + dt*k3)
252        self.density_matrix = &rho0 + &(&k3 * dt);
253        let k4 = self.compute_lindblad_derivative()?;
254
255        // y(t+dt) = y(t) + dt/6 * (k1 + 2*k2 + 2*k3 + k4)
256        let coeff = Complex64::new(dt / 6.0, 0.0);
257        self.density_matrix = rho0
258            + coeff
259                * (&k1 + &(Complex64::new(2.0, 0.0) * k2) + &(Complex64::new(2.0, 0.0) * k3) + &k4);
260
261        self.renormalize();
262        Ok(())
263    }
264
265    /// Adaptive Runge-Kutta step with error control
266    fn adaptive_rk_step(&mut self, dt: f64) -> Result<()> {
267        // For now, use fixed RK4 - full adaptive implementation would require
268        // error estimation and step size control
269        self.runge_kutta4_step(dt)
270    }
271
272    /// Matrix exponential step (exact for time-independent Lindbladian)
273    fn matrix_exponential_step(&mut self, dt: f64) -> Result<()> {
274        if let Some(ref backend) = self.backend {
275            // Use SciRS2 for matrix exponential if available
276            self.matrix_exp_with_scirs2(dt)
277        } else {
278            // Fallback to series expansion
279            self.matrix_exp_series(dt)
280        }
281    }
282
283    /// Matrix exponential using SciRS2
284    fn matrix_exp_with_scirs2(&mut self, dt: f64) -> Result<()> {
285        // This would use SciRS2's matrix exponential routines
286        // For now, fallback to series expansion
287        self.matrix_exp_series(dt)
288    }
289
290    /// Matrix exponential using series expansion
291    fn matrix_exp_series(&mut self, dt: f64) -> Result<()> {
292        let lindbladian = self.construct_lindbladian_superoperator()?;
293
294        // Compute matrix exponential using series: exp(L*dt) ≈ I + L*dt + (L*dt)²/2! + ...
295        let dim_sq = lindbladian.nrows();
296        let mut result = Array2::eye(dim_sq);
297        let mut term = Array2::eye(dim_sq);
298        let l_dt = &lindbladian * dt;
299
300        // Series expansion up to reasonable order
301        for n in 1..=20 {
302            term = term.dot(&l_dt) / n as f64;
303            result = result + &term;
304
305            // Check convergence
306            let term_norm: f64 = term.iter().map(|x| x.norm()).sum();
307            if term_norm < 1e-12 {
308                break;
309            }
310        }
311
312        // Apply to vectorized density matrix
313        let rho_vec = self.vectorize_density_matrix();
314        let new_rho_vec = result.dot(&rho_vec);
315        self.density_matrix = self.devectorize_density_matrix(&new_rho_vec);
316
317        self.renormalize();
318        Ok(())
319    }
320
321    /// Compute Lindblad master equation derivative
322    fn compute_lindblad_derivative(&self) -> Result<Array2<Complex64>> {
323        let dim = self.density_matrix.nrows();
324        let mut derivative = Array2::zeros((dim, dim));
325
326        // Hamiltonian evolution: -i[H, ρ]
327        if let Some(ref h) = self.hamiltonian {
328            let commutator = h.dot(&self.density_matrix) - self.density_matrix.dot(h);
329            derivative += &(commutator * Complex64::new(0.0, -1.0));
330        }
331
332        // Lindblad dissipation terms
333        for lindblad_op in &self.lindblad_ops {
334            let l = &lindblad_op.operator;
335            let l_dag = l.t().mapv(|x| x.conj());
336            let rate = lindblad_op.rate;
337
338            // Dissipation: γ(L ρ L† - (1/2){L†L, ρ})
339            let dissipation = l.dot(&self.density_matrix).dot(&l_dag);
340            let anticommutator =
341                l_dag.dot(l).dot(&self.density_matrix) + self.density_matrix.dot(&l_dag.dot(l));
342            let half = Complex64::new(0.5, 0.0);
343
344            derivative += &((dissipation - &anticommutator * half) * rate);
345        }
346
347        Ok(derivative)
348    }
349
350    /// Construct Lindbladian superoperator matrix
351    fn construct_lindbladian_superoperator(&self) -> Result<Array2<Complex64>> {
352        let dim = 1 << self.num_qubits;
353        let super_dim = dim * dim;
354        let mut lindbladian = Array2::zeros((super_dim, super_dim));
355
356        // Hamiltonian part: -i(H ⊗ I - I ⊗ H^T)
357        if let Some(ref h) = self.hamiltonian {
358            let eye: Array2<Complex64> = Array2::eye(dim);
359            let h_left = kron(&h, &eye);
360            let h_t = h.t().to_owned();
361            let h_right = kron(&eye, &h_t);
362            lindbladian += &((h_left - h_right) * Complex64::new(0.0, -1.0));
363        }
364
365        // Lindblad terms
366        for lindblad_op in &self.lindblad_ops {
367            let l = &lindblad_op.operator;
368            let l_dag = l.t().mapv(|x| x.conj());
369            let rate = lindblad_op.rate;
370
371            let eye: Array2<Complex64> = Array2::eye(dim);
372            let l_dag_l = l_dag.dot(l);
373
374            // L ⊗ L*
375            let left_term = kron(l, &l.mapv(|x| x.conj()));
376
377            // -(1/2)(L†L ⊗ I + I ⊗ (L†L)^T)
378            let l_dag_l_t = l_dag_l.t().to_owned();
379            let right_term = kron(&l_dag_l, &eye) + kron(&eye, &l_dag_l_t);
380            let half = Complex64::new(0.5, 0.0);
381
382            lindbladian += &((left_term - &right_term * half) * rate);
383        }
384
385        Ok(lindbladian)
386    }
387
388    /// Vectorize density matrix (column-major order)
389    fn vectorize_density_matrix(&self) -> Array1<Complex64> {
390        let dim = self.density_matrix.nrows();
391        let mut vec = Array1::zeros(dim * dim);
392
393        for (i, &val) in self.density_matrix.iter().enumerate() {
394            vec[i] = val;
395        }
396
397        vec
398    }
399
400    /// Devectorize density matrix
401    fn devectorize_density_matrix(&self, vec: &Array1<Complex64>) -> Array2<Complex64> {
402        let dim = (vec.len() as f64).sqrt() as usize;
403        Array2::from_shape_vec((dim, dim), vec.to_vec()).unwrap()
404    }
405
406    /// Get current purity Tr(ρ²)
407    pub fn purity(&self) -> f64 {
408        let rho_squared = self.density_matrix.dot(&self.density_matrix);
409        rho_squared.diag().iter().map(|x| x.re).sum()
410    }
411
412    /// Get current trace
413    pub fn trace(&self) -> Complex64 {
414        self.density_matrix.diag().iter().sum()
415    }
416
417    /// Renormalize density matrix to unit trace
418    fn renormalize(&mut self) {
419        let trace = self.trace();
420        if trace.norm() > 1e-12 {
421            self.density_matrix /= trace;
422        }
423    }
424
425    /// Get current density matrix
426    pub fn get_density_matrix(&self) -> &Array2<Complex64> {
427        &self.density_matrix
428    }
429
430    /// Compute expectation value of observable
431    pub fn expectation_value(&self, observable: &Array2<Complex64>) -> Result<Complex64> {
432        if observable.shape() != self.density_matrix.shape() {
433            return Err(SimulatorError::DimensionMismatch(
434                "Observable and density matrix dimensions must match".to_string(),
435            ));
436        }
437
438        // Tr(ρ O)
439        let product = self.density_matrix.dot(observable);
440        Ok(product.diag().iter().sum())
441    }
442}
443
444/// Result of time evolution
445#[derive(Debug, Clone)]
446pub struct EvolutionResult {
447    /// Time points
448    pub times: Vec<f64>,
449    /// Density matrices at each time (if stored)
450    pub densities: Vec<Array2<Complex64>>,
451    /// Purity at each time
452    pub purities: Vec<f64>,
453    /// Trace at each time
454    pub traces: Vec<f64>,
455    /// Final density matrix
456    pub final_density: Array2<Complex64>,
457}
458
459/// Quantum channel represented by Kraus operators
460#[derive(Debug, Clone)]
461pub struct QuantumChannel {
462    /// Kraus operators
463    pub kraus_operators: Vec<Array2<Complex64>>,
464    /// Channel name
465    pub name: String,
466}
467
468impl QuantumChannel {
469    /// Create depolarizing channel
470    pub fn depolarizing(num_qubits: usize, probability: f64) -> Self {
471        let dim = 1 << num_qubits;
472        let mut kraus_ops = Vec::new();
473
474        // Identity term
475        let sqrt_p0 = (1.0 - probability).sqrt();
476        let eye: Array2<Complex64> = Array2::eye(dim) * Complex64::new(sqrt_p0, 0.0);
477        kraus_ops.push(eye);
478
479        // Pauli terms
480        if num_qubits == 1 {
481            let sqrt_p = (probability / 3.0).sqrt();
482
483            // Pauli X
484            let mut pauli_x = Array2::zeros((2, 2));
485            pauli_x[[0, 1]] = Complex64::new(sqrt_p, 0.0);
486            pauli_x[[1, 0]] = Complex64::new(sqrt_p, 0.0);
487            kraus_ops.push(pauli_x);
488
489            // Pauli Y
490            let mut pauli_y = Array2::zeros((2, 2));
491            pauli_y[[0, 1]] = Complex64::new(0.0, -sqrt_p);
492            pauli_y[[1, 0]] = Complex64::new(0.0, sqrt_p);
493            kraus_ops.push(pauli_y);
494
495            // Pauli Z
496            let mut pauli_z = Array2::zeros((2, 2));
497            pauli_z[[0, 0]] = Complex64::new(sqrt_p, 0.0);
498            pauli_z[[1, 1]] = Complex64::new(-sqrt_p, 0.0);
499            kraus_ops.push(pauli_z);
500        }
501
502        Self {
503            kraus_operators: kraus_ops,
504            name: format!("Depolarizing({:.3})", probability),
505        }
506    }
507
508    /// Create amplitude damping channel
509    pub fn amplitude_damping(gamma: f64) -> Self {
510        let mut kraus_ops = Vec::new();
511
512        // K0 = |0⟩⟨0| + √(1-γ)|1⟩⟨1|
513        let mut k0 = Array2::zeros((2, 2));
514        k0[[0, 0]] = Complex64::new(1.0, 0.0);
515        k0[[1, 1]] = Complex64::new((1.0 - gamma).sqrt(), 0.0);
516        kraus_ops.push(k0);
517
518        // K1 = √γ|0⟩⟨1|
519        let mut k1 = Array2::zeros((2, 2));
520        k1[[0, 1]] = Complex64::new(gamma.sqrt(), 0.0);
521        kraus_ops.push(k1);
522
523        Self {
524            kraus_operators: kraus_ops,
525            name: format!("AmplitudeDamping({:.3})", gamma),
526        }
527    }
528
529    /// Create phase damping channel
530    pub fn phase_damping(gamma: f64) -> Self {
531        let mut kraus_ops = Vec::new();
532
533        // K0 = |0⟩⟨0| + √(1-γ)|1⟩⟨1|
534        let mut k0 = Array2::zeros((2, 2));
535        k0[[0, 0]] = Complex64::new(1.0, 0.0);
536        k0[[1, 1]] = Complex64::new((1.0 - gamma).sqrt(), 0.0);
537        kraus_ops.push(k0);
538
539        // K1 = √γ|1⟩⟨1|
540        let mut k1 = Array2::zeros((2, 2));
541        k1[[1, 1]] = Complex64::new(gamma.sqrt(), 0.0);
542        kraus_ops.push(k1);
543
544        Self {
545            kraus_operators: kraus_ops,
546            name: format!("PhaseDamping({:.3})", gamma),
547        }
548    }
549
550    /// Apply channel to density matrix
551    pub fn apply(&self, rho: &Array2<Complex64>) -> Array2<Complex64> {
552        let dim = rho.nrows();
553        let mut result = Array2::zeros((dim, dim));
554
555        for kraus_op in &self.kraus_operators {
556            let k_dag = kraus_op.t().mapv(|x| x.conj());
557            result += &kraus_op.dot(rho).dot(&k_dag);
558        }
559
560        result
561    }
562
563    /// Verify channel is trace-preserving
564    pub fn is_trace_preserving(&self) -> bool {
565        let dim = self.kraus_operators[0].nrows();
566        let mut sum = Array2::zeros((dim, dim));
567
568        for kraus_op in &self.kraus_operators {
569            let k_dag = kraus_op.t().mapv(|x| x.conj());
570            sum += &k_dag.dot(kraus_op);
571        }
572
573        // Check if sum ≈ I
574        let eye: Array2<Complex64> = Array2::eye(dim);
575        (&sum - &eye).iter().all(|&x| x.norm() < 1e-10)
576    }
577}
578
579/// Process tomography for quantum channel characterization
580pub struct ProcessTomography {
581    /// Input basis states
582    pub input_states: Vec<Array2<Complex64>>,
583    /// Output measurements
584    pub output_measurements: Vec<Array2<Complex64>>,
585    /// Reconstructed process matrix
586    pub process_matrix: Option<Array2<Complex64>>,
587}
588
589impl ProcessTomography {
590    /// Create standard process tomography setup
591    pub fn new(num_qubits: usize) -> Self {
592        let mut input_states = Vec::new();
593
594        // Prepare complete basis of density matrices
595        if num_qubits == 1 {
596            // Single qubit: |0⟩, |1⟩, |+⟩, |+i⟩
597            let mut rho_0 = Array2::zeros((2, 2));
598            rho_0[[0, 0]] = Complex64::new(1.0, 0.0);
599            input_states.push(rho_0);
600
601            let mut rho_1 = Array2::zeros((2, 2));
602            rho_1[[1, 1]] = Complex64::new(1.0, 0.0);
603            input_states.push(rho_1);
604
605            let mut rho_plus = Array2::zeros((2, 2));
606            rho_plus[[0, 0]] = Complex64::new(0.5, 0.0);
607            rho_plus[[0, 1]] = Complex64::new(0.5, 0.0);
608            rho_plus[[1, 0]] = Complex64::new(0.5, 0.0);
609            rho_plus[[1, 1]] = Complex64::new(0.5, 0.0);
610            input_states.push(rho_plus);
611
612            let mut rho_plus_i = Array2::zeros((2, 2));
613            rho_plus_i[[0, 0]] = Complex64::new(0.5, 0.0);
614            rho_plus_i[[0, 1]] = Complex64::new(0.0, -0.5);
615            rho_plus_i[[1, 0]] = Complex64::new(0.0, 0.5);
616            rho_plus_i[[1, 1]] = Complex64::new(0.5, 0.0);
617            input_states.push(rho_plus_i);
618        }
619
620        Self {
621            input_states,
622            output_measurements: Vec::new(),
623            process_matrix: None,
624        }
625    }
626
627    /// Perform tomography on quantum channel
628    pub fn characterize_channel(&mut self, channel: &QuantumChannel) -> Result<()> {
629        self.output_measurements.clear();
630
631        for input_state in &self.input_states {
632            let output = channel.apply(input_state);
633            self.output_measurements.push(output);
634        }
635
636        self.reconstruct_process_matrix()?;
637        Ok(())
638    }
639
640    /// Reconstruct process matrix from measurements
641    fn reconstruct_process_matrix(&mut self) -> Result<()> {
642        // Simplified reconstruction - full implementation would use
643        // maximum likelihood estimation or linear inversion
644
645        let dim = self.input_states[0].nrows();
646        let process_dim = dim * dim;
647        let mut chi = Array2::zeros((process_dim, process_dim));
648
649        // This is a placeholder - real process tomography requires
650        // solving a linear system to find the χ matrix
651        self.process_matrix = Some(chi);
652
653        Ok(())
654    }
655
656    /// Get process fidelity
657    pub fn process_fidelity(&self, ideal_channel: &QuantumChannel) -> Result<f64> {
658        if self.output_measurements.is_empty() {
659            return Err(SimulatorError::InvalidOperation(
660                "No measurements available for fidelity calculation".to_string(),
661            ));
662        }
663
664        let mut fidelity_sum = 0.0;
665
666        for (i, input_state) in self.input_states.iter().enumerate() {
667            let ideal_output = ideal_channel.apply(input_state);
668            let measured_output = &self.output_measurements[i];
669
670            // Simplified fidelity calculation
671            let fidelity = quantum_fidelity(measured_output, &ideal_output);
672            fidelity_sum += fidelity;
673        }
674
675        Ok(fidelity_sum / self.input_states.len() as f64)
676    }
677}
678
679/// Compute quantum fidelity between two density matrices
680pub fn quantum_fidelity(rho1: &Array2<Complex64>, rho2: &Array2<Complex64>) -> f64 {
681    // F(ρ₁, ρ₂) = Tr(√(√ρ₁ ρ₂ √ρ₁))²
682    // Simplified calculation for small systems
683
684    // For pure states: F = |⟨ψ₁|ψ₂⟩|²
685    // For mixed states, approximate with trace distance
686    let trace_distance = (rho1 - rho2).iter().map(|x| x.norm()).sum::<f64>();
687    (1.0 - 0.5 * trace_distance).max(0.0)
688}
689
690/// Kronecker product of two matrices
691fn kron(a: &Array2<Complex64>, b: &Array2<Complex64>) -> Array2<Complex64> {
692    let (m1, n1) = a.dim();
693    let (m2, n2) = b.dim();
694    let mut result = Array2::zeros((m1 * m2, n1 * n2));
695
696    for i in 0..m1 {
697        for j in 0..n1 {
698            for k in 0..m2 {
699                for l in 0..n2 {
700                    result[[i * m2 + k, j * n2 + l]] = a[[i, j]] * b[[k, l]];
701                }
702            }
703        }
704    }
705
706    result
707}
708
709/// Noise model builder for common quantum channels
710pub struct NoiseModelBuilder {
711    channels: HashMap<String, QuantumChannel>,
712    application_order: Vec<String>,
713}
714
715impl NoiseModelBuilder {
716    pub fn new() -> Self {
717        Self {
718            channels: HashMap::new(),
719            application_order: Vec::new(),
720        }
721    }
722
723    /// Add depolarizing noise
724    pub fn depolarizing(mut self, name: &str, probability: f64) -> Self {
725        let channel = QuantumChannel::depolarizing(1, probability);
726        self.channels.insert(name.to_string(), channel);
727        self.application_order.push(name.to_string());
728        self
729    }
730
731    /// Add amplitude damping
732    pub fn amplitude_damping(mut self, name: &str, gamma: f64) -> Self {
733        let channel = QuantumChannel::amplitude_damping(gamma);
734        self.channels.insert(name.to_string(), channel);
735        self.application_order.push(name.to_string());
736        self
737    }
738
739    /// Add phase damping
740    pub fn phase_damping(mut self, name: &str, gamma: f64) -> Self {
741        let channel = QuantumChannel::phase_damping(gamma);
742        self.channels.insert(name.to_string(), channel);
743        self.application_order.push(name.to_string());
744        self
745    }
746
747    /// Build composite noise model
748    pub fn build(self) -> CompositeNoiseModel {
749        CompositeNoiseModel {
750            channels: self.channels,
751            application_order: self.application_order,
752        }
753    }
754}
755
756/// Composite noise model with multiple channels
757#[derive(Debug, Clone)]
758pub struct CompositeNoiseModel {
759    channels: HashMap<String, QuantumChannel>,
760    application_order: Vec<String>,
761}
762
763impl CompositeNoiseModel {
764    /// Apply all noise channels in order
765    pub fn apply(&self, rho: &Array2<Complex64>) -> Array2<Complex64> {
766        let mut result = rho.clone();
767
768        for channel_name in &self.application_order {
769            if let Some(channel) = self.channels.get(channel_name) {
770                result = channel.apply(&result);
771            }
772        }
773
774        result
775    }
776
777    /// Get channel by name
778    pub fn get_channel(&self, name: &str) -> Option<&QuantumChannel> {
779        self.channels.get(name)
780    }
781}
782
783#[cfg(test)]
784mod tests {
785    use super::*;
786
787    #[test]
788    fn test_lindblad_simulator_creation() {
789        let sim = LindladSimulator::new(2).unwrap();
790        assert_eq!(sim.num_qubits, 2);
791        assert_eq!(sim.density_matrix.shape(), [4, 4]);
792    }
793
794    #[test]
795    fn test_depolarizing_channel() {
796        let channel = QuantumChannel::depolarizing(1, 0.1);
797        assert!(channel.is_trace_preserving());
798        assert_eq!(channel.kraus_operators.len(), 4);
799    }
800
801    #[test]
802    fn test_amplitude_damping() {
803        let channel = QuantumChannel::amplitude_damping(0.2);
804        assert!(channel.is_trace_preserving());
805
806        // Test on |1⟩ state
807        let mut rho_1 = Array2::zeros((2, 2));
808        rho_1[[1, 1]] = Complex64::new(1.0, 0.0);
809
810        let result = channel.apply(&rho_1);
811
812        // Should have some population in |0⟩
813        assert!(result[[0, 0]].re > 0.0);
814        assert!(result[[1, 1]].re < 1.0);
815    }
816
817    #[test]
818    fn test_noise_model_builder() {
819        let noise_model = NoiseModelBuilder::new()
820            .depolarizing("depol", 0.01)
821            .amplitude_damping("amp_damp", 0.02)
822            .build();
823
824        assert!(noise_model.get_channel("depol").is_some());
825        assert!(noise_model.get_channel("amp_damp").is_some());
826    }
827}