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.random();
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 last_op = kraus_ops
105 .last()
106 .ok_or_else(|| SimulatorError::InvalidState("empty Kraus operators".into()))?;
107 let result = last_op.dot(state);
108 let norm = result.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
109 Ok(result.mapv(|c| c / norm))
110 }
111
112 fn verify_completeness(&self) -> bool {
114 let kraus_ops = self.kraus_operators();
115 let dim = 2_usize.pow(self.num_qubits() as u32);
116
117 let mut sum = Array2::<Complex64>::zeros((dim, dim));
119 for k in &kraus_ops {
120 for i in 0..dim {
122 for j in 0..dim {
123 let mut val = Complex64::new(0.0, 0.0);
124 for m in 0..dim {
125 val += k[[m, i]].conj() * k[[m, j]];
126 }
127 sum[[i, j]] += val;
128 }
129 }
130 }
131
132 let mut is_identity = true;
134 for i in 0..dim {
135 for j in 0..dim {
136 let expected = if i == j {
137 Complex64::new(1.0, 0.0)
138 } else {
139 Complex64::new(0.0, 0.0)
140 };
141 let diff: Complex64 = sum[[i, j]] - expected;
142 if diff.norm() > 1e-10 {
143 is_identity = false;
144 }
145 }
146 }
147 is_identity
148 }
149}
150
151pub struct DepolarizingNoise {
160 pub probability: f64,
162 num_qubits: usize,
163}
164
165impl DepolarizingNoise {
166 pub fn new(probability: f64) -> Self {
168 assert!(
169 (0.0..=1.0).contains(&probability),
170 "Probability must be between 0 and 1"
171 );
172 Self {
173 probability,
174 num_qubits: 1,
175 }
176 }
177
178 pub fn new_two_qubit(probability: f64) -> Self {
180 assert!(
181 (0.0..=1.0).contains(&probability),
182 "Probability must be between 0 and 1"
183 );
184 Self {
185 probability,
186 num_qubits: 2,
187 }
188 }
189}
190
191impl NoiseChannel for DepolarizingNoise {
192 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
193 if self.num_qubits == 1 {
194 let p = self.probability;
195 let sqrt_1mp = (1.0 - p).sqrt();
196 let sqrt_p3 = (p / 3.0).sqrt();
197
198 vec![
199 Array2::from_diag(&Array1::from_vec(vec![
201 Complex64::new(sqrt_1mp, 0.0),
202 Complex64::new(sqrt_1mp, 0.0),
203 ])),
204 Array2::from_shape_vec(
206 (2, 2),
207 vec![
208 Complex64::new(0.0, 0.0),
209 Complex64::new(sqrt_p3, 0.0),
210 Complex64::new(sqrt_p3, 0.0),
211 Complex64::new(0.0, 0.0),
212 ],
213 )
214 .expect("2x2 matrix with 4 elements"),
215 Array2::from_shape_vec(
217 (2, 2),
218 vec![
219 Complex64::new(0.0, 0.0),
220 Complex64::new(0.0, -sqrt_p3),
221 Complex64::new(0.0, sqrt_p3),
222 Complex64::new(0.0, 0.0),
223 ],
224 )
225 .expect("2x2 matrix with 4 elements"),
226 Array2::from_shape_vec(
228 (2, 2),
229 vec![
230 Complex64::new(sqrt_p3, 0.0),
231 Complex64::new(0.0, 0.0),
232 Complex64::new(0.0, 0.0),
233 Complex64::new(-sqrt_p3, 0.0),
234 ],
235 )
236 .expect("2x2 matrix with 4 elements"),
237 ]
238 } else {
239 let p = self.probability;
241 let sqrt_1mp = (1.0 - p).sqrt();
242 let sqrt_p15 = (p / 15.0).sqrt();
243
244 let mut kraus_ops = Vec::new();
245
246 kraus_ops.push(Array2::from_diag(&Array1::from_vec(vec![
248 Complex64::new(
249 sqrt_1mp, 0.0
250 );
251 4
252 ])));
253
254 for _ in 0..15 {
258 kraus_ops.push(Array2::from_diag(&Array1::from_vec(vec![
259 Complex64::new(
260 sqrt_p15, 0.0
261 );
262 4
263 ])));
264 }
265
266 kraus_ops
267 }
268 }
269
270 fn name(&self) -> &str {
271 if self.num_qubits == 1 {
272 "DepolarizingNoise1Q"
273 } else {
274 "DepolarizingNoise2Q"
275 }
276 }
277
278 fn num_qubits(&self) -> usize {
279 self.num_qubits
280 }
281}
282
283pub struct BitFlipNoise {
288 pub probability: f64,
289}
290
291impl BitFlipNoise {
292 pub fn new(probability: f64) -> Self {
293 assert!(
294 (0.0..=1.0).contains(&probability),
295 "Probability must be between 0 and 1"
296 );
297 Self { probability }
298 }
299}
300
301impl NoiseChannel for BitFlipNoise {
302 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
303 let p = self.probability;
304 vec![
305 Array2::from_diag(&Array1::from_vec(vec![
307 Complex64::new((1.0 - p).sqrt(), 0.0),
308 Complex64::new((1.0 - p).sqrt(), 0.0),
309 ])),
310 Array2::from_shape_vec(
312 (2, 2),
313 vec![
314 Complex64::new(0.0, 0.0),
315 Complex64::new(p.sqrt(), 0.0),
316 Complex64::new(p.sqrt(), 0.0),
317 Complex64::new(0.0, 0.0),
318 ],
319 )
320 .expect("2x2 matrix with 4 elements"),
321 ]
322 }
323
324 fn name(&self) -> &str {
325 "BitFlipNoise"
326 }
327
328 fn num_qubits(&self) -> usize {
329 1
330 }
331}
332
333pub struct PhaseFlipNoise {
338 pub probability: f64,
339}
340
341impl PhaseFlipNoise {
342 pub fn new(probability: f64) -> Self {
343 assert!(
344 (0.0..=1.0).contains(&probability),
345 "Probability must be between 0 and 1"
346 );
347 Self { probability }
348 }
349}
350
351impl NoiseChannel for PhaseFlipNoise {
352 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
353 let p = self.probability;
354 vec![
355 Array2::from_diag(&Array1::from_vec(vec![
357 Complex64::new((1.0 - p).sqrt(), 0.0),
358 Complex64::new((1.0 - p).sqrt(), 0.0),
359 ])),
360 Array2::from_shape_vec(
362 (2, 2),
363 vec![
364 Complex64::new(p.sqrt(), 0.0),
365 Complex64::new(0.0, 0.0),
366 Complex64::new(0.0, 0.0),
367 Complex64::new(-p.sqrt(), 0.0),
368 ],
369 )
370 .expect("2x2 matrix with 4 elements"),
371 ]
372 }
373
374 fn name(&self) -> &str {
375 "PhaseFlipNoise"
376 }
377
378 fn num_qubits(&self) -> usize {
379 1
380 }
381}
382
383pub struct AmplitudeDampingNoise {
388 pub gamma: f64,
390}
391
392impl AmplitudeDampingNoise {
393 pub fn new(gamma: f64) -> Self {
394 assert!(
395 (0.0..=1.0).contains(&gamma),
396 "Gamma must be between 0 and 1"
397 );
398 Self { gamma }
399 }
400}
401
402impl NoiseChannel for AmplitudeDampingNoise {
403 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
404 let g = self.gamma;
405 vec![
406 Array2::from_shape_vec(
408 (2, 2),
409 vec![
410 Complex64::new(1.0, 0.0),
411 Complex64::new(0.0, 0.0),
412 Complex64::new(0.0, 0.0),
413 Complex64::new((1.0 - g).sqrt(), 0.0),
414 ],
415 )
416 .expect("2x2 matrix with 4 elements"),
417 Array2::from_shape_vec(
419 (2, 2),
420 vec![
421 Complex64::new(0.0, 0.0),
422 Complex64::new(g.sqrt(), 0.0),
423 Complex64::new(0.0, 0.0),
424 Complex64::new(0.0, 0.0),
425 ],
426 )
427 .expect("2x2 matrix with 4 elements"),
428 ]
429 }
430
431 fn name(&self) -> &str {
432 "AmplitudeDampingNoise"
433 }
434
435 fn num_qubits(&self) -> usize {
436 1
437 }
438}
439
440pub struct PhaseDampingNoise {
445 pub lambda: f64,
447}
448
449impl PhaseDampingNoise {
450 pub fn new(lambda: f64) -> Self {
451 assert!(
452 (0.0..=1.0).contains(&lambda),
453 "Lambda must be between 0 and 1"
454 );
455 Self { lambda }
456 }
457}
458
459impl NoiseChannel for PhaseDampingNoise {
460 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
461 let l = self.lambda;
462 vec![
463 Array2::from_shape_vec(
465 (2, 2),
466 vec![
467 Complex64::new(1.0, 0.0),
468 Complex64::new(0.0, 0.0),
469 Complex64::new(0.0, 0.0),
470 Complex64::new((1.0 - l).sqrt(), 0.0),
471 ],
472 )
473 .expect("2x2 matrix with 4 elements"),
474 Array2::from_shape_vec(
476 (2, 2),
477 vec![
478 Complex64::new(0.0, 0.0),
479 Complex64::new(0.0, 0.0),
480 Complex64::new(0.0, 0.0),
481 Complex64::new(l.sqrt(), 0.0),
482 ],
483 )
484 .expect("2x2 matrix with 4 elements"),
485 ]
486 }
487
488 fn name(&self) -> &str {
489 "PhaseDampingNoise"
490 }
491
492 fn num_qubits(&self) -> usize {
493 1
494 }
495}
496
497pub struct ThermalRelaxationNoise {
502 pub t1: f64,
504 pub t2: f64,
506 pub gate_time: f64,
508 pub excited_state_pop: f64,
510}
511
512impl ThermalRelaxationNoise {
513 pub fn new(t1: f64, t2: f64, gate_time: f64) -> Self {
514 assert!(t1 > 0.0, "T1 must be positive");
515 assert!(t2 > 0.0, "T2 must be positive");
516 assert!(t2 <= 2.0 * t1, "T2 must satisfy T2 ≤ 2T1");
517 assert!(gate_time >= 0.0, "Gate time must be non-negative");
518
519 Self {
520 t1,
521 t2,
522 gate_time,
523 excited_state_pop: 0.0,
524 }
525 }
526
527 pub fn with_thermal_population(mut self, excited_state_pop: f64) -> Self {
528 assert!(
529 (0.0..=1.0).contains(&excited_state_pop),
530 "Excited state population must be between 0 and 1"
531 );
532 self.excited_state_pop = excited_state_pop;
533 self
534 }
535}
536
537impl NoiseChannel for ThermalRelaxationNoise {
538 fn kraus_operators(&self) -> Vec<Array2<Complex64>> {
539 let t = self.gate_time;
540 let t1 = self.t1;
541 let t2 = self.t2;
542 let p_reset = 1.0 - (-t / t1).exp();
543 let p_z = (1.0 - (-t / t2).exp()) - p_reset / 2.0;
544
545 let p_excited = self.excited_state_pop;
547
548 vec![
549 Array2::from_shape_vec(
551 (2, 2),
552 vec![
553 Complex64::new((1.0 - p_reset - p_z).sqrt(), 0.0),
554 Complex64::new(0.0, 0.0),
555 Complex64::new(0.0, 0.0),
556 Complex64::new((1.0 - p_reset - p_z).sqrt(), 0.0),
557 ],
558 )
559 .expect("2x2 matrix with 4 elements"),
560 Array2::from_shape_vec(
562 (2, 2),
563 vec![
564 Complex64::new(0.0, 0.0),
565 Complex64::new((p_reset * (1.0 - p_excited)).sqrt(), 0.0),
566 Complex64::new(0.0, 0.0),
567 Complex64::new(0.0, 0.0),
568 ],
569 )
570 .expect("2x2 matrix with 4 elements"),
571 Array2::from_shape_vec(
573 (2, 2),
574 vec![
575 Complex64::new(p_z.sqrt(), 0.0),
576 Complex64::new(0.0, 0.0),
577 Complex64::new(0.0, 0.0),
578 Complex64::new(-p_z.sqrt(), 0.0),
579 ],
580 )
581 .expect("2x2 matrix with 4 elements"),
582 ]
583 }
584
585 fn name(&self) -> &str {
586 "ThermalRelaxationNoise"
587 }
588
589 fn num_qubits(&self) -> usize {
590 1
591 }
592}
593
594#[derive(Clone)]
598pub struct NoiseModel {
599 global_channels: Vec<Arc<dyn NoiseChannel>>,
601 gate_channels: HashMap<String, Vec<Arc<dyn NoiseChannel>>>,
603 measurement_noise: Option<Arc<dyn NoiseChannel>>,
605 idle_noise: Option<Arc<dyn NoiseChannel>>,
607}
608
609impl NoiseModel {
610 pub fn new() -> Self {
612 Self {
613 global_channels: Vec::new(),
614 gate_channels: HashMap::new(),
615 measurement_noise: None,
616 idle_noise: None,
617 }
618 }
619
620 pub fn add_channel(&mut self, channel: Arc<dyn NoiseChannel>) {
622 self.global_channels.push(channel);
623 }
624
625 pub fn add_gate_noise(&mut self, gate_name: &str, channel: Arc<dyn NoiseChannel>) {
627 self.gate_channels
628 .entry(gate_name.to_string())
629 .or_default()
630 .push(channel);
631 }
632
633 pub fn set_measurement_noise(&mut self, channel: Arc<dyn NoiseChannel>) {
635 self.measurement_noise = Some(channel);
636 }
637
638 pub fn set_idle_noise(&mut self, channel: Arc<dyn NoiseChannel>) {
640 self.idle_noise = Some(channel);
641 }
642
643 pub fn apply_single_qubit(
645 &self,
646 state: &Array1<Complex64>,
647 _qubit: usize,
648 ) -> NoiseResult<Array1<Complex64>> {
649 let mut noisy_state = state.clone();
650
651 for channel in &self.global_channels {
653 if channel.num_qubits() == 1 {
654 noisy_state = channel.apply(&noisy_state.view())?;
655 }
656 }
657
658 Ok(noisy_state)
659 }
660
661 pub fn apply_gate_noise(
663 &self,
664 state: &Array1<Complex64>,
665 gate_name: &str,
666 _qubit: usize,
667 ) -> NoiseResult<Array1<Complex64>> {
668 let mut noisy_state = state.clone();
669
670 if let Some(channels) = self.gate_channels.get(gate_name) {
671 for channel in channels {
672 noisy_state = channel.apply(&noisy_state.view())?;
673 }
674 }
675
676 Ok(noisy_state)
677 }
678
679 pub fn num_global_channels(&self) -> usize {
681 self.global_channels.len()
682 }
683
684 pub fn has_measurement_noise(&self) -> bool {
686 self.measurement_noise.is_some()
687 }
688}
689
690impl Default for NoiseModel {
691 fn default() -> Self {
692 Self::new()
693 }
694}
695
696#[cfg(test)]
697mod tests {
698 use super::*;
699
700 #[test]
701 fn test_depolarizing_noise_kraus() {
702 let noise = DepolarizingNoise::new(0.1);
703 let kraus = noise.kraus_operators();
704
705 assert_eq!(kraus.len(), 4);
707
708 assert!(noise.verify_completeness());
710 }
711
712 #[test]
713 fn test_bit_flip_noise() {
714 let noise = BitFlipNoise::new(0.2);
715 let kraus = noise.kraus_operators();
716
717 assert_eq!(kraus.len(), 2);
718 assert!(noise.verify_completeness());
719 }
720
721 #[test]
722 fn test_phase_flip_noise() {
723 let noise = PhaseFlipNoise::new(0.15);
724 let kraus = noise.kraus_operators();
725
726 assert_eq!(kraus.len(), 2);
727 assert!(noise.verify_completeness());
728 }
729
730 #[test]
731 fn test_amplitude_damping() {
732 let noise = AmplitudeDampingNoise::new(0.05);
733 let kraus = noise.kraus_operators();
734
735 assert_eq!(kraus.len(), 2);
736 assert!(noise.verify_completeness());
737 }
738
739 #[test]
740 fn test_phase_damping() {
741 let noise = PhaseDampingNoise::new(0.1);
742 let kraus = noise.kraus_operators();
743
744 assert_eq!(kraus.len(), 2);
745 assert!(noise.verify_completeness());
746 }
747
748 #[test]
749 fn test_thermal_relaxation() {
750 let noise = ThermalRelaxationNoise::new(50.0, 40.0, 1.0);
751 let kraus = noise.kraus_operators();
752
753 assert_eq!(kraus.len(), 3);
754 }
757
758 #[test]
759 fn test_noise_application() {
760 let noise = DepolarizingNoise::new(0.01);
761
762 let state = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
764
765 for _ in 0..10 {
767 let noisy_state = noise.apply(&state.view()).unwrap();
768 let norm: f64 = noisy_state.iter().map(|c| c.norm_sqr()).sum();
769 assert!((norm - 1.0).abs() < 1e-10, "State not normalized: {}", norm);
770 }
771 }
772
773 #[test]
774 fn test_noise_model() {
775 let mut model = NoiseModel::new();
776
777 model.add_channel(Arc::new(DepolarizingNoise::new(0.01)));
779
780 model.add_gate_noise("X", Arc::new(BitFlipNoise::new(0.02)));
782
783 assert_eq!(model.num_global_channels(), 1);
784 assert!(!model.has_measurement_noise());
785
786 let state = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 0.0)]);
788 let noisy = model.apply_single_qubit(&state, 0).unwrap();
789
790 let norm: f64 = noisy.iter().map(|c| c.norm_sqr()).sum();
791 assert!((norm - 1.0).abs() < 1e-10);
792 }
793
794 #[test]
795 fn test_noise_model_composition() {
796 let mut model = NoiseModel::new();
797
798 model.add_channel(Arc::new(DepolarizingNoise::new(0.005)));
800 model.add_channel(Arc::new(AmplitudeDampingNoise::new(0.01)));
801 model.add_channel(Arc::new(PhaseDampingNoise::new(0.008)));
802
803 assert_eq!(model.num_global_channels(), 3);
804
805 let state = Array1::from_vec(vec![
807 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
808 Complex64::new(1.0 / 2.0_f64.sqrt(), 0.0),
809 ]);
810 let noisy = model.apply_single_qubit(&state, 0).unwrap();
811
812 let norm: f64 = noisy.iter().map(|c| c.norm_sqr()).sum();
813 assert!((norm - 1.0).abs() < 1e-10);
814 }
815}