quantrs2_core/
quantum_channels.rs

1//! Quantum channel representations
2//!
3//! This module provides various representations of quantum channels (completely positive
4//! trace-preserving maps) including Kraus operators, Choi matrices, and Stinespring dilations.
5
6use crate::{
7    error::{QuantRS2Error, QuantRS2Result},
8    matrix_ops::{DenseMatrix, QuantumMatrix},
9};
10use scirs2_core::ndarray::{s, Array2};
11use scirs2_core::Complex;
12
13/// A quantum channel represented in various forms
14#[derive(Debug, Clone)]
15pub struct QuantumChannel {
16    /// Number of input qubits
17    pub input_dim: usize,
18    /// Number of output qubits
19    pub output_dim: usize,
20    /// Kraus operator representation
21    pub kraus: Option<KrausRepresentation>,
22    /// Choi matrix representation
23    pub choi: Option<ChoiRepresentation>,
24    /// Stinespring dilation representation
25    pub stinespring: Option<StinespringRepresentation>,
26    /// Tolerance for numerical comparisons
27    tolerance: f64,
28}
29
30/// Kraus operator representation of a quantum channel
31#[derive(Debug, Clone)]
32pub struct KrausRepresentation {
33    /// List of Kraus operators
34    pub operators: Vec<Array2<Complex<f64>>>,
35}
36
37/// Choi matrix representation (Choi-Jamiolkowski isomorphism)
38#[derive(Debug, Clone)]
39pub struct ChoiRepresentation {
40    /// The Choi matrix
41    pub matrix: Array2<Complex<f64>>,
42}
43
44/// Stinespring dilation representation
45#[derive(Debug, Clone)]
46pub struct StinespringRepresentation {
47    /// Isometry from input to output + environment
48    pub isometry: Array2<Complex<f64>>,
49    /// Dimension of the environment
50    pub env_dim: usize,
51}
52
53impl QuantumChannel {
54    /// Create a new quantum channel from Kraus operators
55    pub fn from_kraus(operators: Vec<Array2<Complex<f64>>>) -> QuantRS2Result<Self> {
56        if operators.is_empty() {
57            return Err(QuantRS2Error::InvalidInput(
58                "At least one Kraus operator required".to_string(),
59            ));
60        }
61
62        // Check dimensions
63        let shape = operators[0].shape();
64        let output_dim = shape[0];
65        let input_dim = shape[1];
66
67        // Verify all operators have same dimensions
68        for (i, op) in operators.iter().enumerate() {
69            if op.shape() != shape {
70                return Err(QuantRS2Error::InvalidInput(format!(
71                    "Kraus operator {i} has inconsistent dimensions"
72                )));
73            }
74        }
75
76        let kraus = KrausRepresentation { operators };
77
78        let channel = Self {
79            input_dim,
80            output_dim,
81            kraus: Some(kraus),
82            choi: None,
83            stinespring: None,
84            tolerance: 1e-10,
85        };
86
87        // Verify completeness relation
88        channel.verify_kraus_completeness()?;
89
90        Ok(channel)
91    }
92
93    /// Create a quantum channel from Choi matrix
94    pub fn from_choi(matrix: Array2<Complex<f64>>) -> QuantRS2Result<Self> {
95        let total_dim = matrix.shape()[0];
96
97        // Choi matrix should be square
98        if matrix.shape()[0] != matrix.shape()[1] {
99            return Err(QuantRS2Error::InvalidInput(
100                "Choi matrix must be square".to_string(),
101            ));
102        }
103
104        // For now, assume input_dim = output_dim
105        let dim = (total_dim as f64).sqrt() as usize;
106        if dim * dim != total_dim {
107            return Err(QuantRS2Error::InvalidInput(
108                "Choi matrix dimension must be perfect square".to_string(),
109            ));
110        }
111
112        let choi = ChoiRepresentation { matrix };
113
114        let channel = Self {
115            input_dim: dim,
116            output_dim: dim,
117            kraus: None,
118            choi: Some(choi),
119            stinespring: None,
120            tolerance: 1e-10,
121        };
122
123        // Verify Choi matrix properties
124        channel.verify_choi_properties()?;
125
126        Ok(channel)
127    }
128
129    /// Convert to Kraus representation
130    pub fn to_kraus(&mut self) -> QuantRS2Result<&KrausRepresentation> {
131        if self.kraus.is_some() {
132            return self
133                .kraus
134                .as_ref()
135                .ok_or_else(|| QuantRS2Error::InvalidInput("Kraus representation missing".into()));
136        }
137
138        if let Some(choi) = &self.choi {
139            let kraus = self.choi_to_kraus(&choi.matrix)?;
140            self.kraus = Some(kraus);
141            self.kraus
142                .as_ref()
143                .ok_or_else(|| QuantRS2Error::InvalidInput("Kraus conversion failed".into()))
144        } else if let Some(stinespring) = &self.stinespring {
145            let kraus = self.stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)?;
146            self.kraus = Some(kraus);
147            self.kraus
148                .as_ref()
149                .ok_or_else(|| QuantRS2Error::InvalidInput("Kraus conversion failed".into()))
150        } else {
151            Err(QuantRS2Error::InvalidInput(
152                "No representation available".to_string(),
153            ))
154        }
155    }
156
157    /// Convert to Choi representation
158    pub fn to_choi(&mut self) -> QuantRS2Result<&ChoiRepresentation> {
159        if self.choi.is_some() {
160            return self
161                .choi
162                .as_ref()
163                .ok_or_else(|| QuantRS2Error::InvalidInput("Choi representation missing".into()));
164        }
165
166        if let Some(kraus) = &self.kraus {
167            let choi = self.kraus_to_choi(&kraus.operators)?;
168            self.choi = Some(choi);
169            self.choi
170                .as_ref()
171                .ok_or_else(|| QuantRS2Error::InvalidInput("Choi conversion failed".into()))
172        } else if let Some(stinespring) = &self.stinespring {
173            // First convert to Kraus, then to Choi
174            let kraus = self.stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)?;
175            let choi = self.kraus_to_choi(&kraus.operators)?;
176            self.choi = Some(choi);
177            self.choi
178                .as_ref()
179                .ok_or_else(|| QuantRS2Error::InvalidInput("Choi conversion failed".into()))
180        } else {
181            Err(QuantRS2Error::InvalidInput(
182                "No representation available".to_string(),
183            ))
184        }
185    }
186
187    /// Convert to Stinespring representation
188    pub fn to_stinespring(&mut self) -> QuantRS2Result<&StinespringRepresentation> {
189        if self.stinespring.is_some() {
190            return self.stinespring.as_ref().ok_or_else(|| {
191                QuantRS2Error::InvalidInput("Stinespring representation missing".into())
192            });
193        }
194
195        // Convert from Kraus to Stinespring
196        let kraus = self.to_kraus()?.clone();
197        let stinespring = self.kraus_to_stinespring(&kraus.operators)?;
198        self.stinespring = Some(stinespring);
199        self.stinespring
200            .as_ref()
201            .ok_or_else(|| QuantRS2Error::InvalidInput("Stinespring conversion failed".into()))
202    }
203
204    /// Apply the channel to a density matrix
205    pub fn apply(&mut self, rho: &Array2<Complex<f64>>) -> QuantRS2Result<Array2<Complex<f64>>> {
206        // Use Kraus representation for application
207        let kraus = self.to_kraus()?.clone();
208        let output_dim = self.output_dim;
209
210        let mut result = Array2::zeros((output_dim, output_dim));
211
212        for k in &kraus.operators {
213            let k_dag = k.mapv(|z| z.conj()).t().to_owned();
214            let term = k.dot(rho).dot(&k_dag);
215            result = result + term;
216        }
217
218        Ok(result)
219    }
220
221    /// Check if channel is unitary
222    pub fn is_unitary(&mut self) -> QuantRS2Result<bool> {
223        let kraus = self.to_kraus()?;
224
225        // Unitary channel has single Kraus operator that is unitary
226        if kraus.operators.len() != 1 {
227            return Ok(false);
228        }
229
230        let mat = DenseMatrix::new(kraus.operators[0].clone())?;
231        mat.is_unitary(self.tolerance)
232    }
233
234    /// Check if channel is a depolarizing channel
235    pub fn is_depolarizing(&mut self) -> QuantRS2Result<bool> {
236        // Depolarizing channel has form: ρ → (1-p)ρ + p*I/d
237        // In Kraus form: K₀ = √(1-3p/4)*I, K₁ = √(p/4)*X, K₂ = √(p/4)*Y, K₃ = √(p/4)*Z
238
239        if self.input_dim != 2 || self.output_dim != 2 {
240            return Ok(false); // Only check single-qubit for now
241        }
242
243        let kraus = self.to_kraus()?;
244
245        if kraus.operators.len() != 4 {
246            return Ok(false);
247        }
248
249        // Check if operators match depolarizing structure
250        // This is a simplified check
251        Ok(true)
252    }
253
254    /// Get the depolarizing parameter if this is a depolarizing channel
255    pub fn depolarizing_parameter(&mut self) -> QuantRS2Result<Option<f64>> {
256        if !self.is_depolarizing()? {
257            return Ok(None);
258        }
259
260        let kraus = self.to_kraus()?;
261
262        // Extract p from first Kraus operator
263        // K₀ = √(1-3p/4)*I
264        let k0_coeff = kraus.operators[0][[0, 0]].norm();
265        let p = 4.0 * k0_coeff.mul_add(-k0_coeff, 1.0) / 3.0;
266
267        Ok(Some(p))
268    }
269
270    /// Verify Kraus completeness relation: ∑ᵢ Kᵢ†Kᵢ = I
271    fn verify_kraus_completeness(&self) -> QuantRS2Result<()> {
272        if let Some(kraus) = &self.kraus {
273            let mut sum: Array2<Complex<f64>> = Array2::zeros((self.input_dim, self.input_dim));
274
275            for k in &kraus.operators {
276                let k_dag = k.mapv(|z| z.conj()).t().to_owned();
277                sum = sum + k_dag.dot(k);
278            }
279
280            // Check if sum equals identity
281            for i in 0..self.input_dim {
282                for j in 0..self.input_dim {
283                    let expected = if i == j {
284                        Complex::new(1.0, 0.0)
285                    } else {
286                        Complex::new(0.0, 0.0)
287                    };
288                    let diff: Complex<f64> = sum[[i, j]] - expected;
289                    if diff.norm() > self.tolerance {
290                        return Err(QuantRS2Error::InvalidInput(
291                            "Kraus operators do not satisfy completeness relation".to_string(),
292                        ));
293                    }
294                }
295            }
296
297            Ok(())
298        } else {
299            Ok(())
300        }
301    }
302
303    /// Verify Choi matrix is positive semidefinite and satisfies partial trace condition
304    fn verify_choi_properties(&self) -> QuantRS2Result<()> {
305        if let Some(choi) = &self.choi {
306            // Check Hermiticity
307            let choi_dag = choi.matrix.mapv(|z| z.conj()).t().to_owned();
308            let diff = &choi.matrix - &choi_dag;
309            let max_diff = diff.iter().map(|z| z.norm()).fold(0.0, f64::max);
310
311            if max_diff > self.tolerance {
312                return Err(QuantRS2Error::InvalidInput(
313                    "Choi matrix is not Hermitian".to_string(),
314                ));
315            }
316
317            // Check positive semidefiniteness via eigenvalues (simplified)
318            // Full implementation would compute eigenvalues
319
320            // Check partial trace equals identity
321            // Tr_B[J] = I_A for CPTP map
322
323            Ok(())
324        } else {
325            Ok(())
326        }
327    }
328
329    /// Convert Kraus operators to Choi matrix
330    fn kraus_to_choi(
331        &self,
332        operators: &[Array2<Complex<f64>>],
333    ) -> QuantRS2Result<ChoiRepresentation> {
334        let d_in = self.input_dim;
335        let d_out = self.output_dim;
336        let total_dim = d_in * d_out;
337
338        let mut choi = Array2::zeros((total_dim, total_dim));
339
340        // Create maximally entangled state |Ω⟩ = ∑ᵢ |ii⟩
341        let mut omega = Array2::zeros((d_in * d_in, 1));
342        for i in 0..d_in {
343            omega[[i * d_in + i, 0]] = Complex::new(1.0, 0.0);
344        }
345        let _omega = omega / Complex::new((d_in as f64).sqrt(), 0.0);
346
347        // Apply channel ⊗ I to |Ω⟩⟨Ω|
348        for k in operators {
349            // Vectorize the Kraus operator
350            let k_vec = self.vectorize_operator(k);
351            let k_vec_dag = k_vec.mapv(|z| z.conj()).t().to_owned();
352
353            // Contribution to Choi matrix
354            choi = choi + k_vec.dot(&k_vec_dag);
355        }
356
357        Ok(ChoiRepresentation { matrix: choi })
358    }
359
360    /// Convert Choi matrix to Kraus operators
361    fn choi_to_kraus(&self, _choi: &Array2<Complex<f64>>) -> QuantRS2Result<KrausRepresentation> {
362        // Eigendecompose the Choi matrix
363        // J = ∑ᵢ λᵢ |vᵢ⟩⟨vᵢ|
364
365        // Simplified implementation - would use proper eigendecomposition
366        let mut operators = Vec::new();
367
368        // For now, return identity as single Kraus operator
369        let identity = Array2::eye(self.output_dim);
370        operators.push(identity.mapv(|x| Complex::new(x, 0.0)));
371
372        Ok(KrausRepresentation { operators })
373    }
374
375    /// Convert Kraus operators to Stinespring dilation
376    fn kraus_to_stinespring(
377        &self,
378        operators: &[Array2<Complex<f64>>],
379    ) -> QuantRS2Result<StinespringRepresentation> {
380        let num_kraus = operators.len();
381        let d_in = self.input_dim;
382        let d_out = self.output_dim;
383
384        // Environment dimension is number of Kraus operators
385        let env_dim = num_kraus;
386
387        // Build isometry V: |ψ⟩ ⊗ |0⟩_E → ∑ᵢ Kᵢ|ψ⟩ ⊗ |i⟩_E
388        let total_out_dim = d_out * env_dim;
389        let mut isometry = Array2::zeros((total_out_dim, d_in));
390
391        for (i, k) in operators.iter().enumerate() {
392            // Place Kraus operator in appropriate block
393            let start_row = i * d_out;
394            let end_row = (i + 1) * d_out;
395
396            isometry.slice_mut(s![start_row..end_row, ..]).assign(k);
397        }
398
399        Ok(StinespringRepresentation { isometry, env_dim })
400    }
401
402    /// Convert Stinespring dilation to Kraus operators
403    fn stinespring_to_kraus(
404        &self,
405        isometry: &Array2<Complex<f64>>,
406        env_dim: usize,
407    ) -> QuantRS2Result<KrausRepresentation> {
408        let d_out = self.output_dim;
409        let mut operators = Vec::new();
410
411        // Extract Kraus operators from blocks of isometry
412        for i in 0..env_dim {
413            let start_row = i * d_out;
414            let end_row = (i + 1) * d_out;
415
416            let k = isometry.slice(s![start_row..end_row, ..]).to_owned();
417
418            // Only include non-zero operators
419            let norm_sq: f64 = k.iter().map(|z| z.norm_sqr()).sum();
420            if norm_sq > self.tolerance {
421                operators.push(k);
422            }
423        }
424
425        Ok(KrausRepresentation { operators })
426    }
427
428    /// Vectorize an operator (column-stacking)
429    fn vectorize_operator(&self, op: &Array2<Complex<f64>>) -> Array2<Complex<f64>> {
430        let (rows, cols) = op.dim();
431        let mut vec = Array2::zeros((rows * cols, 1));
432
433        for j in 0..cols {
434            for i in 0..rows {
435                vec[[i + j * rows, 0]] = op[[i, j]];
436            }
437        }
438
439        vec
440    }
441}
442
443/// Common quantum channels
444pub struct QuantumChannels;
445
446impl QuantumChannels {
447    /// Create a depolarizing channel
448    pub fn depolarizing(p: f64) -> QuantRS2Result<QuantumChannel> {
449        if p < 0.0 || p > 1.0 {
450            return Err(QuantRS2Error::InvalidInput(
451                "Depolarizing parameter must be in [0, 1]".to_string(),
452            ));
453        }
454
455        let sqrt_1_minus_3p_4 = ((1.0 - 3.0 * p / 4.0).max(0.0)).sqrt();
456        let sqrt_p_4 = (p / 4.0).sqrt();
457
458        let operators = vec![
459            // sqrt(1-3p/4) * I
460            Array2::from_shape_vec(
461                (2, 2),
462                vec![
463                    Complex::new(sqrt_1_minus_3p_4, 0.0),
464                    Complex::new(0.0, 0.0),
465                    Complex::new(0.0, 0.0),
466                    Complex::new(sqrt_1_minus_3p_4, 0.0),
467                ],
468            )
469            .expect("valid 2x2 identity Kraus operator"),
470            // sqrt(p/4) * X
471            Array2::from_shape_vec(
472                (2, 2),
473                vec![
474                    Complex::new(0.0, 0.0),
475                    Complex::new(sqrt_p_4, 0.0),
476                    Complex::new(sqrt_p_4, 0.0),
477                    Complex::new(0.0, 0.0),
478                ],
479            )
480            .expect("valid 2x2 X Kraus operator"),
481            // sqrt(p/4) * Y
482            Array2::from_shape_vec(
483                (2, 2),
484                vec![
485                    Complex::new(0.0, 0.0),
486                    Complex::new(0.0, -sqrt_p_4),
487                    Complex::new(0.0, sqrt_p_4),
488                    Complex::new(0.0, 0.0),
489                ],
490            )
491            .expect("valid 2x2 Y Kraus operator"),
492            // sqrt(p/4) * Z
493            Array2::from_shape_vec(
494                (2, 2),
495                vec![
496                    Complex::new(sqrt_p_4, 0.0),
497                    Complex::new(0.0, 0.0),
498                    Complex::new(0.0, 0.0),
499                    Complex::new(-sqrt_p_4, 0.0),
500                ],
501            )
502            .expect("valid 2x2 Z Kraus operator"),
503        ];
504
505        QuantumChannel::from_kraus(operators)
506    }
507
508    /// Create an amplitude damping channel
509    pub fn amplitude_damping(gamma: f64) -> QuantRS2Result<QuantumChannel> {
510        if gamma < 0.0 || gamma > 1.0 {
511            return Err(QuantRS2Error::InvalidInput(
512                "Damping parameter must be in [0, 1]".to_string(),
513            ));
514        }
515
516        let sqrt_gamma = gamma.sqrt();
517        let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
518
519        let operators = vec![
520            // K0 = |0><0| + sqrt(1-gamma)|1><1|
521            Array2::from_shape_vec(
522                (2, 2),
523                vec![
524                    Complex::new(1.0, 0.0),
525                    Complex::new(0.0, 0.0),
526                    Complex::new(0.0, 0.0),
527                    Complex::new(sqrt_1_minus_gamma, 0.0),
528                ],
529            )
530            .expect("valid 2x2 amplitude damping K0"),
531            // K1 = sqrt(gamma)|0><1|
532            Array2::from_shape_vec(
533                (2, 2),
534                vec![
535                    Complex::new(0.0, 0.0),
536                    Complex::new(sqrt_gamma, 0.0),
537                    Complex::new(0.0, 0.0),
538                    Complex::new(0.0, 0.0),
539                ],
540            )
541            .expect("valid 2x2 amplitude damping K1"),
542        ];
543
544        QuantumChannel::from_kraus(operators)
545    }
546
547    /// Create a phase damping channel
548    pub fn phase_damping(gamma: f64) -> QuantRS2Result<QuantumChannel> {
549        if gamma < 0.0 || gamma > 1.0 {
550            return Err(QuantRS2Error::InvalidInput(
551                "Damping parameter must be in [0, 1]".to_string(),
552            ));
553        }
554
555        let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
556        let sqrt_gamma = gamma.sqrt();
557
558        let operators = vec![
559            // K0 = sqrt(1-gamma) * I
560            Array2::from_shape_vec(
561                (2, 2),
562                vec![
563                    Complex::new(sqrt_1_minus_gamma, 0.0),
564                    Complex::new(0.0, 0.0),
565                    Complex::new(0.0, 0.0),
566                    Complex::new(sqrt_1_minus_gamma, 0.0),
567                ],
568            )
569            .expect("valid 2x2 phase damping K0"),
570            // K1 = sqrt(gamma) * Z
571            Array2::from_shape_vec(
572                (2, 2),
573                vec![
574                    Complex::new(sqrt_gamma, 0.0),
575                    Complex::new(0.0, 0.0),
576                    Complex::new(0.0, 0.0),
577                    Complex::new(-sqrt_gamma, 0.0),
578                ],
579            )
580            .expect("valid 2x2 phase damping K1"),
581        ];
582
583        QuantumChannel::from_kraus(operators)
584    }
585
586    /// Create a bit flip channel
587    pub fn bit_flip(p: f64) -> QuantRS2Result<QuantumChannel> {
588        if p < 0.0 || p > 1.0 {
589            return Err(QuantRS2Error::InvalidInput(
590                "Flip probability must be in [0, 1]".to_string(),
591            ));
592        }
593
594        let sqrt_1_minus_p = (1.0 - p).sqrt();
595        let sqrt_p = p.sqrt();
596
597        let operators = vec![
598            // K0 = sqrt(1-p) * I
599            Array2::from_shape_vec(
600                (2, 2),
601                vec![
602                    Complex::new(sqrt_1_minus_p, 0.0),
603                    Complex::new(0.0, 0.0),
604                    Complex::new(0.0, 0.0),
605                    Complex::new(sqrt_1_minus_p, 0.0),
606                ],
607            )
608            .expect("valid 2x2 bit flip K0"),
609            // K1 = sqrt(p) * X
610            Array2::from_shape_vec(
611                (2, 2),
612                vec![
613                    Complex::new(0.0, 0.0),
614                    Complex::new(sqrt_p, 0.0),
615                    Complex::new(sqrt_p, 0.0),
616                    Complex::new(0.0, 0.0),
617                ],
618            )
619            .expect("valid 2x2 bit flip K1"),
620        ];
621
622        QuantumChannel::from_kraus(operators)
623    }
624
625    /// Create a phase flip channel
626    pub fn phase_flip(p: f64) -> QuantRS2Result<QuantumChannel> {
627        if p < 0.0 || p > 1.0 {
628            return Err(QuantRS2Error::InvalidInput(
629                "Flip probability must be in [0, 1]".to_string(),
630            ));
631        }
632
633        let sqrt_1_minus_p = (1.0 - p).sqrt();
634        let sqrt_p = p.sqrt();
635
636        let operators = vec![
637            // K0 = sqrt(1-p) * I
638            Array2::from_shape_vec(
639                (2, 2),
640                vec![
641                    Complex::new(sqrt_1_minus_p, 0.0),
642                    Complex::new(0.0, 0.0),
643                    Complex::new(0.0, 0.0),
644                    Complex::new(sqrt_1_minus_p, 0.0),
645                ],
646            )
647            .expect("valid 2x2 phase flip K0"),
648            // K1 = sqrt(p) * Z
649            Array2::from_shape_vec(
650                (2, 2),
651                vec![
652                    Complex::new(sqrt_p, 0.0),
653                    Complex::new(0.0, 0.0),
654                    Complex::new(0.0, 0.0),
655                    Complex::new(-sqrt_p, 0.0),
656                ],
657            )
658            .expect("valid 2x2 phase flip K1"),
659        ];
660
661        QuantumChannel::from_kraus(operators)
662    }
663}
664
665/// Process tomography utilities
666pub struct ProcessTomography;
667
668impl ProcessTomography {
669    /// Reconstruct a quantum channel from process tomography data
670    pub fn reconstruct_channel(
671        input_states: &[Array2<Complex<f64>>],
672        output_states: &[Array2<Complex<f64>>],
673    ) -> QuantRS2Result<QuantumChannel> {
674        if input_states.len() != output_states.len() {
675            return Err(QuantRS2Error::InvalidInput(
676                "Number of input and output states must match".to_string(),
677            ));
678        }
679
680        // Simplified implementation
681        // Full implementation would use maximum likelihood or least squares
682
683        // For now, return identity channel
684        let d = input_states[0].shape()[0];
685        let identity = Array2::eye(d).mapv(|x| Complex::new(x, 0.0));
686
687        QuantumChannel::from_kraus(vec![identity])
688    }
689
690    /// Generate informationally complete set of input states
691    pub fn generate_input_states(dim: usize) -> Vec<Array2<Complex<f64>>> {
692        let mut states = Vec::new();
693
694        // Add computational basis states
695        for i in 0..dim {
696            let mut state = Array2::zeros((dim, dim));
697            state[[i, i]] = Complex::new(1.0, 0.0);
698            states.push(state);
699        }
700
701        // Add superposition states
702        // Full implementation would generate SIC-POVM or tetrahedron states
703
704        states
705    }
706}
707
708#[cfg(test)]
709mod tests {
710    use super::*;
711    use scirs2_core::Complex;
712
713    #[test]
714    fn test_depolarizing_channel() {
715        let channel =
716            QuantumChannels::depolarizing(0.1).expect("Failed to create depolarizing channel");
717
718        assert_eq!(channel.input_dim, 2);
719        assert_eq!(channel.output_dim, 2);
720        assert!(channel.kraus.is_some());
721        assert_eq!(
722            channel
723                .kraus
724                .as_ref()
725                .expect("Kraus representation missing")
726                .operators
727                .len(),
728            4
729        );
730    }
731
732    #[test]
733    fn test_amplitude_damping() {
734        let channel = QuantumChannels::amplitude_damping(0.3)
735            .expect("Failed to create amplitude damping channel");
736
737        assert!(channel.kraus.is_some());
738        assert_eq!(
739            channel
740                .kraus
741                .as_ref()
742                .expect("Kraus representation missing")
743                .operators
744                .len(),
745            2
746        );
747
748        // Test on |1><1| state
749        let mut rho = Array2::zeros((2, 2));
750        rho[[1, 1]] = Complex::new(1.0, 0.0);
751
752        let mut ch = channel;
753        let output = ch.apply(&rho).expect("Failed to apply channel");
754
755        // Population should decrease
756        assert!(output[[1, 1]].re < 1.0);
757        assert!(output[[0, 0]].re > 0.0);
758    }
759
760    #[test]
761    fn test_kraus_to_choi() {
762        let mut channel =
763            QuantumChannels::bit_flip(0.2).expect("Failed to create bit flip channel");
764        let choi = channel.to_choi().expect("Failed to convert to Choi");
765
766        assert_eq!(choi.matrix.shape(), [4, 4]);
767
768        // Choi matrix should be Hermitian
769        let choi_dag = choi.matrix.mapv(|z| z.conj()).t().to_owned();
770        let diff = &choi.matrix - &choi_dag;
771        let max_diff = diff.iter().map(|z| z.norm()).fold(0.0, f64::max);
772        assert!(max_diff < 1e-10);
773    }
774
775    #[test]
776    fn test_channel_composition() {
777        // Create two channels
778        let mut ch1 =
779            QuantumChannels::phase_flip(0.1).expect("Failed to create phase flip channel");
780        let mut ch2 = QuantumChannels::bit_flip(0.2).expect("Failed to create bit flip channel");
781
782        // Apply both to a superposition state
783        let mut rho = Array2::zeros((2, 2));
784        rho[[0, 0]] = Complex::new(0.5, 0.0);
785        rho[[0, 1]] = Complex::new(0.5, 0.0);
786        rho[[1, 0]] = Complex::new(0.5, 0.0);
787        rho[[1, 1]] = Complex::new(0.5, 0.0);
788
789        let intermediate = ch1.apply(&rho).expect("Failed to apply phase flip channel");
790        let final_state = ch2
791            .apply(&intermediate)
792            .expect("Failed to apply bit flip channel");
793
794        // Trace should be preserved
795        let trace = final_state[[0, 0]] + final_state[[1, 1]];
796        assert!((trace.re - 1.0).abs() < 1e-10);
797        assert!(trace.im.abs() < 1e-10);
798    }
799
800    #[test]
801    fn test_unitary_channel() {
802        // Hadamard as unitary channel
803        let h = Array2::from_shape_vec(
804            (2, 2),
805            vec![
806                Complex::new(1.0, 0.0),
807                Complex::new(1.0, 0.0),
808                Complex::new(1.0, 0.0),
809                Complex::new(-1.0, 0.0),
810            ],
811        )
812        .expect("valid 2x2 Hadamard matrix")
813            / Complex::new(2.0_f64.sqrt(), 0.0);
814
815        let mut channel =
816            QuantumChannel::from_kraus(vec![h]).expect("Failed to create unitary channel");
817
818        assert!(channel.is_unitary().expect("Failed to check unitarity"));
819    }
820
821    #[test]
822    fn test_stinespring_conversion() {
823        let mut channel = QuantumChannels::amplitude_damping(0.5)
824            .expect("Failed to create amplitude damping channel");
825
826        // Convert to Stinespring
827        let stinespring = channel
828            .to_stinespring()
829            .expect("Failed to convert to Stinespring");
830
831        assert_eq!(stinespring.env_dim, 2);
832        assert_eq!(stinespring.isometry.shape(), [4, 2]);
833
834        // Convert back to Kraus
835        let kraus_decomposer =
836            QuantumChannel::from_kraus(vec![Array2::eye(2).mapv(|x| Complex::new(x, 0.0))])
837                .expect("Failed to create identity channel");
838        let kraus = kraus_decomposer
839            .stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)
840            .expect("Failed to convert back to Kraus");
841        assert_eq!(kraus.operators.len(), 2);
842    }
843}