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#[derive(Debug, Clone)]
516pub struct NoiseModel {
517    /// List of noise channels
518    pub channels: Vec<NoiseChannelType>,
519
520    /// Whether the noise is applied after each gate
521    pub per_gate: bool,
522}
523
524impl NoiseModel {
525    /// Create a new empty noise model
526    #[must_use]
527    pub const fn new(per_gate: bool) -> Self {
528        Self {
529            channels: Vec::new(),
530            per_gate,
531        }
532    }
533
534    /// Add a bit flip noise channel to the model
535    pub fn add_bit_flip(&mut self, channel: BitFlipChannel) -> &mut Self {
536        self.channels.push(NoiseChannelType::BitFlip(channel));
537        self
538    }
539
540    /// Add a phase flip noise channel to the model
541    pub fn add_phase_flip(&mut self, channel: PhaseFlipChannel) -> &mut Self {
542        self.channels.push(NoiseChannelType::PhaseFlip(channel));
543        self
544    }
545
546    /// Add a depolarizing noise channel to the model
547    pub fn add_depolarizing(&mut self, channel: DepolarizingChannel) -> &mut Self {
548        self.channels.push(NoiseChannelType::Depolarizing(channel));
549        self
550    }
551
552    /// Add an amplitude damping noise channel to the model
553    pub fn add_amplitude_damping(&mut self, channel: AmplitudeDampingChannel) -> &mut Self {
554        self.channels
555            .push(NoiseChannelType::AmplitudeDamping(channel));
556        self
557    }
558
559    /// Add a phase damping noise channel to the model
560    pub fn add_phase_damping(&mut self, channel: PhaseDampingChannel) -> &mut Self {
561        self.channels.push(NoiseChannelType::PhaseDamping(channel));
562        self
563    }
564
565    /// Apply all noise channels to a state vector
566    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
567        for channel in &self.channels {
568            channel.apply_to_statevector(state)?;
569        }
570
571        // Normalize the state vector after applying all noise channels
572        NoiseChannelType::normalize_state(state);
573
574        Ok(())
575    }
576
577    /// Get the total number of channels
578    #[must_use]
579    pub fn num_channels(&self) -> usize {
580        self.channels.len()
581    }
582}
583
584impl Default for NoiseModel {
585    fn default() -> Self {
586        Self::new(true)
587    }
588}
589
590/// Builder for common noise models
591pub struct NoiseModelBuilder {
592    model: NoiseModel,
593}
594
595impl NoiseModelBuilder {
596    /// Create a new noise model builder
597    #[must_use]
598    pub const fn new(per_gate: bool) -> Self {
599        Self {
600            model: NoiseModel::new(per_gate),
601        }
602    }
603
604    /// Add depolarizing noise to all qubits
605    #[must_use]
606    pub fn with_depolarizing_noise(mut self, qubits: &[QubitId], probability: f64) -> Self {
607        for &qubit in qubits {
608            self.model.add_depolarizing(DepolarizingChannel {
609                target: qubit,
610                probability,
611            });
612        }
613        self
614    }
615
616    /// Add bit flip noise to all qubits
617    #[must_use]
618    pub fn with_bit_flip_noise(mut self, qubits: &[QubitId], probability: f64) -> Self {
619        for &qubit in qubits {
620            self.model.add_bit_flip(BitFlipChannel {
621                target: qubit,
622                probability,
623            });
624        }
625        self
626    }
627
628    /// Add phase flip noise to all qubits
629    #[must_use]
630    pub fn with_phase_flip_noise(mut self, qubits: &[QubitId], probability: f64) -> Self {
631        for &qubit in qubits {
632            self.model.add_phase_flip(PhaseFlipChannel {
633                target: qubit,
634                probability,
635            });
636        }
637        self
638    }
639
640    /// Add amplitude damping to all qubits
641    #[must_use]
642    pub fn with_amplitude_damping(mut self, qubits: &[QubitId], gamma: f64) -> Self {
643        for &qubit in qubits {
644            self.model.add_amplitude_damping(AmplitudeDampingChannel {
645                target: qubit,
646                gamma,
647            });
648        }
649        self
650    }
651
652    /// Add phase damping to all qubits
653    #[must_use]
654    pub fn with_phase_damping(mut self, qubits: &[QubitId], lambda: f64) -> Self {
655        for &qubit in qubits {
656            self.model.add_phase_damping(PhaseDampingChannel {
657                target: qubit,
658                lambda,
659            });
660        }
661        self
662    }
663
664    /// Build the noise model
665    #[must_use]
666    pub fn build(self) -> NoiseModel {
667        self.model
668    }
669}