Skip to main content

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