quantrs2_sim/
noise.rs

1//! Noise models for quantum simulation
2//!
3//! This module provides a framework for adding noise to quantum simulations,
4//! including common noise models such as depolarizing, amplitude damping,
5//! phase damping, and bit flip/phase flip channels.
6
7#![allow(clippy::needless_range_loop)]
8
9use scirs2_core::Complex64;
10use std::fmt::Debug;
11
12use quantrs2_core::error::QuantRS2Result;
13use quantrs2_core::qubit::QubitId;
14
15/// An enum that represents all possible noise channel types
16#[derive(Debug, Clone)]
17pub enum NoiseChannelType {
18    BitFlip(BitFlipChannel),
19    PhaseFlip(PhaseFlipChannel),
20    Depolarizing(DepolarizingChannel),
21    AmplitudeDamping(AmplitudeDampingChannel),
22    PhaseDamping(PhaseDampingChannel),
23}
24
25impl NoiseChannelType {
26    /// Get the name of the noise channel
27    pub fn name(&self) -> &'static str {
28        match self {
29            Self::BitFlip(ch) => ch.name(),
30            Self::PhaseFlip(ch) => ch.name(),
31            Self::Depolarizing(ch) => ch.name(),
32            Self::AmplitudeDamping(ch) => ch.name(),
33            Self::PhaseDamping(ch) => ch.name(),
34        }
35    }
36
37    /// Normalize a state vector to ensure it has unit norm
38    pub fn normalize_state(state: &mut [Complex64]) {
39        // Calculate current norm
40        let mut norm_squared = 0.0;
41        for amp in state.iter() {
42            norm_squared += amp.norm_sqr();
43        }
44
45        // Apply normalization if needed
46        if (norm_squared - 1.0).abs() > 1e-10 {
47            let norm = norm_squared.sqrt();
48            for amp in state.iter_mut() {
49                *amp /= Complex64::new(norm, 0.0);
50            }
51        }
52    }
53
54    /// Get the qubits this channel affects
55    pub fn qubits(&self) -> Vec<QubitId> {
56        match self {
57            Self::BitFlip(ch) => ch.qubits(),
58            Self::PhaseFlip(ch) => ch.qubits(),
59            Self::Depolarizing(ch) => ch.qubits(),
60            Self::AmplitudeDamping(ch) => ch.qubits(),
61            Self::PhaseDamping(ch) => ch.qubits(),
62        }
63    }
64
65    /// Apply the noise channel to a state vector
66    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
67        match self {
68            Self::BitFlip(ch) => ch.apply_to_statevector(state),
69            Self::PhaseFlip(ch) => ch.apply_to_statevector(state),
70            Self::Depolarizing(ch) => ch.apply_to_statevector(state),
71            Self::AmplitudeDamping(ch) => ch.apply_to_statevector(state),
72            Self::PhaseDamping(ch) => ch.apply_to_statevector(state),
73        }
74    }
75
76    /// Get the probability of the noise occurring
77    pub fn probability(&self) -> f64 {
78        match self {
79            Self::BitFlip(ch) => ch.probability(),
80            Self::PhaseFlip(ch) => ch.probability(),
81            Self::Depolarizing(ch) => ch.probability(),
82            Self::AmplitudeDamping(ch) => ch.probability(),
83            Self::PhaseDamping(ch) => ch.probability(),
84        }
85    }
86}
87
88/// Trait for quantum noise channels
89pub trait NoiseChannel: Debug + Clone {
90    /// Return the name of the channel
91    fn name(&self) -> &'static str;
92
93    /// Return the qubits this channel affects
94    fn qubits(&self) -> Vec<QubitId>;
95
96    /// Apply the noise channel to a state vector
97    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()>;
98
99    /// Return the Kraus operators for this channel
100    fn kraus_operators(&self) -> Vec<Vec<Complex64>>;
101
102    /// Probability of the noise occurring
103    fn probability(&self) -> f64;
104}
105
106/// Bitflip noise channel (X errors)
107#[derive(Debug, Clone)]
108pub struct BitFlipChannel {
109    /// Target qubit
110    pub target: QubitId,
111
112    /// Probability of bit flip
113    pub probability: f64,
114}
115
116impl NoiseChannel for BitFlipChannel {
117    fn name(&self) -> &'static str {
118        "BitFlip"
119    }
120
121    fn qubits(&self) -> Vec<QubitId> {
122        vec![self.target]
123    }
124
125    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
126        let target_idx = self.target.id() as usize;
127        let dim = state.len();
128
129        // Apply bit flip with probability p
130        if fastrand::f64() < self.probability {
131            // Create a copy of the state to read from
132            let state_copy = state.to_vec();
133
134            // Apply bit flip to each amplitude
135            for i in 0..dim {
136                let flipped_i = i ^ (1 << target_idx);
137                state[i] = state_copy[flipped_i];
138            }
139        }
140
141        Ok(())
142    }
143
144    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
145        // Kraus operators for bit flip:
146        // K0 = sqrt(1-p) * I, K1 = sqrt(p) * X
147        let p = self.probability;
148        let sqrt_1_minus_p = (1.0 - p).sqrt();
149        let sqrt_p = p.sqrt();
150
151        // I operator
152        let k0 = vec![
153            Complex64::new(sqrt_1_minus_p, 0.0),
154            Complex64::new(0.0, 0.0),
155            Complex64::new(0.0, 0.0),
156            Complex64::new(sqrt_1_minus_p, 0.0),
157        ];
158
159        // X operator
160        let k1 = vec![
161            Complex64::new(0.0, 0.0),
162            Complex64::new(sqrt_p, 0.0),
163            Complex64::new(sqrt_p, 0.0),
164            Complex64::new(0.0, 0.0),
165        ];
166
167        vec![k0, k1]
168    }
169
170    fn probability(&self) -> f64 {
171        self.probability
172    }
173}
174
175/// Phase flip noise channel (Z errors)
176#[derive(Debug, Clone)]
177pub struct PhaseFlipChannel {
178    /// Target qubit
179    pub target: QubitId,
180
181    /// Probability of phase flip
182    pub probability: f64,
183}
184
185impl NoiseChannel for PhaseFlipChannel {
186    fn name(&self) -> &'static str {
187        "PhaseFlip"
188    }
189
190    fn qubits(&self) -> Vec<QubitId> {
191        vec![self.target]
192    }
193
194    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
195        let target_idx = self.target.id() as usize;
196        let dim = state.len();
197
198        // Apply phase flip with probability p
199        if fastrand::f64() < self.probability {
200            // Apply phase flip to each amplitude
201            for i in 0..dim {
202                if (i >> target_idx) & 1 == 1 {
203                    // Apply phase flip to |1⟩ component
204                    state[i] = -state[i];
205                }
206            }
207        }
208
209        Ok(())
210    }
211
212    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
213        // Kraus operators for phase flip:
214        // K0 = sqrt(1-p) * I, K1 = sqrt(p) * Z
215        let p = self.probability;
216        let sqrt_1_minus_p = (1.0 - p).sqrt();
217        let sqrt_p = p.sqrt();
218
219        // I operator
220        let k0 = vec![
221            Complex64::new(sqrt_1_minus_p, 0.0),
222            Complex64::new(0.0, 0.0),
223            Complex64::new(0.0, 0.0),
224            Complex64::new(sqrt_1_minus_p, 0.0),
225        ];
226
227        // Z operator
228        let k1 = vec![
229            Complex64::new(sqrt_p, 0.0),
230            Complex64::new(0.0, 0.0),
231            Complex64::new(0.0, 0.0),
232            Complex64::new(-sqrt_p, 0.0),
233        ];
234
235        vec![k0, k1]
236    }
237
238    fn probability(&self) -> f64 {
239        self.probability
240    }
241}
242
243/// Depolarizing noise channel (equal probability of X, Y, or Z errors)
244#[derive(Debug, Clone)]
245pub struct DepolarizingChannel {
246    /// Target qubit
247    pub target: QubitId,
248
249    /// Probability of depolarizing
250    pub probability: f64,
251}
252
253impl NoiseChannel for DepolarizingChannel {
254    fn name(&self) -> &'static str {
255        "Depolarizing"
256    }
257
258    fn qubits(&self) -> Vec<QubitId> {
259        vec![self.target]
260    }
261
262    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
263        let target_idx = self.target.id() as usize;
264        let dim = state.len();
265
266        // Apply depolarizing noise with probability p
267        if fastrand::f64() < self.probability {
268            // Choose randomly between X, Y, and Z errors
269            let error_type = fastrand::u32(..) % 3;
270
271            // Create a copy of the state to read from
272            let state_copy = state.to_vec();
273
274            match error_type {
275                0 => {
276                    // X error (bit flip)
277                    for i in 0..dim {
278                        let flipped_i = i ^ (1 << target_idx);
279                        state[i] = state_copy[flipped_i];
280                    }
281                }
282                1 => {
283                    // Y error (bit and phase flip)
284                    for i in 0..dim {
285                        let flipped_i = i ^ (1 << target_idx);
286                        let phase = if (i >> target_idx) & 1 == 1 {
287                            -1.0
288                        } else {
289                            1.0
290                        };
291                        state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
292                    }
293                }
294                2 => {
295                    // Z error (phase flip)
296                    for i in 0..dim {
297                        if (i >> target_idx) & 1 == 1 {
298                            state[i] = -state_copy[i];
299                        } else {
300                            state[i] = state_copy[i];
301                        }
302                    }
303                }
304                _ => unreachable!(),
305            }
306        }
307
308        Ok(())
309    }
310
311    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
312        // Kraus operators for depolarizing:
313        // K0 = sqrt(1-3p/4) * I
314        // K1 = sqrt(p/4) * X
315        // K2 = sqrt(p/4) * Y
316        // K3 = sqrt(p/4) * Z
317        let p = self.probability;
318        let sqrt_1_minus_3p_4 = (1.0 - 3.0 * p / 4.0).sqrt();
319        let sqrt_p_4 = (p / 4.0).sqrt();
320
321        // I operator
322        let k0 = vec![
323            Complex64::new(sqrt_1_minus_3p_4, 0.0),
324            Complex64::new(0.0, 0.0),
325            Complex64::new(0.0, 0.0),
326            Complex64::new(sqrt_1_minus_3p_4, 0.0),
327        ];
328
329        // X operator
330        let k1 = vec![
331            Complex64::new(0.0, 0.0),
332            Complex64::new(sqrt_p_4, 0.0),
333            Complex64::new(sqrt_p_4, 0.0),
334            Complex64::new(0.0, 0.0),
335        ];
336
337        // Y operator
338        let k2 = vec![
339            Complex64::new(0.0, 0.0),
340            Complex64::new(0.0, -sqrt_p_4),
341            Complex64::new(0.0, sqrt_p_4),
342            Complex64::new(0.0, 0.0),
343        ];
344
345        // Z operator
346        let k3 = vec![
347            Complex64::new(sqrt_p_4, 0.0),
348            Complex64::new(0.0, 0.0),
349            Complex64::new(0.0, 0.0),
350            Complex64::new(-sqrt_p_4, 0.0),
351        ];
352
353        vec![k0, k1, k2, k3]
354    }
355
356    fn probability(&self) -> f64 {
357        self.probability
358    }
359}
360
361/// Amplitude damping noise channel (energy dissipation, T1 decay)
362#[derive(Debug, Clone)]
363pub struct AmplitudeDampingChannel {
364    /// Target qubit
365    pub target: QubitId,
366
367    /// Damping probability
368    pub gamma: f64,
369}
370
371impl NoiseChannel for AmplitudeDampingChannel {
372    fn name(&self) -> &'static str {
373        "AmplitudeDamping"
374    }
375
376    fn qubits(&self) -> Vec<QubitId> {
377        vec![self.target]
378    }
379
380    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
381        let target_idx = self.target.id() as usize;
382        let dim = state.len();
383
384        // Create a copy of the state to read from
385        let state_copy = state.to_vec();
386
387        // Apply amplitude damping to each basis state
388        for i in 0..dim {
389            if (i >> target_idx) & 1 == 1 {
390                // This basis state has the target qubit in |1⟩
391                let base_idx = i & !(1 << target_idx); // Flip the target bit to 0
392
393                // Damping from |1⟩ to |0⟩ with probability gamma
394                if fastrand::f64() < self.gamma {
395                    // Collapse to |0⟩ state
396                    state[base_idx] += state_copy[i];
397                    state[i] = Complex64::new(0.0, 0.0);
398                } else {
399                    // Renormalize the |1⟩ state
400                    state[i] = state_copy[i] * Complex64::new((1.0 - self.gamma).sqrt(), 0.0);
401                }
402            }
403        }
404
405        Ok(())
406    }
407
408    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
409        // Kraus operators for amplitude damping:
410        // K0 = [[1, 0], [0, sqrt(1-gamma)]]
411        // K1 = [[0, sqrt(gamma)], [0, 0]]
412        let gamma = self.gamma;
413        let sqrt_1_minus_gamma = (1.0 - gamma).sqrt();
414        let sqrt_gamma = gamma.sqrt();
415
416        // K0 operator
417        let k0 = vec![
418            Complex64::new(1.0, 0.0),
419            Complex64::new(0.0, 0.0),
420            Complex64::new(0.0, 0.0),
421            Complex64::new(sqrt_1_minus_gamma, 0.0),
422        ];
423
424        // K1 operator
425        let k1 = vec![
426            Complex64::new(0.0, 0.0),
427            Complex64::new(sqrt_gamma, 0.0),
428            Complex64::new(0.0, 0.0),
429            Complex64::new(0.0, 0.0),
430        ];
431
432        vec![k0, k1]
433    }
434
435    fn probability(&self) -> f64 {
436        self.gamma
437    }
438}
439
440/// Phase damping noise channel (pure dephasing, T2 decay)
441#[derive(Debug, Clone)]
442pub struct PhaseDampingChannel {
443    /// Target qubit
444    pub target: QubitId,
445
446    /// Damping probability
447    pub lambda: f64,
448}
449
450impl NoiseChannel for PhaseDampingChannel {
451    fn name(&self) -> &'static str {
452        "PhaseDamping"
453    }
454
455    fn qubits(&self) -> Vec<QubitId> {
456        vec![self.target]
457    }
458
459    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
460        let target_idx = self.target.id() as usize;
461        let dim = state.len();
462
463        // Apply phase damping to each basis state
464        for i in 0..dim {
465            if (i >> target_idx) & 1 == 1 {
466                // This basis state has the target qubit in |1⟩
467                // Apply phase damping
468                if fastrand::f64() < self.lambda {
469                    // Random phase
470                    let phase = 2.0 * std::f64::consts::PI * fastrand::f64();
471                    state[i] *= Complex64::new(phase.cos(), phase.sin());
472                }
473            }
474        }
475
476        Ok(())
477    }
478
479    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
480        // Kraus operators for phase damping:
481        // K0 = [[1, 0], [0, sqrt(1-lambda)]]
482        // K1 = [[0, 0], [0, sqrt(lambda)]]
483        let lambda = self.lambda;
484        let sqrt_1_minus_lambda = (1.0 - lambda).sqrt();
485        let sqrt_lambda = lambda.sqrt();
486
487        // K0 operator
488        let k0 = vec![
489            Complex64::new(1.0, 0.0),
490            Complex64::new(0.0, 0.0),
491            Complex64::new(0.0, 0.0),
492            Complex64::new(sqrt_1_minus_lambda, 0.0),
493        ];
494
495        // K1 operator
496        let k1 = vec![
497            Complex64::new(0.0, 0.0),
498            Complex64::new(0.0, 0.0),
499            Complex64::new(0.0, 0.0),
500            Complex64::new(sqrt_lambda, 0.0),
501        ];
502
503        vec![k0, k1]
504    }
505
506    fn probability(&self) -> f64 {
507        self.lambda
508    }
509}
510
511/// Noise model that combines multiple noise channels
512#[derive(Debug, Clone)]
513pub struct NoiseModel {
514    /// List of noise channels
515    pub channels: Vec<NoiseChannelType>,
516
517    /// Whether the noise is applied after each gate
518    pub per_gate: bool,
519}
520
521impl NoiseModel {
522    /// Create a new empty noise model
523    pub fn new(per_gate: bool) -> Self {
524        Self {
525            channels: Vec::new(),
526            per_gate,
527        }
528    }
529
530    /// Add a bit flip noise channel to the model
531    pub fn add_bit_flip(&mut self, channel: BitFlipChannel) -> &mut Self {
532        self.channels.push(NoiseChannelType::BitFlip(channel));
533        self
534    }
535
536    /// Add a phase flip noise channel to the model
537    pub fn add_phase_flip(&mut self, channel: PhaseFlipChannel) -> &mut Self {
538        self.channels.push(NoiseChannelType::PhaseFlip(channel));
539        self
540    }
541
542    /// Add a depolarizing noise channel to the model
543    pub fn add_depolarizing(&mut self, channel: DepolarizingChannel) -> &mut Self {
544        self.channels.push(NoiseChannelType::Depolarizing(channel));
545        self
546    }
547
548    /// Add an amplitude damping noise channel to the model
549    pub fn add_amplitude_damping(&mut self, channel: AmplitudeDampingChannel) -> &mut Self {
550        self.channels
551            .push(NoiseChannelType::AmplitudeDamping(channel));
552        self
553    }
554
555    /// Add a phase damping noise channel to the model
556    pub fn add_phase_damping(&mut self, channel: PhaseDampingChannel) -> &mut Self {
557        self.channels.push(NoiseChannelType::PhaseDamping(channel));
558        self
559    }
560
561    /// Apply all noise channels to a state vector
562    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
563        for channel in &self.channels {
564            channel.apply_to_statevector(state)?;
565        }
566
567        // Normalize the state vector after applying all noise channels
568        NoiseChannelType::normalize_state(state);
569
570        Ok(())
571    }
572
573    /// Get the total number of channels
574    pub fn num_channels(&self) -> usize {
575        self.channels.len()
576    }
577}
578
579impl Default for NoiseModel {
580    fn default() -> Self {
581        Self::new(true)
582    }
583}
584
585/// Builder for common noise models
586pub struct NoiseModelBuilder {
587    model: NoiseModel,
588}
589
590impl NoiseModelBuilder {
591    /// Create a new noise model builder
592    pub fn new(per_gate: bool) -> Self {
593        Self {
594            model: NoiseModel::new(per_gate),
595        }
596    }
597
598    /// Add depolarizing noise to all qubits
599    pub fn with_depolarizing_noise(mut self, qubits: &[QubitId], probability: f64) -> Self {
600        for &qubit in qubits {
601            self.model.add_depolarizing(DepolarizingChannel {
602                target: qubit,
603                probability,
604            });
605        }
606        self
607    }
608
609    /// Add bit flip noise to all qubits
610    pub fn with_bit_flip_noise(mut self, qubits: &[QubitId], probability: f64) -> Self {
611        for &qubit in qubits {
612            self.model.add_bit_flip(BitFlipChannel {
613                target: qubit,
614                probability,
615            });
616        }
617        self
618    }
619
620    /// Add phase flip noise to all qubits
621    pub fn with_phase_flip_noise(mut self, qubits: &[QubitId], probability: f64) -> Self {
622        for &qubit in qubits {
623            self.model.add_phase_flip(PhaseFlipChannel {
624                target: qubit,
625                probability,
626            });
627        }
628        self
629    }
630
631    /// Add amplitude damping to all qubits
632    pub fn with_amplitude_damping(mut self, qubits: &[QubitId], gamma: f64) -> Self {
633        for &qubit in qubits {
634            self.model.add_amplitude_damping(AmplitudeDampingChannel {
635                target: qubit,
636                gamma,
637            });
638        }
639        self
640    }
641
642    /// Add phase damping to all qubits
643    pub fn with_phase_damping(mut self, qubits: &[QubitId], lambda: f64) -> Self {
644        for &qubit in qubits {
645            self.model.add_phase_damping(PhaseDampingChannel {
646                target: qubit,
647                lambda,
648            });
649        }
650        self
651    }
652
653    /// Build the noise model
654    pub fn build(self) -> NoiseModel {
655        self.model
656    }
657}