1use crate::{
7 error::{QuantRS2Error, QuantRS2Result},
8 matrix_ops::{partial_trace, tensor_product_many, DenseMatrix, QuantumMatrix},
9 qubit::QubitId,
10};
11use ndarray::{s, Array2, Array4, Axis};
12use num_complex::Complex;
13use rustc_hash::FxHashMap;
14use std::f64::consts::PI;
15
16#[derive(Debug, Clone)]
18pub struct QuantumChannel {
19 pub input_dim: usize,
21 pub output_dim: usize,
23 pub kraus: Option<KrausRepresentation>,
25 pub choi: Option<ChoiRepresentation>,
27 pub stinespring: Option<StinespringRepresentation>,
29 tolerance: f64,
31}
32
33#[derive(Debug, Clone)]
35pub struct KrausRepresentation {
36 pub operators: Vec<Array2<Complex<f64>>>,
38}
39
40#[derive(Debug, Clone)]
42pub struct ChoiRepresentation {
43 pub matrix: Array2<Complex<f64>>,
45}
46
47#[derive(Debug, Clone)]
49pub struct StinespringRepresentation {
50 pub isometry: Array2<Complex<f64>>,
52 pub env_dim: usize,
54}
55
56impl QuantumChannel {
57 pub fn from_kraus(operators: Vec<Array2<Complex<f64>>>) -> QuantRS2Result<Self> {
59 if operators.is_empty() {
60 return Err(QuantRS2Error::InvalidInput(
61 "At least one Kraus operator required".to_string(),
62 ));
63 }
64
65 let shape = operators[0].shape();
67 let output_dim = shape[0];
68 let input_dim = shape[1];
69
70 for (i, op) in operators.iter().enumerate() {
72 if op.shape() != shape {
73 return Err(QuantRS2Error::InvalidInput(format!(
74 "Kraus operator {} has inconsistent dimensions",
75 i
76 )));
77 }
78 }
79
80 let kraus = KrausRepresentation { operators };
81
82 let mut channel = Self {
83 input_dim,
84 output_dim,
85 kraus: Some(kraus),
86 choi: None,
87 stinespring: None,
88 tolerance: 1e-10,
89 };
90
91 channel.verify_kraus_completeness()?;
93
94 Ok(channel)
95 }
96
97 pub fn from_choi(matrix: Array2<Complex<f64>>) -> QuantRS2Result<Self> {
99 let total_dim = matrix.shape()[0];
100
101 if matrix.shape()[0] != matrix.shape()[1] {
103 return Err(QuantRS2Error::InvalidInput(
104 "Choi matrix must be square".to_string(),
105 ));
106 }
107
108 let dim = (total_dim as f64).sqrt() as usize;
110 if dim * dim != total_dim {
111 return Err(QuantRS2Error::InvalidInput(
112 "Choi matrix dimension must be perfect square".to_string(),
113 ));
114 }
115
116 let choi = ChoiRepresentation { matrix };
117
118 let mut channel = Self {
119 input_dim: dim,
120 output_dim: dim,
121 kraus: None,
122 choi: Some(choi),
123 stinespring: None,
124 tolerance: 1e-10,
125 };
126
127 channel.verify_choi_properties()?;
129
130 Ok(channel)
131 }
132
133 pub fn to_kraus(&mut self) -> QuantRS2Result<&KrausRepresentation> {
135 if self.kraus.is_some() {
136 return Ok(self.kraus.as_ref().unwrap());
137 }
138
139 if let Some(choi) = &self.choi {
140 let kraus = self.choi_to_kraus(&choi.matrix)?;
141 self.kraus = Some(kraus);
142 Ok(self.kraus.as_ref().unwrap())
143 } else if let Some(stinespring) = &self.stinespring {
144 let kraus = self.stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)?;
145 self.kraus = Some(kraus);
146 Ok(self.kraus.as_ref().unwrap())
147 } else {
148 Err(QuantRS2Error::InvalidInput(
149 "No representation available".to_string(),
150 ))
151 }
152 }
153
154 pub fn to_choi(&mut self) -> QuantRS2Result<&ChoiRepresentation> {
156 if self.choi.is_some() {
157 return Ok(self.choi.as_ref().unwrap());
158 }
159
160 if let Some(kraus) = &self.kraus {
161 let choi = self.kraus_to_choi(&kraus.operators)?;
162 self.choi = Some(choi);
163 Ok(self.choi.as_ref().unwrap())
164 } else if let Some(stinespring) = &self.stinespring {
165 let kraus = self.stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)?;
167 let choi = self.kraus_to_choi(&kraus.operators)?;
168 self.choi = Some(choi);
169 Ok(self.choi.as_ref().unwrap())
170 } else {
171 Err(QuantRS2Error::InvalidInput(
172 "No representation available".to_string(),
173 ))
174 }
175 }
176
177 pub fn to_stinespring(&mut self) -> QuantRS2Result<&StinespringRepresentation> {
179 if self.stinespring.is_some() {
180 return Ok(self.stinespring.as_ref().unwrap());
181 }
182
183 let kraus = self.to_kraus()?.clone();
185 let stinespring = self.kraus_to_stinespring(&kraus.operators)?;
186 self.stinespring = Some(stinespring);
187 Ok(self.stinespring.as_ref().unwrap())
188 }
189
190 pub fn apply(&mut self, rho: &Array2<Complex<f64>>) -> QuantRS2Result<Array2<Complex<f64>>> {
192 let kraus = self.to_kraus()?.clone();
194 let output_dim = self.output_dim;
195
196 let mut result = Array2::zeros((output_dim, output_dim));
197
198 for k in &kraus.operators {
199 let k_dag = k.mapv(|z| z.conj()).t().to_owned();
200 let term = k.dot(rho).dot(&k_dag);
201 result = result + term;
202 }
203
204 Ok(result)
205 }
206
207 pub fn is_unitary(&mut self) -> QuantRS2Result<bool> {
209 let kraus = self.to_kraus()?;
210
211 if kraus.operators.len() != 1 {
213 return Ok(false);
214 }
215
216 let mat = DenseMatrix::new(kraus.operators[0].clone())?;
217 mat.is_unitary(self.tolerance)
218 }
219
220 pub fn is_depolarizing(&mut self) -> QuantRS2Result<bool> {
222 if self.input_dim != 2 || self.output_dim != 2 {
226 return Ok(false); }
228
229 let kraus = self.to_kraus()?;
230
231 if kraus.operators.len() != 4 {
232 return Ok(false);
233 }
234
235 Ok(true)
238 }
239
240 pub fn depolarizing_parameter(&mut self) -> QuantRS2Result<Option<f64>> {
242 if !self.is_depolarizing()? {
243 return Ok(None);
244 }
245
246 let kraus = self.to_kraus()?;
247
248 let k0_coeff = kraus.operators[0][[0, 0]].norm();
251 let p = 4.0 * (1.0 - k0_coeff * k0_coeff) / 3.0;
252
253 Ok(Some(p))
254 }
255
256 fn verify_kraus_completeness(&self) -> QuantRS2Result<()> {
258 if let Some(kraus) = &self.kraus {
259 let mut sum: Array2<Complex<f64>> = Array2::zeros((self.input_dim, self.input_dim));
260
261 for k in &kraus.operators {
262 let k_dag = k.mapv(|z| z.conj()).t().to_owned();
263 sum = sum + k_dag.dot(k);
264 }
265
266 for i in 0..self.input_dim {
268 for j in 0..self.input_dim {
269 let expected = if i == j {
270 Complex::new(1.0, 0.0)
271 } else {
272 Complex::new(0.0, 0.0)
273 };
274 let diff: Complex<f64> = sum[[i, j]] - expected;
275 if diff.norm() > self.tolerance {
276 return Err(QuantRS2Error::InvalidInput(
277 "Kraus operators do not satisfy completeness relation".to_string(),
278 ));
279 }
280 }
281 }
282
283 Ok(())
284 } else {
285 Ok(())
286 }
287 }
288
289 fn verify_choi_properties(&self) -> QuantRS2Result<()> {
291 if let Some(choi) = &self.choi {
292 let choi_dag = choi.matrix.mapv(|z| z.conj()).t().to_owned();
294 let diff = &choi.matrix - &choi_dag;
295 let max_diff = diff.iter().map(|z| z.norm()).fold(0.0, f64::max);
296
297 if max_diff > self.tolerance {
298 return Err(QuantRS2Error::InvalidInput(
299 "Choi matrix is not Hermitian".to_string(),
300 ));
301 }
302
303 Ok(())
310 } else {
311 Ok(())
312 }
313 }
314
315 fn kraus_to_choi(
317 &self,
318 operators: &[Array2<Complex<f64>>],
319 ) -> QuantRS2Result<ChoiRepresentation> {
320 let d_in = self.input_dim;
321 let d_out = self.output_dim;
322 let total_dim = d_in * d_out;
323
324 let mut choi = Array2::zeros((total_dim, total_dim));
325
326 let mut omega = Array2::zeros((d_in * d_in, 1));
328 for i in 0..d_in {
329 omega[[i * d_in + i, 0]] = Complex::new(1.0, 0.0);
330 }
331 omega = omega / Complex::new((d_in as f64).sqrt(), 0.0);
332
333 for k in operators {
335 let k_vec = self.vectorize_operator(k);
337 let k_vec_dag = k_vec.mapv(|z| z.conj()).t().to_owned();
338
339 choi = choi + k_vec.dot(&k_vec_dag);
341 }
342
343 Ok(ChoiRepresentation { matrix: choi })
344 }
345
346 fn choi_to_kraus(&self, choi: &Array2<Complex<f64>>) -> QuantRS2Result<KrausRepresentation> {
348 let mut operators = Vec::new();
353
354 let identity = Array2::eye(self.output_dim);
356 operators.push(identity.mapv(|x| Complex::new(x, 0.0)));
357
358 Ok(KrausRepresentation { operators })
359 }
360
361 fn kraus_to_stinespring(
363 &self,
364 operators: &[Array2<Complex<f64>>],
365 ) -> QuantRS2Result<StinespringRepresentation> {
366 let num_kraus = operators.len();
367 let d_in = self.input_dim;
368 let d_out = self.output_dim;
369
370 let env_dim = num_kraus;
372
373 let total_out_dim = d_out * env_dim;
375 let mut isometry = Array2::zeros((total_out_dim, d_in));
376
377 for (i, k) in operators.iter().enumerate() {
378 let start_row = i * d_out;
380 let end_row = (i + 1) * d_out;
381
382 isometry.slice_mut(s![start_row..end_row, ..]).assign(k);
383 }
384
385 Ok(StinespringRepresentation { isometry, env_dim })
386 }
387
388 fn stinespring_to_kraus(
390 &self,
391 isometry: &Array2<Complex<f64>>,
392 env_dim: usize,
393 ) -> QuantRS2Result<KrausRepresentation> {
394 let d_out = self.output_dim;
395 let mut operators = Vec::new();
396
397 for i in 0..env_dim {
399 let start_row = i * d_out;
400 let end_row = (i + 1) * d_out;
401
402 let k = isometry.slice(s![start_row..end_row, ..]).to_owned();
403
404 let norm_sq: f64 = k.iter().map(|z| z.norm_sqr()).sum();
406 if norm_sq > self.tolerance {
407 operators.push(k);
408 }
409 }
410
411 Ok(KrausRepresentation { operators })
412 }
413
414 fn vectorize_operator(&self, op: &Array2<Complex<f64>>) -> Array2<Complex<f64>> {
416 let (rows, cols) = op.dim();
417 let mut vec = Array2::zeros((rows * cols, 1));
418
419 for j in 0..cols {
420 for i in 0..rows {
421 vec[[i + j * rows, 0]] = op[[i, j]];
422 }
423 }
424
425 vec
426 }
427}
428
429pub struct QuantumChannels;
431
432impl QuantumChannels {
433 pub fn depolarizing(p: f64) -> QuantRS2Result<QuantumChannel> {
435 if p < 0.0 || p > 1.0 {
436 return Err(QuantRS2Error::InvalidInput(
437 "Depolarizing parameter must be in [0, 1]".to_string(),
438 ));
439 }
440
441 let sqrt_1_minus_3p_4 = ((1.0 - 3.0 * p / 4.0).max(0.0)).sqrt();
442 let sqrt_p_4 = (p / 4.0).sqrt();
443
444 let operators = vec![
445 Array2::from_shape_vec(
447 (2, 2),
448 vec![
449 Complex::new(sqrt_1_minus_3p_4, 0.0),
450 Complex::new(0.0, 0.0),
451 Complex::new(0.0, 0.0),
452 Complex::new(sqrt_1_minus_3p_4, 0.0),
453 ],
454 )
455 .unwrap(),
456 Array2::from_shape_vec(
458 (2, 2),
459 vec![
460 Complex::new(0.0, 0.0),
461 Complex::new(sqrt_p_4, 0.0),
462 Complex::new(sqrt_p_4, 0.0),
463 Complex::new(0.0, 0.0),
464 ],
465 )
466 .unwrap(),
467 Array2::from_shape_vec(
469 (2, 2),
470 vec![
471 Complex::new(0.0, 0.0),
472 Complex::new(0.0, -sqrt_p_4),
473 Complex::new(0.0, sqrt_p_4),
474 Complex::new(0.0, 0.0),
475 ],
476 )
477 .unwrap(),
478 Array2::from_shape_vec(
480 (2, 2),
481 vec![
482 Complex::new(sqrt_p_4, 0.0),
483 Complex::new(0.0, 0.0),
484 Complex::new(0.0, 0.0),
485 Complex::new(-sqrt_p_4, 0.0),
486 ],
487 )
488 .unwrap(),
489 ];
490
491 QuantumChannel::from_kraus(operators)
492 }
493
494 pub fn amplitude_damping(gamma: f64) -> QuantRS2Result<QuantumChannel> {
496 if gamma < 0.0 || gamma > 1.0 {
497 return Err(QuantRS2Error::InvalidInput(
498 "Damping parameter must be in [0, 1]".to_string(),
499 ));
500 }
501
502 let sqrt_gamma = gamma.sqrt();
503 let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
504
505 let operators = vec![
506 Array2::from_shape_vec(
508 (2, 2),
509 vec![
510 Complex::new(1.0, 0.0),
511 Complex::new(0.0, 0.0),
512 Complex::new(0.0, 0.0),
513 Complex::new(sqrt_1_minus_gamma, 0.0),
514 ],
515 )
516 .unwrap(),
517 Array2::from_shape_vec(
519 (2, 2),
520 vec![
521 Complex::new(0.0, 0.0),
522 Complex::new(sqrt_gamma, 0.0),
523 Complex::new(0.0, 0.0),
524 Complex::new(0.0, 0.0),
525 ],
526 )
527 .unwrap(),
528 ];
529
530 QuantumChannel::from_kraus(operators)
531 }
532
533 pub fn phase_damping(gamma: f64) -> QuantRS2Result<QuantumChannel> {
535 if gamma < 0.0 || gamma > 1.0 {
536 return Err(QuantRS2Error::InvalidInput(
537 "Damping parameter must be in [0, 1]".to_string(),
538 ));
539 }
540
541 let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
542 let sqrt_gamma = gamma.sqrt();
543
544 let operators = vec![
545 Array2::from_shape_vec(
547 (2, 2),
548 vec![
549 Complex::new(sqrt_1_minus_gamma, 0.0),
550 Complex::new(0.0, 0.0),
551 Complex::new(0.0, 0.0),
552 Complex::new(sqrt_1_minus_gamma, 0.0),
553 ],
554 )
555 .unwrap(),
556 Array2::from_shape_vec(
558 (2, 2),
559 vec![
560 Complex::new(sqrt_gamma, 0.0),
561 Complex::new(0.0, 0.0),
562 Complex::new(0.0, 0.0),
563 Complex::new(-sqrt_gamma, 0.0),
564 ],
565 )
566 .unwrap(),
567 ];
568
569 QuantumChannel::from_kraus(operators)
570 }
571
572 pub fn bit_flip(p: f64) -> QuantRS2Result<QuantumChannel> {
574 if p < 0.0 || p > 1.0 {
575 return Err(QuantRS2Error::InvalidInput(
576 "Flip probability must be in [0, 1]".to_string(),
577 ));
578 }
579
580 let sqrt_1_minus_p = (1.0 - p).sqrt();
581 let sqrt_p = p.sqrt();
582
583 let operators = vec![
584 Array2::from_shape_vec(
586 (2, 2),
587 vec![
588 Complex::new(sqrt_1_minus_p, 0.0),
589 Complex::new(0.0, 0.0),
590 Complex::new(0.0, 0.0),
591 Complex::new(sqrt_1_minus_p, 0.0),
592 ],
593 )
594 .unwrap(),
595 Array2::from_shape_vec(
597 (2, 2),
598 vec![
599 Complex::new(0.0, 0.0),
600 Complex::new(sqrt_p, 0.0),
601 Complex::new(sqrt_p, 0.0),
602 Complex::new(0.0, 0.0),
603 ],
604 )
605 .unwrap(),
606 ];
607
608 QuantumChannel::from_kraus(operators)
609 }
610
611 pub fn phase_flip(p: f64) -> QuantRS2Result<QuantumChannel> {
613 if p < 0.0 || p > 1.0 {
614 return Err(QuantRS2Error::InvalidInput(
615 "Flip probability must be in [0, 1]".to_string(),
616 ));
617 }
618
619 let sqrt_1_minus_p = (1.0 - p).sqrt();
620 let sqrt_p = p.sqrt();
621
622 let operators = vec![
623 Array2::from_shape_vec(
625 (2, 2),
626 vec![
627 Complex::new(sqrt_1_minus_p, 0.0),
628 Complex::new(0.0, 0.0),
629 Complex::new(0.0, 0.0),
630 Complex::new(sqrt_1_minus_p, 0.0),
631 ],
632 )
633 .unwrap(),
634 Array2::from_shape_vec(
636 (2, 2),
637 vec![
638 Complex::new(sqrt_p, 0.0),
639 Complex::new(0.0, 0.0),
640 Complex::new(0.0, 0.0),
641 Complex::new(-sqrt_p, 0.0),
642 ],
643 )
644 .unwrap(),
645 ];
646
647 QuantumChannel::from_kraus(operators)
648 }
649}
650
651pub struct ProcessTomography;
653
654impl ProcessTomography {
655 pub fn reconstruct_channel(
657 input_states: &[Array2<Complex<f64>>],
658 output_states: &[Array2<Complex<f64>>],
659 ) -> QuantRS2Result<QuantumChannel> {
660 if input_states.len() != output_states.len() {
661 return Err(QuantRS2Error::InvalidInput(
662 "Number of input and output states must match".to_string(),
663 ));
664 }
665
666 let d = input_states[0].shape()[0];
671 let identity = Array2::eye(d).mapv(|x| Complex::new(x, 0.0));
672
673 QuantumChannel::from_kraus(vec![identity])
674 }
675
676 pub fn generate_input_states(dim: usize) -> Vec<Array2<Complex<f64>>> {
678 let mut states = Vec::new();
679
680 for i in 0..dim {
682 let mut state = Array2::zeros((dim, dim));
683 state[[i, i]] = Complex::new(1.0, 0.0);
684 states.push(state);
685 }
686
687 states
691 }
692}
693
694#[cfg(test)]
695mod tests {
696 use super::*;
697 use num_complex::Complex;
698
699 #[test]
700 fn test_depolarizing_channel() {
701 let channel = QuantumChannels::depolarizing(0.1).unwrap();
702
703 assert_eq!(channel.input_dim, 2);
704 assert_eq!(channel.output_dim, 2);
705 assert!(channel.kraus.is_some());
706 assert_eq!(channel.kraus.as_ref().unwrap().operators.len(), 4);
707 }
708
709 #[test]
710 fn test_amplitude_damping() {
711 let channel = QuantumChannels::amplitude_damping(0.3).unwrap();
712
713 assert!(channel.kraus.is_some());
714 assert_eq!(channel.kraus.as_ref().unwrap().operators.len(), 2);
715
716 let mut rho = Array2::zeros((2, 2));
718 rho[[1, 1]] = Complex::new(1.0, 0.0);
719
720 let mut ch = channel;
721 let output = ch.apply(&rho).unwrap();
722
723 assert!(output[[1, 1]].re < 1.0);
725 assert!(output[[0, 0]].re > 0.0);
726 }
727
728 #[test]
729 fn test_kraus_to_choi() {
730 let mut channel = QuantumChannels::bit_flip(0.2).unwrap();
731 let choi = channel.to_choi().unwrap();
732
733 assert_eq!(choi.matrix.shape(), [4, 4]);
734
735 let choi_dag = choi.matrix.mapv(|z| z.conj()).t().to_owned();
737 let diff = &choi.matrix - &choi_dag;
738 let max_diff = diff.iter().map(|z| z.norm()).fold(0.0, f64::max);
739 assert!(max_diff < 1e-10);
740 }
741
742 #[test]
743 fn test_channel_composition() {
744 let mut ch1 = QuantumChannels::phase_flip(0.1).unwrap();
746 let mut ch2 = QuantumChannels::bit_flip(0.2).unwrap();
747
748 let mut rho = Array2::zeros((2, 2));
750 rho[[0, 0]] = Complex::new(0.5, 0.0);
751 rho[[0, 1]] = Complex::new(0.5, 0.0);
752 rho[[1, 0]] = Complex::new(0.5, 0.0);
753 rho[[1, 1]] = Complex::new(0.5, 0.0);
754
755 let intermediate = ch1.apply(&rho).unwrap();
756 let final_state = ch2.apply(&intermediate).unwrap();
757
758 let trace = final_state[[0, 0]] + final_state[[1, 1]];
760 assert!((trace.re - 1.0).abs() < 1e-10);
761 assert!(trace.im.abs() < 1e-10);
762 }
763
764 #[test]
765 fn test_unitary_channel() {
766 let h = Array2::from_shape_vec(
768 (2, 2),
769 vec![
770 Complex::new(1.0, 0.0),
771 Complex::new(1.0, 0.0),
772 Complex::new(1.0, 0.0),
773 Complex::new(-1.0, 0.0),
774 ],
775 )
776 .unwrap()
777 / Complex::new(2.0_f64.sqrt(), 0.0);
778
779 let mut channel = QuantumChannel::from_kraus(vec![h]).unwrap();
780
781 assert!(channel.is_unitary().unwrap());
782 }
783
784 #[test]
785 fn test_stinespring_conversion() {
786 let mut channel = QuantumChannels::amplitude_damping(0.5).unwrap();
787
788 let stinespring = channel.to_stinespring().unwrap();
790
791 assert_eq!(stinespring.env_dim, 2);
792 assert_eq!(stinespring.isometry.shape(), [4, 2]);
793
794 let kraus_decomposer =
796 QuantumChannel::from_kraus(vec![Array2::eye(2).mapv(|x| Complex::new(x, 0.0))])
797 .unwrap();
798 let kraus = kraus_decomposer
799 .stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)
800 .unwrap();
801 assert_eq!(kraus.operators.len(), 2);
802 }
803}