1use crate::{
7 error::{QuantRS2Error, QuantRS2Result},
8 matrix_ops::{DenseMatrix, QuantumMatrix},
9};
10use ndarray::{s, Array2};
11use num_complex::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 {} has inconsistent dimensions",
72 i
73 )));
74 }
75 }
76
77 let kraus = KrausRepresentation { operators };
78
79 let channel = Self {
80 input_dim,
81 output_dim,
82 kraus: Some(kraus),
83 choi: None,
84 stinespring: None,
85 tolerance: 1e-10,
86 };
87
88 channel.verify_kraus_completeness()?;
90
91 Ok(channel)
92 }
93
94 pub fn from_choi(matrix: Array2<Complex<f64>>) -> QuantRS2Result<Self> {
96 let total_dim = matrix.shape()[0];
97
98 if matrix.shape()[0] != matrix.shape()[1] {
100 return Err(QuantRS2Error::InvalidInput(
101 "Choi matrix must be square".to_string(),
102 ));
103 }
104
105 let dim = (total_dim as f64).sqrt() as usize;
107 if dim * dim != total_dim {
108 return Err(QuantRS2Error::InvalidInput(
109 "Choi matrix dimension must be perfect square".to_string(),
110 ));
111 }
112
113 let choi = ChoiRepresentation { matrix };
114
115 let channel = Self {
116 input_dim: dim,
117 output_dim: dim,
118 kraus: None,
119 choi: Some(choi),
120 stinespring: None,
121 tolerance: 1e-10,
122 };
123
124 channel.verify_choi_properties()?;
126
127 Ok(channel)
128 }
129
130 pub fn to_kraus(&mut self) -> QuantRS2Result<&KrausRepresentation> {
132 if self.kraus.is_some() {
133 return Ok(self.kraus.as_ref().unwrap());
134 }
135
136 if let Some(choi) = &self.choi {
137 let kraus = self.choi_to_kraus(&choi.matrix)?;
138 self.kraus = Some(kraus);
139 Ok(self.kraus.as_ref().unwrap())
140 } else if let Some(stinespring) = &self.stinespring {
141 let kraus = self.stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)?;
142 self.kraus = Some(kraus);
143 Ok(self.kraus.as_ref().unwrap())
144 } else {
145 Err(QuantRS2Error::InvalidInput(
146 "No representation available".to_string(),
147 ))
148 }
149 }
150
151 pub fn to_choi(&mut self) -> QuantRS2Result<&ChoiRepresentation> {
153 if self.choi.is_some() {
154 return Ok(self.choi.as_ref().unwrap());
155 }
156
157 if let Some(kraus) = &self.kraus {
158 let choi = self.kraus_to_choi(&kraus.operators)?;
159 self.choi = Some(choi);
160 Ok(self.choi.as_ref().unwrap())
161 } else if let Some(stinespring) = &self.stinespring {
162 let kraus = self.stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)?;
164 let choi = self.kraus_to_choi(&kraus.operators)?;
165 self.choi = Some(choi);
166 Ok(self.choi.as_ref().unwrap())
167 } else {
168 Err(QuantRS2Error::InvalidInput(
169 "No representation available".to_string(),
170 ))
171 }
172 }
173
174 pub fn to_stinespring(&mut self) -> QuantRS2Result<&StinespringRepresentation> {
176 if self.stinespring.is_some() {
177 return Ok(self.stinespring.as_ref().unwrap());
178 }
179
180 let kraus = self.to_kraus()?.clone();
182 let stinespring = self.kraus_to_stinespring(&kraus.operators)?;
183 self.stinespring = Some(stinespring);
184 Ok(self.stinespring.as_ref().unwrap())
185 }
186
187 pub fn apply(&mut self, rho: &Array2<Complex<f64>>) -> QuantRS2Result<Array2<Complex<f64>>> {
189 let kraus = self.to_kraus()?.clone();
191 let output_dim = self.output_dim;
192
193 let mut result = Array2::zeros((output_dim, output_dim));
194
195 for k in &kraus.operators {
196 let k_dag = k.mapv(|z| z.conj()).t().to_owned();
197 let term = k.dot(rho).dot(&k_dag);
198 result = result + term;
199 }
200
201 Ok(result)
202 }
203
204 pub fn is_unitary(&mut self) -> QuantRS2Result<bool> {
206 let kraus = self.to_kraus()?;
207
208 if kraus.operators.len() != 1 {
210 return Ok(false);
211 }
212
213 let mat = DenseMatrix::new(kraus.operators[0].clone())?;
214 mat.is_unitary(self.tolerance)
215 }
216
217 pub fn is_depolarizing(&mut self) -> QuantRS2Result<bool> {
219 if self.input_dim != 2 || self.output_dim != 2 {
223 return Ok(false); }
225
226 let kraus = self.to_kraus()?;
227
228 if kraus.operators.len() != 4 {
229 return Ok(false);
230 }
231
232 Ok(true)
235 }
236
237 pub fn depolarizing_parameter(&mut self) -> QuantRS2Result<Option<f64>> {
239 if !self.is_depolarizing()? {
240 return Ok(None);
241 }
242
243 let kraus = self.to_kraus()?;
244
245 let k0_coeff = kraus.operators[0][[0, 0]].norm();
248 let p = 4.0 * (1.0 - k0_coeff * k0_coeff) / 3.0;
249
250 Ok(Some(p))
251 }
252
253 fn verify_kraus_completeness(&self) -> QuantRS2Result<()> {
255 if let Some(kraus) = &self.kraus {
256 let mut sum: Array2<Complex<f64>> = Array2::zeros((self.input_dim, self.input_dim));
257
258 for k in &kraus.operators {
259 let k_dag = k.mapv(|z| z.conj()).t().to_owned();
260 sum = sum + k_dag.dot(k);
261 }
262
263 for i in 0..self.input_dim {
265 for j in 0..self.input_dim {
266 let expected = if i == j {
267 Complex::new(1.0, 0.0)
268 } else {
269 Complex::new(0.0, 0.0)
270 };
271 let diff: Complex<f64> = sum[[i, j]] - expected;
272 if diff.norm() > self.tolerance {
273 return Err(QuantRS2Error::InvalidInput(
274 "Kraus operators do not satisfy completeness relation".to_string(),
275 ));
276 }
277 }
278 }
279
280 Ok(())
281 } else {
282 Ok(())
283 }
284 }
285
286 fn verify_choi_properties(&self) -> QuantRS2Result<()> {
288 if let Some(choi) = &self.choi {
289 let choi_dag = choi.matrix.mapv(|z| z.conj()).t().to_owned();
291 let diff = &choi.matrix - &choi_dag;
292 let max_diff = diff.iter().map(|z| z.norm()).fold(0.0, f64::max);
293
294 if max_diff > self.tolerance {
295 return Err(QuantRS2Error::InvalidInput(
296 "Choi matrix is not Hermitian".to_string(),
297 ));
298 }
299
300 Ok(())
307 } else {
308 Ok(())
309 }
310 }
311
312 fn kraus_to_choi(
314 &self,
315 operators: &[Array2<Complex<f64>>],
316 ) -> QuantRS2Result<ChoiRepresentation> {
317 let d_in = self.input_dim;
318 let d_out = self.output_dim;
319 let total_dim = d_in * d_out;
320
321 let mut choi = Array2::zeros((total_dim, total_dim));
322
323 let mut omega = Array2::zeros((d_in * d_in, 1));
325 for i in 0..d_in {
326 omega[[i * d_in + i, 0]] = Complex::new(1.0, 0.0);
327 }
328 let _omega = omega / Complex::new((d_in as f64).sqrt(), 0.0);
329
330 for k in operators {
332 let k_vec = self.vectorize_operator(k);
334 let k_vec_dag = k_vec.mapv(|z| z.conj()).t().to_owned();
335
336 choi = choi + k_vec.dot(&k_vec_dag);
338 }
339
340 Ok(ChoiRepresentation { matrix: choi })
341 }
342
343 fn choi_to_kraus(&self, _choi: &Array2<Complex<f64>>) -> QuantRS2Result<KrausRepresentation> {
345 let mut operators = Vec::new();
350
351 let identity = Array2::eye(self.output_dim);
353 operators.push(identity.mapv(|x| Complex::new(x, 0.0)));
354
355 Ok(KrausRepresentation { operators })
356 }
357
358 fn kraus_to_stinespring(
360 &self,
361 operators: &[Array2<Complex<f64>>],
362 ) -> QuantRS2Result<StinespringRepresentation> {
363 let num_kraus = operators.len();
364 let d_in = self.input_dim;
365 let d_out = self.output_dim;
366
367 let env_dim = num_kraus;
369
370 let total_out_dim = d_out * env_dim;
372 let mut isometry = Array2::zeros((total_out_dim, d_in));
373
374 for (i, k) in operators.iter().enumerate() {
375 let start_row = i * d_out;
377 let end_row = (i + 1) * d_out;
378
379 isometry.slice_mut(s![start_row..end_row, ..]).assign(k);
380 }
381
382 Ok(StinespringRepresentation { isometry, env_dim })
383 }
384
385 fn stinespring_to_kraus(
387 &self,
388 isometry: &Array2<Complex<f64>>,
389 env_dim: usize,
390 ) -> QuantRS2Result<KrausRepresentation> {
391 let d_out = self.output_dim;
392 let mut operators = Vec::new();
393
394 for i in 0..env_dim {
396 let start_row = i * d_out;
397 let end_row = (i + 1) * d_out;
398
399 let k = isometry.slice(s![start_row..end_row, ..]).to_owned();
400
401 let norm_sq: f64 = k.iter().map(|z| z.norm_sqr()).sum();
403 if norm_sq > self.tolerance {
404 operators.push(k);
405 }
406 }
407
408 Ok(KrausRepresentation { operators })
409 }
410
411 fn vectorize_operator(&self, op: &Array2<Complex<f64>>) -> Array2<Complex<f64>> {
413 let (rows, cols) = op.dim();
414 let mut vec = Array2::zeros((rows * cols, 1));
415
416 for j in 0..cols {
417 for i in 0..rows {
418 vec[[i + j * rows, 0]] = op[[i, j]];
419 }
420 }
421
422 vec
423 }
424}
425
426pub struct QuantumChannels;
428
429impl QuantumChannels {
430 pub fn depolarizing(p: f64) -> QuantRS2Result<QuantumChannel> {
432 if p < 0.0 || p > 1.0 {
433 return Err(QuantRS2Error::InvalidInput(
434 "Depolarizing parameter must be in [0, 1]".to_string(),
435 ));
436 }
437
438 let sqrt_1_minus_3p_4 = ((1.0 - 3.0 * p / 4.0).max(0.0)).sqrt();
439 let sqrt_p_4 = (p / 4.0).sqrt();
440
441 let operators = vec![
442 Array2::from_shape_vec(
444 (2, 2),
445 vec![
446 Complex::new(sqrt_1_minus_3p_4, 0.0),
447 Complex::new(0.0, 0.0),
448 Complex::new(0.0, 0.0),
449 Complex::new(sqrt_1_minus_3p_4, 0.0),
450 ],
451 )
452 .unwrap(),
453 Array2::from_shape_vec(
455 (2, 2),
456 vec![
457 Complex::new(0.0, 0.0),
458 Complex::new(sqrt_p_4, 0.0),
459 Complex::new(sqrt_p_4, 0.0),
460 Complex::new(0.0, 0.0),
461 ],
462 )
463 .unwrap(),
464 Array2::from_shape_vec(
466 (2, 2),
467 vec![
468 Complex::new(0.0, 0.0),
469 Complex::new(0.0, -sqrt_p_4),
470 Complex::new(0.0, sqrt_p_4),
471 Complex::new(0.0, 0.0),
472 ],
473 )
474 .unwrap(),
475 Array2::from_shape_vec(
477 (2, 2),
478 vec![
479 Complex::new(sqrt_p_4, 0.0),
480 Complex::new(0.0, 0.0),
481 Complex::new(0.0, 0.0),
482 Complex::new(-sqrt_p_4, 0.0),
483 ],
484 )
485 .unwrap(),
486 ];
487
488 QuantumChannel::from_kraus(operators)
489 }
490
491 pub fn amplitude_damping(gamma: f64) -> QuantRS2Result<QuantumChannel> {
493 if gamma < 0.0 || gamma > 1.0 {
494 return Err(QuantRS2Error::InvalidInput(
495 "Damping parameter must be in [0, 1]".to_string(),
496 ));
497 }
498
499 let sqrt_gamma = gamma.sqrt();
500 let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
501
502 let operators = vec![
503 Array2::from_shape_vec(
505 (2, 2),
506 vec![
507 Complex::new(1.0, 0.0),
508 Complex::new(0.0, 0.0),
509 Complex::new(0.0, 0.0),
510 Complex::new(sqrt_1_minus_gamma, 0.0),
511 ],
512 )
513 .unwrap(),
514 Array2::from_shape_vec(
516 (2, 2),
517 vec![
518 Complex::new(0.0, 0.0),
519 Complex::new(sqrt_gamma, 0.0),
520 Complex::new(0.0, 0.0),
521 Complex::new(0.0, 0.0),
522 ],
523 )
524 .unwrap(),
525 ];
526
527 QuantumChannel::from_kraus(operators)
528 }
529
530 pub fn phase_damping(gamma: f64) -> QuantRS2Result<QuantumChannel> {
532 if gamma < 0.0 || gamma > 1.0 {
533 return Err(QuantRS2Error::InvalidInput(
534 "Damping parameter must be in [0, 1]".to_string(),
535 ));
536 }
537
538 let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
539 let sqrt_gamma = gamma.sqrt();
540
541 let operators = vec![
542 Array2::from_shape_vec(
544 (2, 2),
545 vec![
546 Complex::new(sqrt_1_minus_gamma, 0.0),
547 Complex::new(0.0, 0.0),
548 Complex::new(0.0, 0.0),
549 Complex::new(sqrt_1_minus_gamma, 0.0),
550 ],
551 )
552 .unwrap(),
553 Array2::from_shape_vec(
555 (2, 2),
556 vec![
557 Complex::new(sqrt_gamma, 0.0),
558 Complex::new(0.0, 0.0),
559 Complex::new(0.0, 0.0),
560 Complex::new(-sqrt_gamma, 0.0),
561 ],
562 )
563 .unwrap(),
564 ];
565
566 QuantumChannel::from_kraus(operators)
567 }
568
569 pub fn bit_flip(p: f64) -> QuantRS2Result<QuantumChannel> {
571 if p < 0.0 || p > 1.0 {
572 return Err(QuantRS2Error::InvalidInput(
573 "Flip probability must be in [0, 1]".to_string(),
574 ));
575 }
576
577 let sqrt_1_minus_p = (1.0 - p).sqrt();
578 let sqrt_p = p.sqrt();
579
580 let operators = vec![
581 Array2::from_shape_vec(
583 (2, 2),
584 vec![
585 Complex::new(sqrt_1_minus_p, 0.0),
586 Complex::new(0.0, 0.0),
587 Complex::new(0.0, 0.0),
588 Complex::new(sqrt_1_minus_p, 0.0),
589 ],
590 )
591 .unwrap(),
592 Array2::from_shape_vec(
594 (2, 2),
595 vec![
596 Complex::new(0.0, 0.0),
597 Complex::new(sqrt_p, 0.0),
598 Complex::new(sqrt_p, 0.0),
599 Complex::new(0.0, 0.0),
600 ],
601 )
602 .unwrap(),
603 ];
604
605 QuantumChannel::from_kraus(operators)
606 }
607
608 pub fn phase_flip(p: f64) -> QuantRS2Result<QuantumChannel> {
610 if p < 0.0 || p > 1.0 {
611 return Err(QuantRS2Error::InvalidInput(
612 "Flip probability must be in [0, 1]".to_string(),
613 ));
614 }
615
616 let sqrt_1_minus_p = (1.0 - p).sqrt();
617 let sqrt_p = p.sqrt();
618
619 let operators = vec![
620 Array2::from_shape_vec(
622 (2, 2),
623 vec![
624 Complex::new(sqrt_1_minus_p, 0.0),
625 Complex::new(0.0, 0.0),
626 Complex::new(0.0, 0.0),
627 Complex::new(sqrt_1_minus_p, 0.0),
628 ],
629 )
630 .unwrap(),
631 Array2::from_shape_vec(
633 (2, 2),
634 vec![
635 Complex::new(sqrt_p, 0.0),
636 Complex::new(0.0, 0.0),
637 Complex::new(0.0, 0.0),
638 Complex::new(-sqrt_p, 0.0),
639 ],
640 )
641 .unwrap(),
642 ];
643
644 QuantumChannel::from_kraus(operators)
645 }
646}
647
648pub struct ProcessTomography;
650
651impl ProcessTomography {
652 pub fn reconstruct_channel(
654 input_states: &[Array2<Complex<f64>>],
655 output_states: &[Array2<Complex<f64>>],
656 ) -> QuantRS2Result<QuantumChannel> {
657 if input_states.len() != output_states.len() {
658 return Err(QuantRS2Error::InvalidInput(
659 "Number of input and output states must match".to_string(),
660 ));
661 }
662
663 let d = input_states[0].shape()[0];
668 let identity = Array2::eye(d).mapv(|x| Complex::new(x, 0.0));
669
670 QuantumChannel::from_kraus(vec![identity])
671 }
672
673 pub fn generate_input_states(dim: usize) -> Vec<Array2<Complex<f64>>> {
675 let mut states = Vec::new();
676
677 for i in 0..dim {
679 let mut state = Array2::zeros((dim, dim));
680 state[[i, i]] = Complex::new(1.0, 0.0);
681 states.push(state);
682 }
683
684 states
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use super::*;
694 use num_complex::Complex;
695
696 #[test]
697 fn test_depolarizing_channel() {
698 let channel = QuantumChannels::depolarizing(0.1).unwrap();
699
700 assert_eq!(channel.input_dim, 2);
701 assert_eq!(channel.output_dim, 2);
702 assert!(channel.kraus.is_some());
703 assert_eq!(channel.kraus.as_ref().unwrap().operators.len(), 4);
704 }
705
706 #[test]
707 fn test_amplitude_damping() {
708 let channel = QuantumChannels::amplitude_damping(0.3).unwrap();
709
710 assert!(channel.kraus.is_some());
711 assert_eq!(channel.kraus.as_ref().unwrap().operators.len(), 2);
712
713 let mut rho = Array2::zeros((2, 2));
715 rho[[1, 1]] = Complex::new(1.0, 0.0);
716
717 let mut ch = channel;
718 let output = ch.apply(&rho).unwrap();
719
720 assert!(output[[1, 1]].re < 1.0);
722 assert!(output[[0, 0]].re > 0.0);
723 }
724
725 #[test]
726 fn test_kraus_to_choi() {
727 let mut channel = QuantumChannels::bit_flip(0.2).unwrap();
728 let choi = channel.to_choi().unwrap();
729
730 assert_eq!(choi.matrix.shape(), [4, 4]);
731
732 let choi_dag = choi.matrix.mapv(|z| z.conj()).t().to_owned();
734 let diff = &choi.matrix - &choi_dag;
735 let max_diff = diff.iter().map(|z| z.norm()).fold(0.0, f64::max);
736 assert!(max_diff < 1e-10);
737 }
738
739 #[test]
740 fn test_channel_composition() {
741 let mut ch1 = QuantumChannels::phase_flip(0.1).unwrap();
743 let mut ch2 = QuantumChannels::bit_flip(0.2).unwrap();
744
745 let mut rho = Array2::zeros((2, 2));
747 rho[[0, 0]] = Complex::new(0.5, 0.0);
748 rho[[0, 1]] = Complex::new(0.5, 0.0);
749 rho[[1, 0]] = Complex::new(0.5, 0.0);
750 rho[[1, 1]] = Complex::new(0.5, 0.0);
751
752 let intermediate = ch1.apply(&rho).unwrap();
753 let final_state = ch2.apply(&intermediate).unwrap();
754
755 let trace = final_state[[0, 0]] + final_state[[1, 1]];
757 assert!((trace.re - 1.0).abs() < 1e-10);
758 assert!(trace.im.abs() < 1e-10);
759 }
760
761 #[test]
762 fn test_unitary_channel() {
763 let h = Array2::from_shape_vec(
765 (2, 2),
766 vec![
767 Complex::new(1.0, 0.0),
768 Complex::new(1.0, 0.0),
769 Complex::new(1.0, 0.0),
770 Complex::new(-1.0, 0.0),
771 ],
772 )
773 .unwrap()
774 / Complex::new(2.0_f64.sqrt(), 0.0);
775
776 let mut channel = QuantumChannel::from_kraus(vec![h]).unwrap();
777
778 assert!(channel.is_unitary().unwrap());
779 }
780
781 #[test]
782 fn test_stinespring_conversion() {
783 let mut channel = QuantumChannels::amplitude_damping(0.5).unwrap();
784
785 let stinespring = channel.to_stinespring().unwrap();
787
788 assert_eq!(stinespring.env_dim, 2);
789 assert_eq!(stinespring.isometry.shape(), [4, 2]);
790
791 let kraus_decomposer =
793 QuantumChannel::from_kraus(vec![Array2::eye(2).mapv(|x| Complex::new(x, 0.0))])
794 .unwrap();
795 let kraus = kraus_decomposer
796 .stinespring_to_kraus(&stinespring.isometry, stinespring.env_dim)
797 .unwrap();
798 assert_eq!(kraus.operators.len(), 2);
799 }
800}