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    #[must_use]
428    pub fn name(&self) -> &'static str {
429        match self {
430            Self::Base(ch) => ch.name(),
431            Self::TwoQubitDepolarizing(ch) => ch.name(),
432            Self::ThermalRelaxation(ch) => ch.name(),
433            Self::Crosstalk(ch) => ch.name(),
434        }
435    }
436
437    /// Get the qubits this channel affects
438    #[must_use]
439    pub fn qubits(&self) -> Vec<QubitId> {
440        match self {
441            Self::Base(ch) => ch.qubits(),
442            Self::TwoQubitDepolarizing(ch) => ch.qubits(),
443            Self::ThermalRelaxation(ch) => ch.qubits(),
444            Self::Crosstalk(ch) => ch.qubits(),
445        }
446    }
447
448    /// Apply the noise channel to a state vector
449    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
450        match self {
451            Self::Base(ch) => ch.apply_to_statevector(state),
452            Self::TwoQubitDepolarizing(ch) => ch.apply_to_statevector(state),
453            Self::ThermalRelaxation(ch) => ch.apply_to_statevector(state),
454            Self::Crosstalk(ch) => ch.apply_to_statevector(state),
455        }
456    }
457
458    /// Get the probability of the noise occurring
459    #[must_use]
460    pub fn probability(&self) -> f64 {
461        match self {
462            Self::Base(ch) => ch.probability(),
463            Self::TwoQubitDepolarizing(ch) => ch.probability(),
464            Self::ThermalRelaxation(ch) => ch.probability(),
465            Self::Crosstalk(ch) => ch.probability(),
466        }
467    }
468}
469
470/// Advanced noise model that supports the new noise channel types
471#[derive(Debug, Clone)]
472pub struct AdvancedNoiseModel {
473    /// List of noise channels
474    pub channels: Vec<AdvancedNoiseChannelType>,
475
476    /// Whether the noise is applied after each gate
477    pub per_gate: bool,
478}
479
480impl AdvancedNoiseModel {
481    /// Create a new empty noise model
482    #[must_use]
483    pub const fn new(per_gate: bool) -> Self {
484        Self {
485            channels: Vec::new(),
486            per_gate,
487        }
488    }
489
490    /// Add a basic noise channel to the model
491    pub fn add_base_channel(&mut self, channel: NoiseChannelType) -> &mut Self {
492        self.channels.push(AdvancedNoiseChannelType::Base(channel));
493        self
494    }
495
496    /// Add a two-qubit depolarizing noise channel to the model
497    pub fn add_two_qubit_depolarizing(
498        &mut self,
499        channel: TwoQubitDepolarizingChannel,
500    ) -> &mut Self {
501        self.channels
502            .push(AdvancedNoiseChannelType::TwoQubitDepolarizing(channel));
503        self
504    }
505
506    /// Add a thermal relaxation noise channel to the model
507    pub fn add_thermal_relaxation(&mut self, channel: ThermalRelaxationChannel) -> &mut Self {
508        self.channels
509            .push(AdvancedNoiseChannelType::ThermalRelaxation(channel));
510        self
511    }
512
513    /// Add a crosstalk noise channel to the model
514    pub fn add_crosstalk(&mut self, channel: CrosstalkChannel) -> &mut Self {
515        self.channels
516            .push(AdvancedNoiseChannelType::Crosstalk(channel));
517        self
518    }
519
520    /// Apply all noise channels to a state vector
521    pub fn apply_to_statevector(&self, state: &mut [Complex64]) -> QuantRS2Result<()> {
522        for channel in &self.channels {
523            channel.apply_to_statevector(state)?;
524        }
525
526        // Normalize the state vector after applying all noise channels
527        NoiseChannelType::normalize_state(state);
528
529        Ok(())
530    }
531
532    /// Get the total number of channels
533    #[must_use]
534    pub fn num_channels(&self) -> usize {
535        self.channels.len()
536    }
537
538    /// Convert to basic noise model (for backward compatibility)
539    #[must_use]
540    pub fn to_basic_model(&self) -> NoiseModel {
541        let mut model = NoiseModel::new(self.per_gate);
542
543        for channel in &self.channels {
544            if let AdvancedNoiseChannelType::Base(ch) = channel {
545                model.channels.push(ch.clone());
546            }
547        }
548
549        model
550    }
551}
552
553impl Default for AdvancedNoiseModel {
554    fn default() -> Self {
555        Self::new(true)
556    }
557}
558
559/// Builder for realistic device noise models
560pub struct RealisticNoiseModelBuilder {
561    model: AdvancedNoiseModel,
562}
563
564impl RealisticNoiseModelBuilder {
565    /// Create a new noise model builder
566    #[must_use]
567    pub const fn new(per_gate: bool) -> Self {
568        Self {
569            model: AdvancedNoiseModel::new(per_gate),
570        }
571    }
572
573    /// Add realistic IBM Quantum device noise parameters
574    #[must_use]
575    pub fn with_ibm_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
576        match device_name {
577            "ibmq_lima" | "ibmq_belem" | "ibmq_quito" => {
578                // 5-qubit IBM Quantum Falcon processors
579                // Parameters are approximate and based on typical values
580
581                // Relaxation and dephasing times
582                let t1_values = [115e-6, 100e-6, 120e-6, 105e-6, 110e-6]; // ~100 microseconds
583                let t2_values = [95e-6, 80e-6, 100e-6, 90e-6, 85e-6]; // ~90 microseconds
584
585                // Single-qubit gates
586                let gate_time_1q = 35e-9; // 35 nanoseconds
587                let gate_error_1q = 0.001; // 0.1% error rate
588
589                // Two-qubit gates (CNOT)
590                // let _gate_time_2q = 300e-9; // 300 nanoseconds
591                let gate_error_2q = 0.01; // 1% error rate
592
593                // Readout errors
594                let readout_error = 0.025; // 2.5% error
595
596                // Add individual qubit noise
597                for (i, &qubit) in qubits.iter().enumerate().take(5) {
598                    let t1 = t1_values[i % 5];
599                    let t2 = t2_values[i % 5];
600
601                    // Add thermal relaxation
602                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
603                        target: qubit,
604                        t1,
605                        t2,
606                        gate_time: gate_time_1q,
607                        excited_state_population: 0.01, // ~1% thermal excitation
608                    });
609
610                    // Add depolarizing noise for single-qubit gates
611                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
612                        crate::noise::DepolarizingChannel {
613                            target: qubit,
614                            probability: gate_error_1q,
615                        },
616                    ));
617
618                    // Add readout error as a bit flip channel
619                    self.model.add_base_channel(NoiseChannelType::BitFlip(
620                        crate::noise::BitFlipChannel {
621                            target: qubit,
622                            probability: readout_error,
623                        },
624                    ));
625                }
626
627                // Add two-qubit gate noise (for nearest-neighbor connectivity)
628                for i in 0..qubits.len().saturating_sub(1) {
629                    let q1 = qubits[i];
630                    let q2 = qubits[i + 1];
631
632                    // Add two-qubit depolarizing noise
633                    self.model
634                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
635                            qubit1: q1,
636                            qubit2: q2,
637                            probability: gate_error_2q,
638                        });
639
640                    // Add crosstalk between adjacent qubits
641                    self.model.add_crosstalk(CrosstalkChannel {
642                        primary: q1,
643                        neighbor: q2,
644                        strength: 0.003, // 0.3% crosstalk
645                    });
646                }
647            }
648            "ibmq_bogota" | "ibmq_santiago" | "ibmq_casablanca" => {
649                // 5-qubit IBM Quantum Falcon processors (newer)
650                // Parameters are approximate and based on typical values
651
652                // Relaxation and dephasing times
653                let t1_values = [140e-6, 130e-6, 145e-6, 135e-6, 150e-6]; // ~140 microseconds
654                let t2_values = [120e-6, 110e-6, 125e-6, 115e-6, 130e-6]; // ~120 microseconds
655
656                // Single-qubit gates
657                let gate_time_1q = 30e-9; // 30 nanoseconds
658                let gate_error_1q = 0.0005; // 0.05% error rate
659
660                // Two-qubit gates (CNOT)
661                // let _gate_time_2q = 250e-9; // 250 nanoseconds
662                let gate_error_2q = 0.008; // 0.8% error rate
663
664                // Readout errors
665                let readout_error = 0.02; // 2% error
666
667                // Add individual qubit noise
668                for (i, &qubit) in qubits.iter().enumerate().take(5) {
669                    let t1 = t1_values[i % 5];
670                    let t2 = t2_values[i % 5];
671
672                    // Add thermal relaxation
673                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
674                        target: qubit,
675                        t1,
676                        t2,
677                        gate_time: gate_time_1q,
678                        excited_state_population: 0.008, // ~0.8% thermal excitation
679                    });
680
681                    // Add depolarizing noise for single-qubit gates
682                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
683                        crate::noise::DepolarizingChannel {
684                            target: qubit,
685                            probability: gate_error_1q,
686                        },
687                    ));
688
689                    // Add readout error as a bit flip channel
690                    self.model.add_base_channel(NoiseChannelType::BitFlip(
691                        crate::noise::BitFlipChannel {
692                            target: qubit,
693                            probability: readout_error,
694                        },
695                    ));
696                }
697
698                // Add two-qubit gate noise (for nearest-neighbor connectivity)
699                for i in 0..qubits.len().saturating_sub(1) {
700                    let q1 = qubits[i];
701                    let q2 = qubits[i + 1];
702
703                    // Add two-qubit depolarizing noise
704                    self.model
705                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
706                            qubit1: q1,
707                            qubit2: q2,
708                            probability: gate_error_2q,
709                        });
710
711                    // Add crosstalk between adjacent qubits
712                    self.model.add_crosstalk(CrosstalkChannel {
713                        primary: q1,
714                        neighbor: q2,
715                        strength: 0.002, // 0.2% crosstalk
716                    });
717                }
718            }
719            "ibm_cairo" | "ibm_hanoi" | "ibm_auckland" => {
720                // 27-qubit IBM Quantum Falcon processors
721                // Parameters are approximate and based on typical values
722
723                // Relaxation and dephasing times (average values)
724                let t1 = 130e-6; // 130 microseconds
725                let t2 = 100e-6; // 100 microseconds
726
727                // Single-qubit gates
728                let gate_time_1q = 35e-9; // 35 nanoseconds
729                let gate_error_1q = 0.0004; // 0.04% error rate
730
731                // Two-qubit gates (CNOT)
732                // let _gate_time_2q = 275e-9; // 275 nanoseconds
733                let gate_error_2q = 0.007; // 0.7% error rate
734
735                // Readout errors
736                let readout_error = 0.018; // 1.8% error
737
738                // Add individual qubit noise
739                for &qubit in qubits {
740                    // Add thermal relaxation
741                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
742                        target: qubit,
743                        t1,
744                        t2,
745                        gate_time: gate_time_1q,
746                        excited_state_population: 0.007, // ~0.7% thermal excitation
747                    });
748
749                    // Add depolarizing noise for single-qubit gates
750                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
751                        crate::noise::DepolarizingChannel {
752                            target: qubit,
753                            probability: gate_error_1q,
754                        },
755                    ));
756
757                    // Add readout error as a bit flip channel
758                    self.model.add_base_channel(NoiseChannelType::BitFlip(
759                        crate::noise::BitFlipChannel {
760                            target: qubit,
761                            probability: readout_error,
762                        },
763                    ));
764                }
765
766                // Add two-qubit gate noise (for nearest-neighbor connectivity)
767                for i in 0..qubits.len().saturating_sub(1) {
768                    let q1 = qubits[i];
769                    let q2 = qubits[i + 1];
770
771                    // Add two-qubit depolarizing noise
772                    self.model
773                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
774                            qubit1: q1,
775                            qubit2: q2,
776                            probability: gate_error_2q,
777                        });
778
779                    // Add crosstalk between adjacent qubits
780                    self.model.add_crosstalk(CrosstalkChannel {
781                        primary: q1,
782                        neighbor: q2,
783                        strength: 0.0015, // 0.15% crosstalk
784                    });
785                }
786            }
787            "ibm_washington" | "ibm_eagle" => {
788                // 127-qubit IBM Quantum Eagle processors
789                // Parameters are approximate and based on typical values
790
791                // Relaxation and dephasing times (average values)
792                let t1 = 150e-6; // 150 microseconds
793                let t2 = 120e-6; // 120 microseconds
794
795                // Single-qubit gates
796                let gate_time_1q = 30e-9; // 30 nanoseconds
797                let gate_error_1q = 0.0003; // 0.03% error rate
798
799                // Two-qubit gates (CNOT)
800                // let _gate_time_2q = 220e-9; // 220 nanoseconds
801                let gate_error_2q = 0.006; // 0.6% error rate
802
803                // Readout errors
804                let readout_error = 0.015; // 1.5% error
805
806                // Add individual qubit noise
807                for &qubit in qubits {
808                    // Add thermal relaxation
809                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
810                        target: qubit,
811                        t1,
812                        t2,
813                        gate_time: gate_time_1q,
814                        excited_state_population: 0.006, // ~0.6% thermal excitation
815                    });
816
817                    // Add depolarizing noise for single-qubit gates
818                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
819                        crate::noise::DepolarizingChannel {
820                            target: qubit,
821                            probability: gate_error_1q,
822                        },
823                    ));
824
825                    // Add readout error as a bit flip channel
826                    self.model.add_base_channel(NoiseChannelType::BitFlip(
827                        crate::noise::BitFlipChannel {
828                            target: qubit,
829                            probability: readout_error,
830                        },
831                    ));
832                }
833
834                // Add two-qubit gate noise (for nearest-neighbor connectivity)
835                for i in 0..qubits.len().saturating_sub(1) {
836                    let q1 = qubits[i];
837                    let q2 = qubits[i + 1];
838
839                    // Add two-qubit depolarizing noise
840                    self.model
841                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
842                            qubit1: q1,
843                            qubit2: q2,
844                            probability: gate_error_2q,
845                        });
846
847                    // Add crosstalk between adjacent qubits
848                    self.model.add_crosstalk(CrosstalkChannel {
849                        primary: q1,
850                        neighbor: q2,
851                        strength: 0.001, // 0.1% crosstalk
852                    });
853                }
854            }
855            _ => {
856                // Generic IBM Quantum device (conservative estimates)
857                // Parameters are approximate and based on typical values
858
859                // Relaxation and dephasing times (average values)
860                let t1 = 100e-6; // 100 microseconds
861                let t2 = 80e-6; // 80 microseconds
862
863                // Single-qubit gates
864                let gate_time_1q = 40e-9; // 40 nanoseconds
865                let gate_error_1q = 0.001; // 0.1% error rate
866
867                // Two-qubit gates (CNOT)
868                // let _gate_time_2q = 300e-9; // 300 nanoseconds
869                let gate_error_2q = 0.01; // 1% error rate
870
871                // Readout errors
872                let readout_error = 0.025; // 2.5% error
873
874                // Add individual qubit noise
875                for &qubit in qubits {
876                    // Add thermal relaxation
877                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
878                        target: qubit,
879                        t1,
880                        t2,
881                        gate_time: gate_time_1q,
882                        excited_state_population: 0.01, // ~1% thermal excitation
883                    });
884
885                    // Add depolarizing noise for single-qubit gates
886                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
887                        crate::noise::DepolarizingChannel {
888                            target: qubit,
889                            probability: gate_error_1q,
890                        },
891                    ));
892
893                    // Add readout error as a bit flip channel
894                    self.model.add_base_channel(NoiseChannelType::BitFlip(
895                        crate::noise::BitFlipChannel {
896                            target: qubit,
897                            probability: readout_error,
898                        },
899                    ));
900                }
901
902                // Add two-qubit gate noise (for nearest-neighbor connectivity)
903                for i in 0..qubits.len().saturating_sub(1) {
904                    let q1 = qubits[i];
905                    let q2 = qubits[i + 1];
906
907                    // Add two-qubit depolarizing noise
908                    self.model
909                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
910                            qubit1: q1,
911                            qubit2: q2,
912                            probability: gate_error_2q,
913                        });
914
915                    // Add crosstalk between adjacent qubits
916                    self.model.add_crosstalk(CrosstalkChannel {
917                        primary: q1,
918                        neighbor: q2,
919                        strength: 0.003, // 0.3% crosstalk
920                    });
921                }
922            }
923        }
924
925        self
926    }
927
928    /// Add realistic Rigetti device noise parameters
929    #[must_use]
930    pub fn with_rigetti_device_noise(mut self, qubits: &[QubitId], device_name: &str) -> Self {
931        match device_name {
932            "Aspen-M-3" | "Aspen-M-2" => {
933                // Rigetti Aspen-M series processors
934                // Parameters are approximate and based on typical values
935
936                // Relaxation and dephasing times (average values)
937                let t1 = 20e-6; // 20 microseconds
938                let t2 = 15e-6; // 15 microseconds
939
940                // Single-qubit gates
941                let gate_time_1q = 50e-9; // 50 nanoseconds
942                let gate_error_1q = 0.0015; // 0.15% error rate
943
944                // Two-qubit gates (CZ)
945                // let _gate_time_2q = 220e-9; // 220 nanoseconds
946                let gate_error_2q = 0.02; // 2% error rate
947
948                // Readout errors
949                let readout_error = 0.03; // 3% error
950
951                // Add individual qubit noise
952                for &qubit in qubits {
953                    // Add thermal relaxation
954                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
955                        target: qubit,
956                        t1,
957                        t2,
958                        gate_time: gate_time_1q,
959                        excited_state_population: 0.02, // ~2% thermal excitation
960                    });
961
962                    // Add depolarizing noise for single-qubit gates
963                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
964                        crate::noise::DepolarizingChannel {
965                            target: qubit,
966                            probability: gate_error_1q,
967                        },
968                    ));
969
970                    // Add readout error as a bit flip channel
971                    self.model.add_base_channel(NoiseChannelType::BitFlip(
972                        crate::noise::BitFlipChannel {
973                            target: qubit,
974                            probability: readout_error,
975                        },
976                    ));
977                }
978
979                // Add two-qubit gate noise (for nearest-neighbor connectivity)
980                for i in 0..qubits.len().saturating_sub(1) {
981                    let q1 = qubits[i];
982                    let q2 = qubits[i + 1];
983
984                    // Add two-qubit depolarizing noise
985                    self.model
986                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
987                            qubit1: q1,
988                            qubit2: q2,
989                            probability: gate_error_2q,
990                        });
991
992                    // Add crosstalk between adjacent qubits
993                    self.model.add_crosstalk(CrosstalkChannel {
994                        primary: q1,
995                        neighbor: q2,
996                        strength: 0.004, // 0.4% crosstalk
997                    });
998                }
999            }
1000            _ => {
1001                // Generic Rigetti device (conservative estimates)
1002                // Parameters are approximate and based on typical values
1003
1004                // Relaxation and dephasing times (average values)
1005                let t1 = 15e-6; // 15 microseconds
1006                let t2 = 12e-6; // 12 microseconds
1007
1008                // Single-qubit gates
1009                let gate_time_1q = 60e-9; // 60 nanoseconds
1010                let gate_error_1q = 0.002; // 0.2% error rate
1011
1012                // Two-qubit gates (CZ)
1013                // let _gate_time_2q = 250e-9; // 250 nanoseconds
1014                let gate_error_2q = 0.025; // 2.5% error rate
1015
1016                // Readout errors
1017                let readout_error = 0.035; // 3.5% error
1018
1019                // Add individual qubit noise
1020                for &qubit in qubits {
1021                    // Add thermal relaxation
1022                    self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1023                        target: qubit,
1024                        t1,
1025                        t2,
1026                        gate_time: gate_time_1q,
1027                        excited_state_population: 0.025, // ~2.5% thermal excitation
1028                    });
1029
1030                    // Add depolarizing noise for single-qubit gates
1031                    self.model.add_base_channel(NoiseChannelType::Depolarizing(
1032                        crate::noise::DepolarizingChannel {
1033                            target: qubit,
1034                            probability: gate_error_1q,
1035                        },
1036                    ));
1037
1038                    // Add readout error as a bit flip channel
1039                    self.model.add_base_channel(NoiseChannelType::BitFlip(
1040                        crate::noise::BitFlipChannel {
1041                            target: qubit,
1042                            probability: readout_error,
1043                        },
1044                    ));
1045                }
1046
1047                // Add two-qubit gate noise (for nearest-neighbor connectivity)
1048                for i in 0..qubits.len().saturating_sub(1) {
1049                    let q1 = qubits[i];
1050                    let q2 = qubits[i + 1];
1051
1052                    // Add two-qubit depolarizing noise
1053                    self.model
1054                        .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1055                            qubit1: q1,
1056                            qubit2: q2,
1057                            probability: gate_error_2q,
1058                        });
1059
1060                    // Add crosstalk between adjacent qubits
1061                    self.model.add_crosstalk(CrosstalkChannel {
1062                        primary: q1,
1063                        neighbor: q2,
1064                        strength: 0.005, // 0.5% crosstalk
1065                    });
1066                }
1067            }
1068        }
1069
1070        self
1071    }
1072
1073    /// Add custom thermal relaxation parameters
1074    #[must_use]
1075    pub fn with_custom_thermal_relaxation(
1076        mut self,
1077        qubits: &[QubitId],
1078        t1: Duration,
1079        t2: Duration,
1080        gate_time: Duration,
1081    ) -> Self {
1082        let t1_seconds = t1.as_secs_f64();
1083        let t2_seconds = t2.as_secs_f64();
1084        let gate_time_seconds = gate_time.as_secs_f64();
1085
1086        for &qubit in qubits {
1087            self.model.add_thermal_relaxation(ThermalRelaxationChannel {
1088                target: qubit,
1089                t1: t1_seconds,
1090                t2: t2_seconds,
1091                gate_time: gate_time_seconds,
1092                excited_state_population: 0.01, // Default 1% thermal excitation
1093            });
1094        }
1095
1096        self
1097    }
1098
1099    /// Add custom two-qubit depolarizing noise
1100    #[must_use]
1101    pub fn with_custom_two_qubit_noise(
1102        mut self,
1103        qubit_pairs: &[(QubitId, QubitId)],
1104        probability: f64,
1105    ) -> Self {
1106        for &(q1, q2) in qubit_pairs {
1107            self.model
1108                .add_two_qubit_depolarizing(TwoQubitDepolarizingChannel {
1109                    qubit1: q1,
1110                    qubit2: q2,
1111                    probability,
1112                });
1113        }
1114
1115        self
1116    }
1117
1118    /// Add custom crosstalk noise between pairs of qubits
1119    #[must_use]
1120    pub fn with_custom_crosstalk(
1121        mut self,
1122        qubit_pairs: &[(QubitId, QubitId)],
1123        strength: f64,
1124    ) -> Self {
1125        for &(q1, q2) in qubit_pairs {
1126            self.model.add_crosstalk(CrosstalkChannel {
1127                primary: q1,
1128                neighbor: q2,
1129                strength,
1130            });
1131        }
1132
1133        self
1134    }
1135
1136    /// Build the noise model
1137    #[must_use]
1138    pub fn build(self) -> AdvancedNoiseModel {
1139        self.model
1140    }
1141}