Skip to main content

quantrs2_sim/
noise_advanced.rs

1//! Advanced noise models beyond simple single-qubit depolarising channels.
2//!
3//! Provides two-qubit correlated noise, crosstalk, leakage to higher energy
4//! levels, and time-dependent amplitude / phase damping noise channels for
5//! realistic device simulation.
6
7#![allow(clippy::needless_range_loop)]
8
9use scirs2_core::Complex64;
10use std::f64::consts::PI;
11use std::time::Duration;
12
13use quantrs2_core::error::QuantRS2Result;
14use quantrs2_core::qubit::QubitId;
15
16use crate::noise::{NoiseChannel, NoiseChannelType, NoiseModel};
17
18/// Two-qubit depolarizing noise channel
19#[derive(Debug, Clone)]
20pub struct TwoQubitDepolarizingChannel {
21    /// First qubit
22    pub qubit1: QubitId,
23
24    /// Second qubit
25    pub qubit2: QubitId,
26
27    /// Probability of error
28    pub probability: f64,
29}
30
31impl NoiseChannel for TwoQubitDepolarizingChannel {
32    fn name(&self) -> &'static str {
33        "TwoQubitDepolarizing"
34    }
35
36    fn qubits(&self) -> Vec<QubitId> {
37        vec![self.qubit1, self.qubit2]
38    }
39
40    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
41        let q1_idx = self.qubit1.id() as usize;
42        #[allow(clippy::needless_range_loop)]
43        let q2_idx = self.qubit2.id() as usize;
44        let dim = state.len();
45
46        // Apply two-qubit depolarizing noise with probability p
47        if fastrand::f64() < self.probability {
48            // Choose randomly between 15 possible Pauli errors (excluding I⊗I)
49            let error_type = fastrand::u32(..) % 15;
50
51            // Create a copy of the state to read from
52            let state_copy = state.to_vec();
53
54            match error_type {
55                0 => {
56                    // X⊗I
57                    for i in 0..dim {
58                        let flipped_i = i ^ (1 << q1_idx);
59                        state[i] = state_copy[flipped_i];
60                    }
61                }
62                1 => {
63                    // I⊗X
64                    for i in 0..dim {
65                        let flipped_i = i ^ (1 << q2_idx);
66                        state[i] = state_copy[flipped_i];
67                    }
68                }
69                2 => {
70                    // X⊗X
71                    for i in 0..dim {
72                        let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
73                        state[i] = state_copy[flipped_i];
74                    }
75                }
76                3 => {
77                    // Y⊗I
78                    for i in 0..dim {
79                        let flipped_i = i ^ (1 << q1_idx);
80                        let phase = if (i >> q1_idx) & 1 == 1 { 1.0 } else { -1.0 };
81                        state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
82                    }
83                }
84                4 => {
85                    // I⊗Y
86                    for i in 0..dim {
87                        let flipped_i = i ^ (1 << q2_idx);
88                        let phase = if (i >> q2_idx) & 1 == 1 { 1.0 } else { -1.0 };
89                        state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
90                    }
91                }
92                5 => {
93                    // Y⊗Y
94                    for i in 0..dim {
95                        let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
96                        let phase1 = if (i >> q1_idx) & 1 == 1 { 1.0 } else { -1.0 };
97                        let phase2 = if (i >> q2_idx) & 1 == 1 { 1.0 } else { -1.0 };
98                        state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase1 * phase2);
99                    }
100                }
101                6 => {
102                    // Z⊗I
103                    for i in 0..dim {
104                        if (i >> q1_idx) & 1 == 1 {
105                            state[i] = -state_copy[i];
106                        }
107                    }
108                }
109                7 => {
110                    // I⊗Z
111                    for i in 0..dim {
112                        if (i >> q2_idx) & 1 == 1 {
113                            state[i] = -state_copy[i];
114                        }
115                    }
116                }
117                8 => {
118                    // Z⊗Z
119                    for i in 0..dim {
120                        let parity = ((i >> q1_idx) & 1) ^ ((i >> q2_idx) & 1);
121                        if parity == 1 {
122                            state[i] = -state_copy[i];
123                        }
124                    }
125                }
126                9 => {
127                    // X⊗Y
128                    for i in 0..dim {
129                        let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
130                        let phase = if (i >> q2_idx) & 1 == 1 { 1.0 } else { -1.0 };
131                        state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
132                    }
133                }
134                10 => {
135                    // X⊗Z
136                    for i in 0..dim {
137                        let flipped_i = i ^ (1 << q1_idx);
138                        if (flipped_i >> q2_idx) & 1 == 1 {
139                            state[i] = -state_copy[flipped_i];
140                        } else {
141                            state[i] = state_copy[flipped_i];
142                        }
143                    }
144                }
145                11 => {
146                    // Y⊗X
147                    for i in 0..dim {
148                        let flipped_i = i ^ (1 << q1_idx) ^ (1 << q2_idx);
149                        let phase = if (i >> q1_idx) & 1 == 1 { 1.0 } else { -1.0 };
150                        state[i] = state_copy[flipped_i] * Complex64::new(0.0, phase);
151                    }
152                }
153                12 => {
154                    // Y⊗Z
155                    for i in 0..dim {
156                        let flipped_i = i ^ (1 << q1_idx);
157                        let phase = if ((i >> q1_idx) & 1 == 1) ^ ((i >> q2_idx) & 1 == 1) {
158                            Complex64::new(0.0, -1.0)
159                        } else {
160                            Complex64::new(0.0, 1.0)
161                        };
162                        state[i] = state_copy[flipped_i] * phase;
163                    }
164                }
165                13 => {
166                    // Z⊗X
167                    for i in 0..dim {
168                        let flipped_i = i ^ (1 << q2_idx);
169                        if (i >> q1_idx) & 1 == 1 {
170                            state[i] = -state_copy[flipped_i];
171                        } else {
172                            state[i] = state_copy[flipped_i];
173                        }
174                    }
175                }
176                14 => {
177                    // Z⊗Y
178                    for i in 0..dim {
179                        let flipped_i = i ^ (1 << q2_idx);
180                        let phase = if ((i >> q1_idx) & 1 == 1) ^ ((i >> q2_idx) & 1 == 1) {
181                            Complex64::new(0.0, -1.0)
182                        } else {
183                            Complex64::new(0.0, 1.0)
184                        };
185                        state[i] = state_copy[flipped_i] * phase;
186                    }
187                }
188                _ => unreachable!(),
189            }
190        }
191
192        Ok(())
193    }
194
195    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
196        // Two-qubit depolarizing has 16 Kraus operators (15 Pauli errors + identity)
197        // This is a simplified implementation since full representation is large
198        let p = self.probability;
199        let sqrt_1_minus_p = (1.0 - p).sqrt();
200        let sqrt_p_15 = (p / 15.0).sqrt();
201
202        // Return placeholder Kraus operators
203        // In a full implementation, this would be a 16×16 matrix
204        vec![
205            vec![Complex64::new(sqrt_1_minus_p, 0.0)],
206            vec![Complex64::new(sqrt_p_15, 0.0)],
207        ]
208    }
209
210    fn probability(&self) -> f64 {
211        self.probability
212    }
213}
214
215/// Thermal relaxation noise channel (combination of T1 and T2 effects)
216#[derive(Debug, Clone)]
217pub struct ThermalRelaxationChannel {
218    /// Target qubit
219    pub target: QubitId,
220
221    /// T1 relaxation time (seconds)
222    pub t1: f64,
223
224    /// T2 pure dephasing time (seconds)
225    pub t2: f64,
226
227    /// Gate time (seconds)
228    pub gate_time: f64,
229
230    /// Excited state population at thermal equilibrium (0.0 to 1.0)
231    pub excited_state_population: f64,
232}
233
234impl NoiseChannel for ThermalRelaxationChannel {
235    fn name(&self) -> &'static str {
236        "ThermalRelaxation"
237    }
238
239    fn qubits(&self) -> Vec<QubitId> {
240        vec![self.target]
241    }
242
243    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
244        let target_idx = self.target.id() as usize;
245        let dim = state.len();
246
247        // Calculate relaxation and dephasing probabilities
248        let p_reset = 1.0 - (-self.gate_time / self.t1).exp();
249        let p_phase = 0.5 * (1.0 - (-self.gate_time / self.t2).exp());
250
251        // Create a copy of the state for reading
252        let state_copy = state.to_vec();
253
254        // Apply thermal relaxation
255        // First apply amplitude damping (relaxation)
256        for i in 0..dim {
257            if (i >> target_idx) & 1 == 1 {
258                // This basis state has the target qubit in |1⟩
259                let base_idx = i & !(1 << target_idx); // Flip the target bit to 0
260
261                // Apply relaxation with probability p_reset
262                if fastrand::f64() < p_reset {
263                    // With probability (1-p_eq), collapse to |0⟩ state
264                    // With probability p_eq, collapse to |1⟩ state (thermal equilibrium)
265                    if fastrand::f64() < self.excited_state_population {
266                        // Stay in |1⟩ due to thermal excitation
267                        state[i] = state_copy[i];
268                    } else {
269                        // Collapse to |0⟩
270                        state[base_idx] += state_copy[i];
271                        state[i] = Complex64::new(0.0, 0.0);
272                    }
273                } else {
274                    // No relaxation occurs, but apply sqrt(1-p) factor
275                    state[i] = state_copy[i] * Complex64::new((1.0 - p_reset).sqrt(), 0.0);
276                }
277            }
278        }
279
280        // Then apply phase damping (dephasing on top of amplitude damping)
281        for i in 0..dim {
282            if (i >> target_idx) & 1 == 1 {
283                // Apply additional pure dephasing
284                if fastrand::f64() < p_phase {
285                    // Random phase
286                    state[i] *= Complex64::new(-1.0, 0.0); // Apply phase flip
287                }
288            }
289        }
290
291        // Normalize the state
292        NoiseChannelType::normalize_state(state);
293
294        Ok(())
295    }
296
297    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
298        // For thermal relaxation, we would typically have 3 Kraus operators
299        // This is a simplified implementation
300        let p_reset = 1.0 - (-self.gate_time / self.t1).exp();
301        let p_phase = 0.5 * (1.0 - (-self.gate_time / self.t2).exp());
302
303        // Return placeholder Kraus operators
304        vec![vec![Complex64::new(1.0 - p_reset - p_phase, 0.0)]]
305    }
306
307    fn probability(&self) -> f64 {
308        // Return the combined probability of an error occurring
309        let p_reset = 1.0 - (-self.gate_time / self.t1).exp();
310        let p_phase = 0.5 * (1.0 - (-self.gate_time / self.t2).exp());
311        p_reset + p_phase - p_reset * p_phase // Combined probability
312    }
313}
314
315/// Crosstalk noise channel for adjacent qubits
316#[derive(Debug, Clone)]
317pub struct CrosstalkChannel {
318    /// Primary qubit
319    pub primary: QubitId,
320
321    /// Neighbor qubit
322    pub neighbor: QubitId,
323
324    /// Crosstalk strength (0.0 to 1.0)
325    pub strength: f64,
326}
327
328impl NoiseChannel for CrosstalkChannel {
329    fn name(&self) -> &'static str {
330        "Crosstalk"
331    }
332
333    fn qubits(&self) -> Vec<QubitId> {
334        vec![self.primary, self.neighbor]
335    }
336
337    fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
338        let primary_idx = self.primary.id() as usize;
339        let neighbor_idx = self.neighbor.id() as usize;
340        let dim = state.len();
341
342        // Apply crosstalk with probability based on strength
343        if fastrand::f64() < self.strength {
344            // Create a copy of the state for reading
345            let state_copy = state.to_vec();
346
347            // Randomly select an effect (simplified model):
348            // 1. ZZ interaction
349            // 2. Neighbor rotation
350            let effect = fastrand::u32(..) % 2;
351
352            match effect {
353                0 => {
354                    // ZZ interaction
355                    for i in 0..dim {
356                        let parity = ((i >> primary_idx) & 1) ^ ((i >> neighbor_idx) & 1);
357                        if parity == 1 {
358                            // Apply phase shift if qubits have different parity
359                            let phase = fastrand::f64() * PI;
360                            state[i] *= Complex64::new(phase.cos(), phase.sin());
361                        }
362                    }
363                }
364                1 => {
365                    // Small rotation on neighbor when primary qubit is |1⟩
366                    for i in 0..dim {
367                        if (i >> primary_idx) & 1 == 1 {
368                            // Primary qubit is |1⟩, apply partial X rotation to neighbor
369                            let neighbor_bit = (i >> neighbor_idx) & 1;
370                            let flipped_i = i ^ (1 << neighbor_idx);
371
372                            // Small, random amplitude swap
373                            let theta: f64 = fastrand::f64() * 0.2; // Small angle
374                            let cos_theta = theta.cos();
375                            let sin_theta = theta.sin();
376
377                            let amp_original = state_copy[i];
378                            let amp_flipped = state_copy[flipped_i];
379
380                            if neighbor_bit == 0 {
381                                state[i] = amp_original * Complex64::new(cos_theta, 0.0)
382                                    + amp_flipped * Complex64::new(sin_theta, 0.0);
383                                state[flipped_i] = amp_original * Complex64::new(-sin_theta, 0.0)
384                                    + amp_flipped * Complex64::new(cos_theta, 0.0);
385                            } else {
386                                state[i] = amp_original * Complex64::new(cos_theta, 0.0)
387                                    - amp_flipped * Complex64::new(sin_theta, 0.0);
388                                state[flipped_i] = amp_original * Complex64::new(sin_theta, 0.0)
389                                    + amp_flipped * Complex64::new(cos_theta, 0.0);
390                            }
391                        }
392                    }
393                }
394                _ => unreachable!(),
395            }
396        }
397
398        // Normalize the state
399        NoiseChannelType::normalize_state(state);
400
401        Ok(())
402    }
403
404    fn kraus_operators(&self) -> Vec<Vec<Complex64>> {
405        // Crosstalk noise is complex and typically needs multiple Kraus operators
406        // This is a placeholder for a full implementation
407        vec![vec![Complex64::new(1.0, 0.0)]]
408    }
409
410    fn probability(&self) -> f64 {
411        self.strength
412    }
413}
414
415/// Extension to `NoiseChannelType` to include advanced noise channels
416#[derive(Debug, Clone)]
417pub enum AdvancedNoiseChannelType {
418    /// Base noise channel types
419    Base(NoiseChannelType),
420
421    /// Two-qubit depolarizing channel
422    TwoQubitDepolarizing(TwoQubitDepolarizingChannel),
423
424    /// Thermal relaxation channel
425    ThermalRelaxation(ThermalRelaxationChannel),
426
427    /// Crosstalk channel
428    Crosstalk(CrosstalkChannel),
429}
430
431impl AdvancedNoiseChannelType {
432    /// Get the name of the noise channel
433    #[must_use]
434    pub fn name(&self) -> &'static str {
435        match self {
436            Self::Base(ch) => ch.name(),
437            Self::TwoQubitDepolarizing(ch) => ch.name(),
438            Self::ThermalRelaxation(ch) => ch.name(),
439            Self::Crosstalk(ch) => ch.name(),
440        }
441    }
442
443    /// Get the qubits this channel affects
444    #[must_use]
445    pub fn qubits(&self) -> Vec<QubitId> {
446        match self {
447            Self::Base(ch) => ch.qubits(),
448            Self::TwoQubitDepolarizing(ch) => ch.qubits(),
449            Self::ThermalRelaxation(ch) => ch.qubits(),
450            Self::Crosstalk(ch) => ch.qubits(),
451        }
452    }
453
454    /// Apply the noise channel to a state vector
455    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
456        match self {
457            Self::Base(ch) => ch.apply_to_statevector(state),
458            Self::TwoQubitDepolarizing(ch) => ch.apply_to_statevector(state),
459            Self::ThermalRelaxation(ch) => ch.apply_to_statevector(state),
460            Self::Crosstalk(ch) => ch.apply_to_statevector(state),
461        }
462    }
463
464    /// Get the probability of the noise occurring
465    #[must_use]
466    pub fn probability(&self) -> f64 {
467        match self {
468            Self::Base(ch) => ch.probability(),
469            Self::TwoQubitDepolarizing(ch) => ch.probability(),
470            Self::ThermalRelaxation(ch) => ch.probability(),
471            Self::Crosstalk(ch) => ch.probability(),
472        }
473    }
474}
475
476/// Advanced noise model that supports the new noise channel types
477#[derive(Debug, Clone)]
478pub struct AdvancedNoiseModel {
479    /// List of noise channels
480    pub channels: Vec<AdvancedNoiseChannelType>,
481
482    /// Whether the noise is applied after each gate
483    pub per_gate: bool,
484}
485
486impl AdvancedNoiseModel {
487    /// Create a new empty noise model
488    #[must_use]
489    pub const fn new(per_gate: bool) -> Self {
490        Self {
491            channels: Vec::new(),
492            per_gate,
493        }
494    }
495
496    /// Add a basic noise channel to the model
497    pub fn add_base_channel(&mut self, channel: NoiseChannelType) -> &mut Self {
498        self.channels.push(AdvancedNoiseChannelType::Base(channel));
499        self
500    }
501
502    /// Add a two-qubit depolarizing noise channel to the model
503    pub fn add_two_qubit_depolarizing(
504        &mut self,
505        channel: TwoQubitDepolarizingChannel,
506    ) -> &mut Self {
507        self.channels
508            .push(AdvancedNoiseChannelType::TwoQubitDepolarizing(channel));
509        self
510    }
511
512    /// Add a thermal relaxation noise channel to the model
513    pub fn add_thermal_relaxation(&mut self, channel: ThermalRelaxationChannel) -> &mut Self {
514        self.channels
515            .push(AdvancedNoiseChannelType::ThermalRelaxation(channel));
516        self
517    }
518
519    /// Add a crosstalk noise channel to the model
520    pub fn add_crosstalk(&mut self, channel: CrosstalkChannel) -> &mut Self {
521        self.channels
522            .push(AdvancedNoiseChannelType::Crosstalk(channel));
523        self
524    }
525
526    /// Apply all noise channels to a state vector
527    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
528        for channel in &self.channels {
529            channel.apply_to_statevector(state)?;
530        }
531
532        // Normalize the state vector after applying all noise channels
533        NoiseChannelType::normalize_state(state);
534
535        Ok(())
536    }
537
538    /// Get the total number of channels
539    #[must_use]
540    pub fn num_channels(&self) -> usize {
541        self.channels.len()
542    }
543
544    /// Convert to basic noise model (for backward compatibility)
545    #[must_use]
546    pub fn to_basic_model(&self) -> NoiseModel {
547        let mut model = NoiseModel::new(self.per_gate);
548
549        for channel in &self.channels {
550            if let AdvancedNoiseChannelType::Base(ch) = channel {
551                model.channels.push(ch.clone());
552            }
553        }
554
555        model
556    }
557}
558
559impl Default for AdvancedNoiseModel {
560    fn default() -> Self {
561        Self::new(true)
562    }
563}
564
565/// Builder for realistic device noise models
566pub struct RealisticNoiseModelBuilder {
567    model: AdvancedNoiseModel,
568}
569
570impl RealisticNoiseModelBuilder {
571    /// Create a new noise model builder
572    #[must_use]
573    pub const fn new(per_gate: bool) -> Self {
574        Self {
575            model: AdvancedNoiseModel::new(per_gate),
576        }
577    }
578
579    /// Add realistic IBM Quantum device noise parameters
580    #[must_use]
581    pub fn with_ibm_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
582        match device_name {
583            "ibmq_lima" | "ibmq_belem" | "ibmq_quito" => {
584                // 5-qubit IBM Quantum Falcon processors
585                // Parameters are approximate and based on typical values
586
587                // Relaxation and dephasing times
588                let t1_values = [115e-6, 100e-6, 120e-6, 105e-6, 110e-6]; // ~100 microseconds
589                let t2_values = [95e-6, 80e-6, 100e-6, 90e-6, 85e-6]; // ~90 microseconds
590
591                // Single-qubit gates
592                let gate_time_1q = 35e-9; // 35 nanoseconds
593                let gate_error_1q = 0.001; // 0.1% error rate
594
595                // Two-qubit gates (CNOT)
596                // let _gate_time_2q = 300e-9; // 300 nanoseconds
597                let gate_error_2q = 0.01; // 1% error rate
598
599                // Readout errors
600                let readout_error = 0.025; // 2.5% error
601
602                // Add individual qubit noise
603                for (i, &qubit) in qubits.iter().enumerate().take(5) {
604                    let t1 = t1_values[i % 5];
605                    let t2 = t2_values[i % 5];
606
607                    // Add thermal relaxation
608                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
609                        target: qubit,
610                        t1,
611                        t2,
612                        gate_time: gate_time_1q,
613                        excited_state_population: 0.01, // ~1% thermal excitation
614                    });
615
616                    // Add depolarizing noise for single-qubit gates
617                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
618                        crate::noise::DepolarizingChannel {
619                            target: qubit,
620                            probability: gate_error_1q,
621                        },
622                    ));
623
624                    // Add readout error as a bit flip channel
625                    self.model.add_base_channel(NoiseChannelType::BitFlip(
626                        crate::noise::BitFlipChannel {
627                            target: qubit,
628                            probability: readout_error,
629                        },
630                    ));
631                }
632
633                // Add two-qubit gate noise (for nearest-neighbor connectivity)
634                for i in 0..qubits.len().saturating_sub(1) {
635                    let q1 = qubits[i];
636                    let q2 = qubits[i + 1];
637
638                    // Add two-qubit depolarizing noise
639                    self.model
640                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
641                            qubit1: q1,
642                            qubit2: q2,
643                            probability: gate_error_2q,
644                        });
645
646                    // Add crosstalk between adjacent qubits
647                    self.model.add_crosstalk(CrosstalkChannel {
648                        primary: q1,
649                        neighbor: q2,
650                        strength: 0.003, // 0.3% crosstalk
651                    });
652                }
653            }
654            "ibmq_bogota" | "ibmq_santiago" | "ibmq_casablanca" => {
655                // 5-qubit IBM Quantum Falcon processors (newer)
656                // Parameters are approximate and based on typical values
657
658                // Relaxation and dephasing times
659                let t1_values = [140e-6, 130e-6, 145e-6, 135e-6, 150e-6]; // ~140 microseconds
660                let t2_values = [120e-6, 110e-6, 125e-6, 115e-6, 130e-6]; // ~120 microseconds
661
662                // Single-qubit gates
663                let gate_time_1q = 30e-9; // 30 nanoseconds
664                let gate_error_1q = 0.0005; // 0.05% error rate
665
666                // Two-qubit gates (CNOT)
667                // let _gate_time_2q = 250e-9; // 250 nanoseconds
668                let gate_error_2q = 0.008; // 0.8% error rate
669
670                // Readout errors
671                let readout_error = 0.02; // 2% error
672
673                // Add individual qubit noise
674                for (i, &qubit) in qubits.iter().enumerate().take(5) {
675                    let t1 = t1_values[i % 5];
676                    let t2 = t2_values[i % 5];
677
678                    // Add thermal relaxation
679                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
680                        target: qubit,
681                        t1,
682                        t2,
683                        gate_time: gate_time_1q,
684                        excited_state_population: 0.008, // ~0.8% thermal excitation
685                    });
686
687                    // Add depolarizing noise for single-qubit gates
688                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
689                        crate::noise::DepolarizingChannel {
690                            target: qubit,
691                            probability: gate_error_1q,
692                        },
693                    ));
694
695                    // Add readout error as a bit flip channel
696                    self.model.add_base_channel(NoiseChannelType::BitFlip(
697                        crate::noise::BitFlipChannel {
698                            target: qubit,
699                            probability: readout_error,
700                        },
701                    ));
702                }
703
704                // Add two-qubit gate noise (for nearest-neighbor connectivity)
705                for i in 0..qubits.len().saturating_sub(1) {
706                    let q1 = qubits[i];
707                    let q2 = qubits[i + 1];
708
709                    // Add two-qubit depolarizing noise
710                    self.model
711                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
712                            qubit1: q1,
713                            qubit2: q2,
714                            probability: gate_error_2q,
715                        });
716
717                    // Add crosstalk between adjacent qubits
718                    self.model.add_crosstalk(CrosstalkChannel {
719                        primary: q1,
720                        neighbor: q2,
721                        strength: 0.002, // 0.2% crosstalk
722                    });
723                }
724            }
725            "ibm_cairo" | "ibm_hanoi" | "ibm_auckland" => {
726                // 27-qubit IBM Quantum Falcon processors
727                // Parameters are approximate and based on typical values
728
729                // Relaxation and dephasing times (average values)
730                let t1 = 130e-6; // 130 microseconds
731                let t2 = 100e-6; // 100 microseconds
732
733                // Single-qubit gates
734                let gate_time_1q = 35e-9; // 35 nanoseconds
735                let gate_error_1q = 0.0004; // 0.04% error rate
736
737                // Two-qubit gates (CNOT)
738                // let _gate_time_2q = 275e-9; // 275 nanoseconds
739                let gate_error_2q = 0.007; // 0.7% error rate
740
741                // Readout errors
742                let readout_error = 0.018; // 1.8% error
743
744                // Add individual qubit noise
745                for &qubit in qubits {
746                    // Add thermal relaxation
747                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
748                        target: qubit,
749                        t1,
750                        t2,
751                        gate_time: gate_time_1q,
752                        excited_state_population: 0.007, // ~0.7% thermal excitation
753                    });
754
755                    // Add depolarizing noise for single-qubit gates
756                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
757                        crate::noise::DepolarizingChannel {
758                            target: qubit,
759                            probability: gate_error_1q,
760                        },
761                    ));
762
763                    // Add readout error as a bit flip channel
764                    self.model.add_base_channel(NoiseChannelType::BitFlip(
765                        crate::noise::BitFlipChannel {
766                            target: qubit,
767                            probability: readout_error,
768                        },
769                    ));
770                }
771
772                // Add two-qubit gate noise (for nearest-neighbor connectivity)
773                for i in 0..qubits.len().saturating_sub(1) {
774                    let q1 = qubits[i];
775                    let q2 = qubits[i + 1];
776
777                    // Add two-qubit depolarizing noise
778                    self.model
779                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
780                            qubit1: q1,
781                            qubit2: q2,
782                            probability: gate_error_2q,
783                        });
784
785                    // Add crosstalk between adjacent qubits
786                    self.model.add_crosstalk(CrosstalkChannel {
787                        primary: q1,
788                        neighbor: q2,
789                        strength: 0.0015, // 0.15% crosstalk
790                    });
791                }
792            }
793            "ibm_washington" | "ibm_eagle" => {
794                // 127-qubit IBM Quantum Eagle processors
795                // Parameters are approximate and based on typical values
796
797                // Relaxation and dephasing times (average values)
798                let t1 = 150e-6; // 150 microseconds
799                let t2 = 120e-6; // 120 microseconds
800
801                // Single-qubit gates
802                let gate_time_1q = 30e-9; // 30 nanoseconds
803                let gate_error_1q = 0.0003; // 0.03% error rate
804
805                // Two-qubit gates (CNOT)
806                // let _gate_time_2q = 220e-9; // 220 nanoseconds
807                let gate_error_2q = 0.006; // 0.6% error rate
808
809                // Readout errors
810                let readout_error = 0.015; // 1.5% error
811
812                // Add individual qubit noise
813                for &qubit in qubits {
814                    // Add thermal relaxation
815                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
816                        target: qubit,
817                        t1,
818                        t2,
819                        gate_time: gate_time_1q,
820                        excited_state_population: 0.006, // ~0.6% thermal excitation
821                    });
822
823                    // Add depolarizing noise for single-qubit gates
824                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
825                        crate::noise::DepolarizingChannel {
826                            target: qubit,
827                            probability: gate_error_1q,
828                        },
829                    ));
830
831                    // Add readout error as a bit flip channel
832                    self.model.add_base_channel(NoiseChannelType::BitFlip(
833                        crate::noise::BitFlipChannel {
834                            target: qubit,
835                            probability: readout_error,
836                        },
837                    ));
838                }
839
840                // Add two-qubit gate noise (for nearest-neighbor connectivity)
841                for i in 0..qubits.len().saturating_sub(1) {
842                    let q1 = qubits[i];
843                    let q2 = qubits[i + 1];
844
845                    // Add two-qubit depolarizing noise
846                    self.model
847                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
848                            qubit1: q1,
849                            qubit2: q2,
850                            probability: gate_error_2q,
851                        });
852
853                    // Add crosstalk between adjacent qubits
854                    self.model.add_crosstalk(CrosstalkChannel {
855                        primary: q1,
856                        neighbor: q2,
857                        strength: 0.001, // 0.1% crosstalk
858                    });
859                }
860            }
861            _ => {
862                // Generic IBM Quantum device (conservative estimates)
863                // Parameters are approximate and based on typical values
864
865                // Relaxation and dephasing times (average values)
866                let t1 = 100e-6; // 100 microseconds
867                let t2 = 80e-6; // 80 microseconds
868
869                // Single-qubit gates
870                let gate_time_1q = 40e-9; // 40 nanoseconds
871                let gate_error_1q = 0.001; // 0.1% error rate
872
873                // Two-qubit gates (CNOT)
874                // let _gate_time_2q = 300e-9; // 300 nanoseconds
875                let gate_error_2q = 0.01; // 1% error rate
876
877                // Readout errors
878                let readout_error = 0.025; // 2.5% error
879
880                // Add individual qubit noise
881                for &qubit in qubits {
882                    // Add thermal relaxation
883                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
884                        target: qubit,
885                        t1,
886                        t2,
887                        gate_time: gate_time_1q,
888                        excited_state_population: 0.01, // ~1% thermal excitation
889                    });
890
891                    // Add depolarizing noise for single-qubit gates
892                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
893                        crate::noise::DepolarizingChannel {
894                            target: qubit,
895                            probability: gate_error_1q,
896                        },
897                    ));
898
899                    // Add readout error as a bit flip channel
900                    self.model.add_base_channel(NoiseChannelType::BitFlip(
901                        crate::noise::BitFlipChannel {
902                            target: qubit,
903                            probability: readout_error,
904                        },
905                    ));
906                }
907
908                // Add two-qubit gate noise (for nearest-neighbor connectivity)
909                for i in 0..qubits.len().saturating_sub(1) {
910                    let q1 = qubits[i];
911                    let q2 = qubits[i + 1];
912
913                    // Add two-qubit depolarizing noise
914                    self.model
915                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
916                            qubit1: q1,
917                            qubit2: q2,
918                            probability: gate_error_2q,
919                        });
920
921                    // Add crosstalk between adjacent qubits
922                    self.model.add_crosstalk(CrosstalkChannel {
923                        primary: q1,
924                        neighbor: q2,
925                        strength: 0.003, // 0.3% crosstalk
926                    });
927                }
928            }
929        }
930
931        self
932    }
933
934    /// Add realistic Rigetti device noise parameters
935    #[must_use]
936    pub fn with_rigetti_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
937        match device_name {
938            "Aspen-M-3" | "Aspen-M-2" => {
939                // Rigetti Aspen-M series processors
940                // Parameters are approximate and based on typical values
941
942                // Relaxation and dephasing times (average values)
943                let t1 = 20e-6; // 20 microseconds
944                let t2 = 15e-6; // 15 microseconds
945
946                // Single-qubit gates
947                let gate_time_1q = 50e-9; // 50 nanoseconds
948                let gate_error_1q = 0.0015; // 0.15% error rate
949
950                // Two-qubit gates (CZ)
951                // let _gate_time_2q = 220e-9; // 220 nanoseconds
952                let gate_error_2q = 0.02; // 2% error rate
953
954                // Readout errors
955                let readout_error = 0.03; // 3% error
956
957                // Add individual qubit noise
958                for &qubit in qubits {
959                    // Add thermal relaxation
960                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
961                        target: qubit,
962                        t1,
963                        t2,
964                        gate_time: gate_time_1q,
965                        excited_state_population: 0.02, // ~2% thermal excitation
966                    });
967
968                    // Add depolarizing noise for single-qubit gates
969                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
970                        crate::noise::DepolarizingChannel {
971                            target: qubit,
972                            probability: gate_error_1q,
973                        },
974                    ));
975
976                    // Add readout error as a bit flip channel
977                    self.model.add_base_channel(NoiseChannelType::BitFlip(
978                        crate::noise::BitFlipChannel {
979                            target: qubit,
980                            probability: readout_error,
981                        },
982                    ));
983                }
984
985                // Add two-qubit gate noise (for nearest-neighbor connectivity)
986                for i in 0..qubits.len().saturating_sub(1) {
987                    let q1 = qubits[i];
988                    let q2 = qubits[i + 1];
989
990                    // Add two-qubit depolarizing noise
991                    self.model
992                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
993                            qubit1: q1,
994                            qubit2: q2,
995                            probability: gate_error_2q,
996                        });
997
998                    // Add crosstalk between adjacent qubits
999                    self.model.add_crosstalk(CrosstalkChannel {
1000                        primary: q1,
1001                        neighbor: q2,
1002                        strength: 0.004, // 0.4% crosstalk
1003                    });
1004                }
1005            }
1006            _ => {
1007                // Generic Rigetti device (conservative estimates)
1008                // Parameters are approximate and based on typical values
1009
1010                // Relaxation and dephasing times (average values)
1011                let t1 = 15e-6; // 15 microseconds
1012                let t2 = 12e-6; // 12 microseconds
1013
1014                // Single-qubit gates
1015                let gate_time_1q = 60e-9; // 60 nanoseconds
1016                let gate_error_1q = 0.002; // 0.2% error rate
1017
1018                // Two-qubit gates (CZ)
1019                // let _gate_time_2q = 250e-9; // 250 nanoseconds
1020                let gate_error_2q = 0.025; // 2.5% error rate
1021
1022                // Readout errors
1023                let readout_error = 0.035; // 3.5% error
1024
1025                // Add individual qubit noise
1026                for &qubit in qubits {
1027                    // Add thermal relaxation
1028                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1029                        target: qubit,
1030                        t1,
1031                        t2,
1032                        gate_time: gate_time_1q,
1033                        excited_state_population: 0.025, // ~2.5% thermal excitation
1034                    });
1035
1036                    // Add depolarizing noise for single-qubit gates
1037                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
1038                        crate::noise::DepolarizingChannel {
1039                            target: qubit,
1040                            probability: gate_error_1q,
1041                        },
1042                    ));
1043
1044                    // Add readout error as a bit flip channel
1045                    self.model.add_base_channel(NoiseChannelType::BitFlip(
1046                        crate::noise::BitFlipChannel {
1047                            target: qubit,
1048                            probability: readout_error,
1049                        },
1050                    ));
1051                }
1052
1053                // Add two-qubit gate noise (for nearest-neighbor connectivity)
1054                for i in 0..qubits.len().saturating_sub(1) {
1055                    let q1 = qubits[i];
1056                    let q2 = qubits[i + 1];
1057
1058                    // Add two-qubit depolarizing noise
1059                    self.model
1060                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1061                            qubit1: q1,
1062                            qubit2: q2,
1063                            probability: gate_error_2q,
1064                        });
1065
1066                    // Add crosstalk between adjacent qubits
1067                    self.model.add_crosstalk(CrosstalkChannel {
1068                        primary: q1,
1069                        neighbor: q2,
1070                        strength: 0.005, // 0.5% crosstalk
1071                    });
1072                }
1073            }
1074        }
1075
1076        self
1077    }
1078
1079    /// Add custom thermal relaxation parameters
1080    #[must_use]
1081    pub fn with_custom_thermal_relaxation(
1082        mut self,
1083        qubits: &[QubitId],
1084        t1: Duration,
1085        t2: Duration,
1086        gate_time: Duration,
1087    ) -> Self {
1088        let t1_seconds = t1.as_secs_f64();
1089        let t2_seconds = t2.as_secs_f64();
1090        let gate_time_seconds = gate_time.as_secs_f64();
1091
1092        for &qubit in qubits {
1093            self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1094                target: qubit,
1095                t1: t1_seconds,
1096                t2: t2_seconds,
1097                gate_time: gate_time_seconds,
1098                excited_state_population: 0.01, // Default 1% thermal excitation
1099            });
1100        }
1101
1102        self
1103    }
1104
1105    /// Add custom two-qubit depolarizing noise
1106    #[must_use]
1107    pub fn with_custom_two_qubit_noise(
1108        mut self,
1109        qubit_pairs: &[(QubitId, QubitId)],
1110        probability: f64,
1111    ) -> Self {
1112        for &(q1, q2) in qubit_pairs {
1113            self.model
1114                .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1115                    qubit1: q1,
1116                    qubit2: q2,
1117                    probability,
1118                });
1119        }
1120
1121        self
1122    }
1123
1124    /// Add custom crosstalk noise between pairs of qubits
1125    #[must_use]
1126    pub fn with_custom_crosstalk(
1127        mut self,
1128        qubit_pairs: &[(QubitId, QubitId)],
1129        strength: f64,
1130    ) -> Self {
1131        for &(q1, q2) in qubit_pairs {
1132            self.model.add_crosstalk(CrosstalkChannel {
1133                primary: q1,
1134                neighbor: q2,
1135                strength,
1136            });
1137        }
1138
1139        self
1140    }
1141
1142    /// Build the noise model
1143    #[must_use]
1144    pub fn build(self) -> AdvancedNoiseModel {
1145        self.model
1146    }
1147}