quantrs2_sim/
noise_advanced.rs

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