1use crate::{
7 error::{QuantRS2Error, QuantRS2Result},
8 matrix_ops::{DenseMatrix, QuantumMatrix},
9};
10use scirs2_core::ndarray::{s, Array2};
11use scirs2_core::Complex;
12
13#[derive(Debug, Clone)]
15pub struct QuantumChannel {
16 pub input_dim: usize,
18 pub output_dim: usize,
20 pub kraus: Option<KrausRepresentation>,
22 pub choi: Option<ChoiRepresentation>,
24 pub stinespring: Option<StinespringRepresentation>,
26 tolerance: f64,
28}
29
30#[derive(Debug, Clone)]
32pub struct KrausRepresentation {
33 pub operators: Vec<Array2<Complex<f64>>>,
35}
36
37#[derive(Debug, Clone)]
39pub struct ChoiRepresentation {
40 pub matrix: Array2<Complex<f64>>,
42}
43
44#[derive(Debug, Clone)]
46pub struct StinespringRepresentation {
47 pub isometry: Array2<Complex<f64>>,
49 pub env_dim: usize,
51}
52
53impl QuantumChannel {
54 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 let shape = operators[0].shape();
64 let output_dim = shape[0];
65 let input_dim = shape[1];
66
67 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 channel.verify_kraus_completeness()?;
89
90 Ok(channel)
91 }
92
93 pub fn from_choi(matrix: Array2<Complex<f64>>) -> QuantRS2Result<Self> {
95 let total_dim = matrix.shape()[0];
96
97 if matrix.shape()[0] != matrix.shape()[1] {
99 return Err(QuantRS2Error::InvalidInput(
100 "Choi matrix must be square".to_string(),
101 ));
102 }
103
104 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 channel.verify_choi_properties()?;
125
126 Ok(channel)
127 }
128
129 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 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 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 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 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 pub fn apply(&mut self, rho: &Array2<Complex<f64>>) -> QuantRS2Result<Array2<Complex<f64>>> {
206 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 pub fn is_unitary(&mut self) -> QuantRS2Result<bool> {
223 let kraus = self.to_kraus()?;
224
225 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 pub fn is_depolarizing(&mut self) -> QuantRS2Result<bool> {
236 if self.input_dim != 2 || self.output_dim != 2 {
240 return Ok(false); }
242
243 let kraus = self.to_kraus()?;
244
245 if kraus.operators.len() != 4 {
246 return Ok(false);
247 }
248
249 Ok(true)
252 }
253
254 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 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 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 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 fn verify_choi_properties(&self) -> QuantRS2Result<()> {
305 if let Some(choi) = &self.choi {
306 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 Ok(())
324 } else {
325 Ok(())
326 }
327 }
328
329 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 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 for k in operators {
349 let k_vec = self.vectorize_operator(k);
351 let k_vec_dag = k_vec.mapv(|z| z.conj()).t().to_owned();
352
353 choi = choi + k_vec.dot(&k_vec_dag);
355 }
356
357 Ok(ChoiRepresentation { matrix: choi })
358 }
359
360 fn choi_to_kraus(&self, _choi: &Array2<Complex<f64>>) -> QuantRS2Result<KrausRepresentation> {
362 let mut operators = Vec::new();
367
368 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 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 let env_dim = num_kraus;
386
387 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 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 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 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 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 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
443pub struct QuantumChannels;
445
446impl QuantumChannels {
447 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
665pub struct ProcessTomography;
667
668impl ProcessTomography {
669 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 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 pub fn generate_input_states(dim: usize) -> Vec<Array2<Complex<f64>>> {
692 let mut states = Vec::new();
693
694 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 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 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 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 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 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 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 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 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 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 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}