1use crate::error::SimulatorError;
36use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
37use scirs2_core::random::prelude::*;
38use scirs2_core::{Complex64, ComplexFloat};
39use std::collections::HashMap;
40use std::sync::Arc;
41
42pub type NoiseResult<T> = Result<T, SimulatorError>;
44
45pub trait NoiseChannel: Send + Sync {
50 fn kraus_operators(&self) -> Vec<Array2<Complex64>>;
52
53 fn name(&self) -> &str;
55
56 fn num_qubits(&self) -> usize;
58
59 fn apply(&self, state: &ArrayView1<Complex64>) -> NoiseResult<Array1<Complex64>> {
63 let kraus_ops = self.kraus_operators();
64 let dim = state.len();
65
66 if dim != 2_usize.pow(self.num_qubits() as u32) {
68 return Err(SimulatorError::DimensionMismatch(format!(
69 "State dimension {} does not match {} qubits (expected {})",
70 dim,
71 self.num_qubits(),
72 2_usize.pow(self.num_qubits() as u32)
73 )));
74 }
75
76 let mut rng = thread_rng();
78 let total_prob: f64 = kraus_ops
79 .iter()
80 .map(|k| {
81 let result = k.dot(state);
83 result.iter().map(|c| c.norm_sqr()).sum::<f64>()
84 })
85 .sum();
86
87 let mut cumulative = 0.0;
89 let sample: f64 = rng.gen();
90
91 for k in &kraus_ops {
92 let result = k.dot(state);
93 let prob = result.iter().map(|c| c.norm_sqr()).sum::<f64>() / total_prob;
94 cumulative += prob;
95
96 if sample < cumulative {
97 let norm = result.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
99 return Ok(result.mapv(|c| c / norm));
100 }
101 }
102
103 let result = kraus_ops.last().unwrap().dot(state);
105 let norm = result.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
106 Ok(result.mapv(|c| c / norm))
107 }
108
109 fn verify_completeness(&self) -> bool {
111 let kraus_ops = self.kraus_operators();
112 let dim = 2_usize.pow(self.num_qubits() as u32);
113
114 let mut sum = Array2::<Complex64>::zeros((dim, dim));
116 for k in &kraus_ops {
117 for i in 0..dim {
119 for j in 0..dim {
120 let mut val = Complex64::new(0.0, 0.0);
121 for m in 0..dim {
122 val += k[[m, i]].conj() * k[[m, j]];
123 }
124 sum[[i, j]] += val;
125 }
126 }
127 }
128
129 let mut is_identity = true;
131 for i in 0..dim {
132 for j in 0..dim {
133 let expected = if i == j {
134 Complex64::new(1.0, 0.0)
135 } else {
136 Complex64::new(0.0, 0.0)
137 };
138 let diff: Complex64 = sum[[i, j]] - expected;
139 if diff.norm() > 1e-10 {
140 is_identity = false;
141 }
142 }
143 }
144 is_identity
145 }
146}
147
148pub struct DepolarizingNoise {
157 pub probability: f64,
159 num_qubits: usize,
160}
161
162impl DepolarizingNoise {
163 pub fn new(probability: f64) -> Self {
165 assert!(
166 (0.0..=1.0).contains(&probability),
167 "Probability must be between 0 and 1"
168 );
169 Self {
170 probability,
171 num_qubits: 1,
172 }
173 }
174
175 pub fn new_two_qubit(probability: f64) -> Self {
177 assert!(
178 (0.0..=1.0).contains(&probability),
179 "Probability must be between 0 and 1"
180 );
181 Self {
182 probability,
183 num_qubits: 2,
184 }
185 }
186}
187
188impl NoiseChannel for DepolarizingNoise {
189 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
190 if self.num_qubits == 1 {
191 let p = self.probability;
192 let sqrt_1mp = (1.0 - p).sqrt();
193 let sqrt_p3 = (p / 3.0).sqrt();
194
195 vec![
196 Array2::from_diag(&Array1::from_vec(vec![
198 Complex64::new(sqrt_1mp, 0.0),
199 Complex64::new(sqrt_1mp, 0.0),
200 ])),
201 Array2::from_shape_vec(
203 (2, 2),
204 vec![
205 Complex64::new(0.0, 0.0),
206 Complex64::new(sqrt_p3, 0.0),
207 Complex64::new(sqrt_p3, 0.0),
208 Complex64::new(0.0, 0.0),
209 ],
210 )
211 .unwrap(),
212 Array2::from_shape_vec(
214 (2, 2),
215 vec![
216 Complex64::new(0.0, 0.0),
217 Complex64::new(0.0, -sqrt_p3),
218 Complex64::new(0.0, sqrt_p3),
219 Complex64::new(0.0, 0.0),
220 ],
221 )
222 .unwrap(),
223 Array2::from_shape_vec(
225 (2, 2),
226 vec![
227 Complex64::new(sqrt_p3, 0.0),
228 Complex64::new(0.0, 0.0),
229 Complex64::new(0.0, 0.0),
230 Complex64::new(-sqrt_p3, 0.0),
231 ],
232 )
233 .unwrap(),
234 ]
235 } else {
236 let p = self.probability;
238 let sqrt_1mp = (1.0 - p).sqrt();
239 let sqrt_p15 = (p / 15.0).sqrt();
240
241 let mut kraus_ops = Vec::new();
242
243 kraus_ops.push(Array2::from_diag(&Array1::from_vec(vec![
245 Complex64::new(
246 sqrt_1mp, 0.0
247 );
248 4
249 ])));
250
251 for _ in 0..15 {
255 kraus_ops.push(Array2::from_diag(&Array1::from_vec(vec![
256 Complex64::new(
257 sqrt_p15, 0.0
258 );
259 4
260 ])));
261 }
262
263 kraus_ops
264 }
265 }
266
267 fn name(&self) -> &str {
268 if self.num_qubits == 1 {
269 "DepolarizingNoise1Q"
270 } else {
271 "DepolarizingNoise2Q"
272 }
273 }
274
275 fn num_qubits(&self) -> usize {
276 self.num_qubits
277 }
278}
279
280pub struct BitFlipNoise {
285 pub probability: f64,
286}
287
288impl BitFlipNoise {
289 pub fn new(probability: f64) -> Self {
290 assert!(
291 (0.0..=1.0).contains(&probability),
292 "Probability must be between 0 and 1"
293 );
294 Self { probability }
295 }
296}
297
298impl NoiseChannel for BitFlipNoise {
299 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
300 let p = self.probability;
301 vec![
302 Array2::from_diag(&Array1::from_vec(vec![
304 Complex64::new((1.0 - p).sqrt(), 0.0),
305 Complex64::new((1.0 - p).sqrt(), 0.0),
306 ])),
307 Array2::from_shape_vec(
309 (2, 2),
310 vec![
311 Complex64::new(0.0, 0.0),
312 Complex64::new(p.sqrt(), 0.0),
313 Complex64::new(p.sqrt(), 0.0),
314 Complex64::new(0.0, 0.0),
315 ],
316 )
317 .unwrap(),
318 ]
319 }
320
321 fn name(&self) -> &str {
322 "BitFlipNoise"
323 }
324
325 fn num_qubits(&self) -> usize {
326 1
327 }
328}
329
330pub struct PhaseFlipNoise {
335 pub probability: f64,
336}
337
338impl PhaseFlipNoise {
339 pub fn new(probability: f64) -> Self {
340 assert!(
341 (0.0..=1.0).contains(&probability),
342 "Probability must be between 0 and 1"
343 );
344 Self { probability }
345 }
346}
347
348impl NoiseChannel for PhaseFlipNoise {
349 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
350 let p = self.probability;
351 vec![
352 Array2::from_diag(&Array1::from_vec(vec![
354 Complex64::new((1.0 - p).sqrt(), 0.0),
355 Complex64::new((1.0 - p).sqrt(), 0.0),
356 ])),
357 Array2::from_shape_vec(
359 (2, 2),
360 vec![
361 Complex64::new(p.sqrt(), 0.0),
362 Complex64::new(0.0, 0.0),
363 Complex64::new(0.0, 0.0),
364 Complex64::new(-p.sqrt(), 0.0),
365 ],
366 )
367 .unwrap(),
368 ]
369 }
370
371 fn name(&self) -> &str {
372 "PhaseFlipNoise"
373 }
374
375 fn num_qubits(&self) -> usize {
376 1
377 }
378}
379
380pub struct AmplitudeDampingNoise {
385 pub gamma: f64,
387}
388
389impl AmplitudeDampingNoise {
390 pub fn new(gamma: f64) -> Self {
391 assert!(
392 (0.0..=1.0).contains(&gamma),
393 "Gamma must be between 0 and 1"
394 );
395 Self { gamma }
396 }
397}
398
399impl NoiseChannel for AmplitudeDampingNoise {
400 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
401 let g = self.gamma;
402 vec![
403 Array2::from_shape_vec(
405 (2, 2),
406 vec![
407 Complex64::new(1.0, 0.0),
408 Complex64::new(0.0, 0.0),
409 Complex64::new(0.0, 0.0),
410 Complex64::new((1.0 - g).sqrt(), 0.0),
411 ],
412 )
413 .unwrap(),
414 Array2::from_shape_vec(
416 (2, 2),
417 vec![
418 Complex64::new(0.0, 0.0),
419 Complex64::new(g.sqrt(), 0.0),
420 Complex64::new(0.0, 0.0),
421 Complex64::new(0.0, 0.0),
422 ],
423 )
424 .unwrap(),
425 ]
426 }
427
428 fn name(&self) -> &str {
429 "AmplitudeDampingNoise"
430 }
431
432 fn num_qubits(&self) -> usize {
433 1
434 }
435}
436
437pub struct PhaseDampingNoise {
442 pub lambda: f64,
444}
445
446impl PhaseDampingNoise {
447 pub fn new(lambda: f64) -> Self {
448 assert!(
449 (0.0..=1.0).contains(&lambda),
450 "Lambda must be between 0 and 1"
451 );
452 Self { lambda }
453 }
454}
455
456impl NoiseChannel for PhaseDampingNoise {
457 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
458 let l = self.lambda;
459 vec![
460 Array2::from_shape_vec(
462 (2, 2),
463 vec![
464 Complex64::new(1.0, 0.0),
465 Complex64::new(0.0, 0.0),
466 Complex64::new(0.0, 0.0),
467 Complex64::new((1.0 - l).sqrt(), 0.0),
468 ],
469 )
470 .unwrap(),
471 Array2::from_shape_vec(
473 (2, 2),
474 vec![
475 Complex64::new(0.0, 0.0),
476 Complex64::new(0.0, 0.0),
477 Complex64::new(0.0, 0.0),
478 Complex64::new(l.sqrt(), 0.0),
479 ],
480 )
481 .unwrap(),
482 ]
483 }
484
485 fn name(&self) -> &str {
486 "PhaseDampingNoise"
487 }
488
489 fn num_qubits(&self) -> usize {
490 1
491 }
492}
493
494pub struct ThermalRelaxationNoise {
499 pub t1: f64,
501 pub t2: f64,
503 pub gate_time: f64,
505 pub excited_state_pop: f64,
507}
508
509impl ThermalRelaxationNoise {
510 pub fn new(t1: f64, t2: f64, gate_time: f64) -> Self {
511 assert!(t1 > 0.0, "T1 must be positive");
512 assert!(t2 > 0.0, "T2 must be positive");
513 assert!(t2 <= 2.0 * t1, "T2 must satisfy T2 ≤ 2T1");
514 assert!(gate_time >= 0.0, "Gate time must be non-negative");
515
516 Self {
517 t1,
518 t2,
519 gate_time,
520 excited_state_pop: 0.0,
521 }
522 }
523
524 pub fn with_thermal_population(mut self, excited_state_pop: f64) -> Self {
525 assert!(
526 (0.0..=1.0).contains(&excited_state_pop),
527 "Excited state population must be between 0 and 1"
528 );
529 self.excited_state_pop = excited_state_pop;
530 self
531 }
532}
533
534impl NoiseChannel for ThermalRelaxationNoise {
535 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
536 let t = self.gate_time;
537 let t1 = self.t1;
538 let t2 = self.t2;
539 let p_reset = 1.0 - (-t / t1).exp();
540 let p_z = (1.0 - (-t / t2).exp()) - p_reset / 2.0;
541
542 let p_excited = self.excited_state_pop;
544
545 vec![
546 Array2::from_shape_vec(
548 (2, 2),
549 vec![
550 Complex64::new((1.0 - p_reset - p_z).sqrt(), 0.0),
551 Complex64::new(0.0, 0.0),
552 Complex64::new(0.0, 0.0),
553 Complex64::new((1.0 - p_reset - p_z).sqrt(), 0.0),
554 ],
555 )
556 .unwrap(),
557 Array2::from_shape_vec(
559 (2, 2),
560 vec![
561 Complex64::new(0.0, 0.0),
562 Complex64::new((p_reset * (1.0 - p_excited)).sqrt(), 0.0),
563 Complex64::new(0.0, 0.0),
564 Complex64::new(0.0, 0.0),
565 ],
566 )
567 .unwrap(),
568 Array2::from_shape_vec(
570 (2, 2),
571 vec![
572 Complex64::new(p_z.sqrt(), 0.0),
573 Complex64::new(0.0, 0.0),
574 Complex64::new(0.0, 0.0),
575 Complex64::new(-p_z.sqrt(), 0.0),
576 ],
577 )
578 .unwrap(),
579 ]
580 }
581
582 fn name(&self) -> &str {
583 "ThermalRelaxationNoise"
584 }
585
586 fn num_qubits(&self) -> usize {
587 1
588 }
589}
590
591#[derive(Clone)]
595pub struct NoiseModel {
596 global_channels: Vec<Arc<dyn NoiseChannel>>,
598 gate_channels: HashMap<String, Vec<Arc<dyn NoiseChannel>>>,
600 measurement_noise: Option<Arc<dyn NoiseChannel>>,
602 idle_noise: Option<Arc<dyn NoiseChannel>>,
604}
605
606impl NoiseModel {
607 pub fn new() -> Self {
609 Self {
610 global_channels: Vec::new(),
611 gate_channels: HashMap::new(),
612 measurement_noise: None,
613 idle_noise: None,
614 }
615 }
616
617 pub fn add_channel(&mut self, channel: Arc<dyn NoiseChannel>) {
619 self.global_channels.push(channel);
620 }
621
622 pub fn add_gate_noise(&mut self, gate_name: &str, channel: Arc<dyn NoiseChannel>) {
624 self.gate_channels
625 .entry(gate_name.to_string())
626 .or_default()
627 .push(channel);
628 }
629
630 pub fn set_measurement_noise(&mut self, channel: Arc<dyn NoiseChannel>) {
632 self.measurement_noise = Some(channel);
633 }
634
635 pub fn set_idle_noise(&mut self, channel: Arc<dyn NoiseChannel>) {
637 self.idle_noise = Some(channel);
638 }
639
640 pub fn apply_single_qubit(
642 &self,
643 state: &Array1<Complex64>,
644 _qubit: usize,
645 ) -> NoiseResult<Array1<Complex64>> {
646 let mut noisy_state = state.clone();
647
648 for channel in &self.global_channels {
650 if channel.num_qubits() == 1 {
651 noisy_state = channel.apply(&noisy_state.view())?;
652 }
653 }
654
655 Ok(noisy_state)
656 }
657
658 pub fn apply_gate_noise(
660 &self,
661 state: &Array1<Complex64>,
662 gate_name: &str,
663 _qubit: usize,
664 ) -> NoiseResult<Array1<Complex64>> {
665 let mut noisy_state = state.clone();
666
667 if let Some(channels) = self.gate_channels.get(gate_name) {
668 for channel in channels {
669 noisy_state = channel.apply(&noisy_state.view())?;
670 }
671 }
672
673 Ok(noisy_state)
674 }
675
676 pub fn num_global_channels(&self) -> usize {
678 self.global_channels.len()
679 }
680
681 pub fn has_measurement_noise(&self) -> bool {
683 self.measurement_noise.is_some()
684 }
685}
686
687impl Default for NoiseModel {
688 fn default() -> Self {
689 Self::new()
690 }
691}
692
693#[cfg(test)]
694mod tests {
695 use super::*;
696
697 #[test]
698 fn test_depolarizing_noise_kraus() {
699 let noise = DepolarizingNoise::new(0.1);
700 let kraus = noise.kraus_operators();
701
702 assert_eq!(kraus.len(), 4);
704
705 assert!(noise.verify_completeness());
707 }
708
709 #[test]
710 fn test_bit_flip_noise() {
711 let noise = BitFlipNoise::new(0.2);
712 let kraus = noise.kraus_operators();
713
714 assert_eq!(kraus.len(), 2);
715 assert!(noise.verify_completeness());
716 }
717
718 #[test]
719 fn test_phase_flip_noise() {
720 let noise = PhaseFlipNoise::new(0.15);
721 let kraus = noise.kraus_operators();
722
723 assert_eq!(kraus.len(), 2);
724 assert!(noise.verify_completeness());
725 }
726
727 #[test]
728 fn test_amplitude_damping() {
729 let noise = AmplitudeDampingNoise::new(0.05);
730 let kraus = noise.kraus_operators();
731
732 assert_eq!(kraus.len(), 2);
733 assert!(noise.verify_completeness());
734 }
735
736 #[test]
737 fn test_phase_damping() {
738 let noise = PhaseDampingNoise::new(0.1);
739 let kraus = noise.kraus_operators();
740
741 assert_eq!(kraus.len(), 2);
742 assert!(noise.verify_completeness());
743 }
744
745 #[test]
746 fn test_thermal_relaxation() {
747 let noise = ThermalRelaxationNoise::new(50.0, 40.0, 1.0);
748 let kraus = noise.kraus_operators();
749
750 assert_eq!(kraus.len(), 3);
751 }
754
755 #[test]
756 fn test_noise_application() {
757 let noise = DepolarizingNoise::new(0.01);
758
759 let state = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
761
762 for _ in 0..10 {
764 let noisy_state = noise.apply(&state.view()).unwrap();
765 let norm: f64 = noisy_state.iter().map(|c| c.norm_sqr()).sum();
766 assert!((norm - 1.0).abs() < 1e-10, "State not normalized: {}", norm);
767 }
768 }
769
770 #[test]
771 fn test_noise_model() {
772 let mut model = NoiseModel::new();
773
774 model.add_channel(Arc::new(DepolarizingNoise::new(0.01)));
776
777 model.add_gate_noise("X", Arc::new(BitFlipNoise::new(0.02)));
779
780 assert_eq!(model.num_global_channels(), 1);
781 assert!(!model.has_measurement_noise());
782
783 let state = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
785 let noisy = model.apply_single_qubit(&state, 0).unwrap();
786
787 let norm: f64 = noisy.iter().map(|c| c.norm_sqr()).sum();
788 assert!((norm - 1.0).abs() < 1e-10);
789 }
790
791 #[test]
792 fn test_noise_model_composition() {
793 let mut model = NoiseModel::new();
794
795 model.add_channel(Arc::new(DepolarizingNoise::new(0.005)));
797 model.add_channel(Arc::new(AmplitudeDampingNoise::new(0.01)));
798 model.add_channel(Arc::new(PhaseDampingNoise::new(0.008)));
799
800 assert_eq!(model.num_global_channels(), 3);
801
802 let state = Array1::from_vec(vec![
804 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
805 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
806 ]);
807 let noisy = model.apply_single_qubit(&state, 0).unwrap();
808
809 let norm: f64 = noisy.iter().map(|c| c.norm_sqr()).sum();
810 assert!((norm - 1.0).abs() < 1e-10);
811 }
812}