quantrs2_circuit/
noise_models.rs

1//! Hardware noise model integration for quantum circuits
2//!
3//! This module provides comprehensive noise modeling capabilities for various quantum
4//! hardware platforms, including gate errors, decoherence, crosstalk, and readout errors.
5
6use crate::builder::Circuit;
7use quantrs2_core::{
8    error::{QuantRS2Error, QuantRS2Result},
9    gate::GateOp,
10    qubit::QubitId,
11};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14use std::f64::consts::PI;
15
16/// Comprehensive noise model for quantum devices
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct NoiseModel {
19    /// Single-qubit gate errors
20    pub single_qubit_errors: HashMap<String, SingleQubitError>,
21    /// Two-qubit gate errors
22    pub two_qubit_errors: HashMap<String, TwoQubitError>,
23    /// Qubit decoherence parameters
24    pub decoherence: HashMap<usize, DecoherenceParams>,
25    /// Readout errors
26    pub readout_errors: HashMap<usize, ReadoutError>,
27    /// Crosstalk parameters
28    pub crosstalk: Option<CrosstalkModel>,
29    /// Thermal noise
30    pub thermal_noise: Option<ThermalNoise>,
31    /// Leakage errors
32    pub leakage_errors: HashMap<usize, LeakageError>,
33    /// Calibration timestamp
34    pub calibration_time: std::time::SystemTime,
35}
36
37/// Single-qubit gate error model
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct SingleQubitError {
40    /// Depolarizing error probability
41    pub depolarizing: f64,
42    /// Pauli X error probability
43    pub pauli_x: f64,
44    /// Pauli Y error probability
45    pub pauli_y: f64,
46    /// Pauli Z error probability
47    pub pauli_z: f64,
48    /// Amplitude damping probability
49    pub amplitude_damping: f64,
50    /// Phase damping probability
51    pub phase_damping: f64,
52    /// Gate duration in nanoseconds
53    pub duration: f64,
54}
55
56/// Two-qubit gate error model
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct TwoQubitError {
59    /// Depolarizing error probability
60    pub depolarizing: f64,
61    /// Individual Pauli error probabilities (16 combinations)
62    pub pauli_errors: [[f64; 4]; 4], // [I,X,Y,Z] x [I,X,Y,Z]
63    /// Amplitude damping for both qubits
64    pub amplitude_damping: [f64; 2],
65    /// Phase damping for both qubits
66    pub phase_damping: [f64; 2],
67    /// Gate duration in nanoseconds
68    pub duration: f64,
69    /// Crosstalk coupling strength
70    pub crosstalk_strength: f64,
71}
72
73/// Decoherence parameters for a qubit
74#[derive(Debug, Clone, Serialize, Deserialize)]
75pub struct DecoherenceParams {
76    /// T1 relaxation time in microseconds
77    pub t1: f64,
78    /// T2 dephasing time in microseconds
79    pub t2: f64,
80    /// T2* pure dephasing time in microseconds
81    pub t2_star: f64,
82    /// Effective temperature in mK
83    pub temperature: f64,
84}
85
86/// Readout error model
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct ReadoutError {
89    /// Probability of reading |1⟩ when state is |0⟩
90    pub prob_0_to_1: f64,
91    /// Probability of reading |0⟩ when state is |1⟩
92    pub prob_1_to_0: f64,
93    /// Readout fidelity
94    pub fidelity: f64,
95    /// Measurement duration in nanoseconds
96    pub duration: f64,
97}
98
99/// Crosstalk model between qubits
100#[derive(Debug, Clone, Serialize, Deserialize, Default)]
101pub struct CrosstalkModel {
102    /// Coupling matrix (symmetric)
103    pub coupling_matrix: Vec<Vec<f64>>,
104    /// ZZ coupling strengths
105    pub zz_coupling: HashMap<(usize, usize), f64>,
106    /// XY coupling strengths
107    pub xy_coupling: HashMap<(usize, usize), f64>,
108    /// Frequency shifts due to neighboring qubits
109    pub frequency_shifts: HashMap<usize, Vec<(usize, f64)>>,
110}
111
112/// Thermal noise parameters
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct ThermalNoise {
115    /// Ambient temperature in mK
116    pub temperature: f64,
117    /// Thermal photon population
118    pub thermal_photons: f64,
119    /// Heating rate per second
120    pub heating_rate: f64,
121}
122
123/// Leakage error model (transitions to non-computational states)
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct LeakageError {
126    /// Probability of leaking to |2⟩ state
127    pub leakage_to_2: f64,
128    /// Probability of leaking to higher states
129    pub leakage_to_higher: f64,
130    /// Seepage probability (returning from leakage states)
131    pub seepage: f64,
132}
133
134/// Noise-aware circuit analysis and optimization
135pub struct NoiseAnalyzer {
136    noise_models: HashMap<String, NoiseModel>,
137}
138
139impl NoiseAnalyzer {
140    /// Create a new noise analyzer
141    #[must_use]
142    pub fn new() -> Self {
143        let mut analyzer = Self {
144            noise_models: HashMap::new(),
145        };
146
147        // Load common device noise models
148        analyzer.load_device_noise_models();
149        analyzer
150    }
151
152    /// Add a custom noise model
153    pub fn add_noise_model(&mut self, device: String, model: NoiseModel) {
154        self.noise_models.insert(device, model);
155    }
156
157    /// Get available noise models
158    #[must_use]
159    pub fn available_models(&self) -> Vec<String> {
160        self.noise_models.keys().cloned().collect()
161    }
162
163    /// Analyze circuit noise properties
164    pub fn analyze_circuit_noise<const N: usize>(
165        &self,
166        circuit: &Circuit<N>,
167        device: &str,
168    ) -> QuantRS2Result<NoiseAnalysisResult> {
169        let noise_model = self
170            .noise_models
171            .get(device)
172            .ok_or_else(|| QuantRS2Error::InvalidInput(format!("Unknown device: {device}")))?;
173
174        let mut total_error = 0.0;
175        let mut gate_errors = Vec::new();
176        let mut decoherence_error = 0.0;
177        let mut readout_error = 0.0;
178        let mut crosstalk_error = 0.0;
179
180        // Analyze each gate
181        for (i, gate) in circuit.gates().iter().enumerate() {
182            let gate_error = self.calculate_gate_error(gate.as_ref(), noise_model)?;
183            gate_errors.push((i, gate.name().to_string(), gate_error));
184            total_error += gate_error;
185        }
186
187        // Calculate decoherence errors
188        decoherence_error = self.calculate_decoherence_error(circuit, noise_model)?;
189
190        // Calculate readout errors
191        readout_error = self.calculate_readout_error(N, noise_model);
192
193        // Calculate crosstalk errors
194        if let Some(crosstalk) = &noise_model.crosstalk {
195            crosstalk_error = self.calculate_crosstalk_error(circuit, crosstalk)?;
196        }
197
198        let total_fidelity =
199            1.0 - (total_error + decoherence_error + readout_error + crosstalk_error);
200
201        Ok(NoiseAnalysisResult {
202            total_error: total_error + decoherence_error + readout_error + crosstalk_error,
203            total_fidelity,
204            gate_errors,
205            decoherence_error,
206            readout_error,
207            crosstalk_error,
208            dominant_error_source: self.identify_dominant_error_source(
209                total_error,
210                decoherence_error,
211                readout_error,
212                crosstalk_error,
213            ),
214        })
215    }
216
217    /// Calculate error for a single gate
218    fn calculate_gate_error(
219        &self,
220        gate: &dyn GateOp,
221        noise_model: &NoiseModel,
222    ) -> QuantRS2Result<f64> {
223        let gate_name = gate.name();
224        let qubits = gate.qubits();
225
226        match qubits.len() {
227            1 => {
228                if let Some(error) = noise_model.single_qubit_errors.get(gate_name) {
229                    Ok(error.depolarizing + error.amplitude_damping + error.phase_damping)
230                } else {
231                    // Use average single-qubit error if specific gate not found
232                    let avg_error = noise_model
233                        .single_qubit_errors
234                        .values()
235                        .map(|e| e.depolarizing + e.amplitude_damping + e.phase_damping)
236                        .sum::<f64>()
237                        / noise_model.single_qubit_errors.len() as f64;
238                    Ok(avg_error)
239                }
240            }
241            2 => {
242                if let Some(error) = noise_model.two_qubit_errors.get(gate_name) {
243                    Ok(error.depolarizing
244                        + error.amplitude_damping.iter().sum::<f64>()
245                        + error.phase_damping.iter().sum::<f64>())
246                } else {
247                    // Use average two-qubit error if specific gate not found
248                    let avg_error = noise_model
249                        .two_qubit_errors
250                        .values()
251                        .map(|e| {
252                            e.depolarizing
253                                + e.amplitude_damping.iter().sum::<f64>()
254                                + e.phase_damping.iter().sum::<f64>()
255                        })
256                        .sum::<f64>()
257                        / noise_model.two_qubit_errors.len() as f64;
258                    Ok(avg_error)
259                }
260            }
261            _ => {
262                // Multi-qubit gates - estimate based on constituent gates
263                Ok(0.01 * qubits.len() as f64) // Rough estimate
264            }
265        }
266    }
267
268    /// Calculate decoherence error over circuit execution
269    fn calculate_decoherence_error<const N: usize>(
270        &self,
271        circuit: &Circuit<N>,
272        noise_model: &NoiseModel,
273    ) -> QuantRS2Result<f64> {
274        let total_time = self.estimate_circuit_time(circuit, noise_model);
275        let mut total_decoherence = 0.0;
276
277        for qubit_id in 0..N {
278            if let Some(decoherence) = noise_model.decoherence.get(&qubit_id) {
279                // T1 relaxation error
280                let t1_error = 1.0 - (-total_time / decoherence.t1).exp();
281
282                // T2 dephasing error
283                let t2_error = 1.0 - (-total_time / decoherence.t2).exp();
284
285                total_decoherence += t1_error + t2_error;
286            }
287        }
288
289        Ok(total_decoherence / N as f64)
290    }
291
292    /// Estimate total circuit execution time
293    fn estimate_circuit_time<const N: usize>(
294        &self,
295        circuit: &Circuit<N>,
296        noise_model: &NoiseModel,
297    ) -> f64 {
298        let mut total_time = 0.0;
299
300        for gate in circuit.gates() {
301            let gate_name = gate.name();
302            let qubits = gate.qubits();
303
304            let duration = match qubits.len() {
305                1 => noise_model
306                    .single_qubit_errors
307                    .get(gate_name)
308                    .map_or(10.0, |e| e.duration), // Default 10ns
309                2 => noise_model
310                    .two_qubit_errors
311                    .get(gate_name)
312                    .map_or(200.0, |e| e.duration), // Default 200ns
313                _ => 500.0, // Multi-qubit gates take longer
314            };
315
316            total_time += duration;
317        }
318
319        total_time / 1000.0 // Convert to microseconds
320    }
321
322    /// Calculate readout error
323    fn calculate_readout_error(&self, num_qubits: usize, noise_model: &NoiseModel) -> f64 {
324        let mut total_readout_error = 0.0;
325
326        for qubit_id in 0..num_qubits {
327            if let Some(readout) = noise_model.readout_errors.get(&qubit_id) {
328                total_readout_error += 1.0 - readout.fidelity;
329            }
330        }
331
332        total_readout_error / num_qubits as f64
333    }
334
335    /// Calculate crosstalk error
336    fn calculate_crosstalk_error<const N: usize>(
337        &self,
338        circuit: &Circuit<N>,
339        crosstalk: &CrosstalkModel,
340    ) -> QuantRS2Result<f64> {
341        let mut total_crosstalk = 0.0;
342
343        for gate in circuit.gates() {
344            if gate.qubits().len() == 2 {
345                let qubits: Vec<_> = gate.qubits().iter().map(|q| q.id() as usize).collect();
346                let q1 = qubits[0];
347                let q2 = qubits[1];
348
349                // ZZ crosstalk
350                if let Some(zz_strength) = crosstalk.zz_coupling.get(&(q1, q2)) {
351                    total_crosstalk += zz_strength.abs();
352                }
353
354                // XY crosstalk
355                if let Some(xy_strength) = crosstalk.xy_coupling.get(&(q1, q2)) {
356                    total_crosstalk += xy_strength.abs();
357                }
358            }
359        }
360
361        Ok(total_crosstalk / circuit.gates().len() as f64)
362    }
363
364    /// Identify the dominant source of error
365    fn identify_dominant_error_source(
366        &self,
367        gate_error: f64,
368        decoherence_error: f64,
369        readout_error: f64,
370        crosstalk_error: f64,
371    ) -> ErrorSource {
372        let max_error = gate_error
373            .max(decoherence_error)
374            .max(readout_error)
375            .max(crosstalk_error);
376
377        if max_error == gate_error {
378            ErrorSource::GateErrors
379        } else if max_error == decoherence_error {
380            ErrorSource::Decoherence
381        } else if max_error == readout_error {
382            ErrorSource::Readout
383        } else {
384            ErrorSource::Crosstalk
385        }
386    }
387
388    /// Load device-specific noise models
389    fn load_device_noise_models(&mut self) {
390        // IBM Quantum noise model
391        self.add_noise_model("ibm_quantum".to_string(), NoiseModel::ibm_quantum());
392
393        // Google Quantum AI noise model
394        self.add_noise_model("google_quantum".to_string(), NoiseModel::google_quantum());
395
396        // AWS Braket noise model
397        self.add_noise_model("aws_braket".to_string(), NoiseModel::aws_braket());
398    }
399}
400
401/// Result of noise analysis
402#[derive(Debug, Clone)]
403pub struct NoiseAnalysisResult {
404    /// Total error probability
405    pub total_error: f64,
406    /// Overall circuit fidelity
407    pub total_fidelity: f64,
408    /// Individual gate errors (index, `gate_name`, error)
409    pub gate_errors: Vec<(usize, String, f64)>,
410    /// Decoherence contribution
411    pub decoherence_error: f64,
412    /// Readout error contribution
413    pub readout_error: f64,
414    /// Crosstalk error contribution
415    pub crosstalk_error: f64,
416    /// Dominant error source
417    pub dominant_error_source: ErrorSource,
418}
419
420/// Primary sources of quantum errors
421#[derive(Debug, Clone, PartialEq, Eq)]
422pub enum ErrorSource {
423    GateErrors,
424    Decoherence,
425    Readout,
426    Crosstalk,
427}
428
429impl NoiseModel {
430    /// Create IBM Quantum noise model based on typical device characteristics
431    #[must_use]
432    pub fn ibm_quantum() -> Self {
433        let mut single_qubit_errors = HashMap::new();
434
435        // Typical IBM single-qubit gate errors
436        single_qubit_errors.insert(
437            "X".to_string(),
438            SingleQubitError {
439                depolarizing: 0.0001,
440                pauli_x: 0.00005,
441                pauli_y: 0.00005,
442                pauli_z: 0.0001,
443                amplitude_damping: 0.0002,
444                phase_damping: 0.0003,
445                duration: 35.0, // nanoseconds
446            },
447        );
448
449        single_qubit_errors.insert(
450            "RZ".to_string(),
451            SingleQubitError {
452                depolarizing: 0.0,
453                pauli_x: 0.0,
454                pauli_y: 0.0,
455                pauli_z: 0.00001,
456                amplitude_damping: 0.0,
457                phase_damping: 0.00002,
458                duration: 0.0, // Virtual gate
459            },
460        );
461
462        let mut two_qubit_errors = HashMap::new();
463
464        // CNOT gate error
465        two_qubit_errors.insert(
466            "CNOT".to_string(),
467            TwoQubitError {
468                depolarizing: 0.01,
469                pauli_errors: [[0.0; 4]; 4], // Simplified for now
470                amplitude_damping: [0.002, 0.002],
471                phase_damping: [0.003, 0.003],
472                duration: 300.0, // nanoseconds
473                crosstalk_strength: 0.001,
474            },
475        );
476
477        let mut decoherence = HashMap::new();
478        for i in 0..127 {
479            decoherence.insert(
480                i,
481                DecoherenceParams {
482                    t1: 100.0, // microseconds
483                    t2: 80.0,  // microseconds
484                    t2_star: 70.0,
485                    temperature: 15.0, // mK
486                },
487            );
488        }
489
490        let mut readout_errors = HashMap::new();
491        for i in 0..127 {
492            readout_errors.insert(
493                i,
494                ReadoutError {
495                    prob_0_to_1: 0.01,
496                    prob_1_to_0: 0.02,
497                    fidelity: 0.985,
498                    duration: 1000.0, // nanoseconds
499                },
500            );
501        }
502
503        Self {
504            single_qubit_errors,
505            two_qubit_errors,
506            decoherence,
507            readout_errors,
508            crosstalk: Some(CrosstalkModel::default()),
509            thermal_noise: Some(ThermalNoise {
510                temperature: 15.0,
511                thermal_photons: 0.001,
512                heating_rate: 0.0001,
513            }),
514            leakage_errors: HashMap::new(),
515            calibration_time: std::time::SystemTime::now(),
516        }
517    }
518
519    /// Create Google Quantum AI noise model
520    #[must_use]
521    pub fn google_quantum() -> Self {
522        let mut single_qubit_errors = HashMap::new();
523
524        single_qubit_errors.insert(
525            "RZ".to_string(),
526            SingleQubitError {
527                depolarizing: 0.0,
528                pauli_x: 0.0,
529                pauli_y: 0.0,
530                pauli_z: 0.00001,
531                amplitude_damping: 0.0,
532                phase_damping: 0.00001,
533                duration: 0.0,
534            },
535        );
536
537        single_qubit_errors.insert(
538            "SQRT_X".to_string(),
539            SingleQubitError {
540                depolarizing: 0.0005,
541                pauli_x: 0.0002,
542                pauli_y: 0.0002,
543                pauli_z: 0.0001,
544                amplitude_damping: 0.0001,
545                phase_damping: 0.0002,
546                duration: 25.0,
547            },
548        );
549
550        let mut two_qubit_errors = HashMap::new();
551
552        two_qubit_errors.insert(
553            "CZ".to_string(),
554            TwoQubitError {
555                depolarizing: 0.005,
556                pauli_errors: [[0.0; 4]; 4],
557                amplitude_damping: [0.001, 0.001],
558                phase_damping: [0.002, 0.002],
559                duration: 20.0,
560                crosstalk_strength: 0.0005,
561            },
562        );
563
564        let mut decoherence = HashMap::new();
565        for i in 0..70 {
566            decoherence.insert(
567                i,
568                DecoherenceParams {
569                    t1: 50.0,
570                    t2: 40.0,
571                    t2_star: 35.0,
572                    temperature: 10.0,
573                },
574            );
575        }
576
577        let mut readout_errors = HashMap::new();
578        for i in 0..70 {
579            readout_errors.insert(
580                i,
581                ReadoutError {
582                    prob_0_to_1: 0.005,
583                    prob_1_to_0: 0.008,
584                    fidelity: 0.99,
585                    duration: 500.0,
586                },
587            );
588        }
589
590        Self {
591            single_qubit_errors,
592            two_qubit_errors,
593            decoherence,
594            readout_errors,
595            crosstalk: Some(CrosstalkModel::default()),
596            thermal_noise: Some(ThermalNoise {
597                temperature: 10.0,
598                thermal_photons: 0.0005,
599                heating_rate: 0.00005,
600            }),
601            leakage_errors: HashMap::new(),
602            calibration_time: std::time::SystemTime::now(),
603        }
604    }
605
606    /// Create AWS Braket noise model
607    #[must_use]
608    pub fn aws_braket() -> Self {
609        // Simplified model that varies by backend
610        let mut single_qubit_errors = HashMap::new();
611
612        single_qubit_errors.insert(
613            "RZ".to_string(),
614            SingleQubitError {
615                depolarizing: 0.0001,
616                pauli_x: 0.00005,
617                pauli_y: 0.00005,
618                pauli_z: 0.00002,
619                amplitude_damping: 0.0001,
620                phase_damping: 0.0002,
621                duration: 0.0,
622            },
623        );
624
625        let mut two_qubit_errors = HashMap::new();
626
627        two_qubit_errors.insert(
628            "CNOT".to_string(),
629            TwoQubitError {
630                depolarizing: 0.008,
631                pauli_errors: [[0.0; 4]; 4],
632                amplitude_damping: [0.0015, 0.0015],
633                phase_damping: [0.0025, 0.0025],
634                duration: 200.0,
635                crosstalk_strength: 0.0008,
636            },
637        );
638
639        let mut decoherence = HashMap::new();
640        for i in 0..100 {
641            decoherence.insert(
642                i,
643                DecoherenceParams {
644                    t1: 80.0,
645                    t2: 60.0,
646                    t2_star: 50.0,
647                    temperature: 12.0,
648                },
649            );
650        }
651
652        let mut readout_errors = HashMap::new();
653        for i in 0..100 {
654            readout_errors.insert(
655                i,
656                ReadoutError {
657                    prob_0_to_1: 0.008,
658                    prob_1_to_0: 0.012,
659                    fidelity: 0.988,
660                    duration: 800.0,
661                },
662            );
663        }
664
665        Self {
666            single_qubit_errors,
667            two_qubit_errors,
668            decoherence,
669            readout_errors,
670            crosstalk: Some(CrosstalkModel::default()),
671            thermal_noise: Some(ThermalNoise {
672                temperature: 12.0,
673                thermal_photons: 0.0008,
674                heating_rate: 0.00008,
675            }),
676            leakage_errors: HashMap::new(),
677            calibration_time: std::time::SystemTime::now(),
678        }
679    }
680}
681
682impl Default for NoiseAnalyzer {
683    fn default() -> Self {
684        Self::new()
685    }
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691    use quantrs2_core::gate::multi::CNOT;
692    use quantrs2_core::gate::single::Hadamard;
693
694    #[test]
695    fn test_noise_analyzer_creation() {
696        let analyzer = NoiseAnalyzer::new();
697        assert!(!analyzer.available_models().is_empty());
698    }
699
700    #[test]
701    fn test_noise_model_creation() {
702        let model = NoiseModel::ibm_quantum();
703        assert!(!model.single_qubit_errors.is_empty());
704        assert!(!model.two_qubit_errors.is_empty());
705        assert!(!model.decoherence.is_empty());
706    }
707
708    #[test]
709    fn test_circuit_noise_analysis() {
710        let analyzer = NoiseAnalyzer::new();
711        let mut circuit = Circuit::<2>::new();
712        circuit
713            .add_gate(Hadamard { target: QubitId(0) })
714            .expect("add H gate to circuit");
715        circuit
716            .add_gate(CNOT {
717                control: QubitId(0),
718                target: QubitId(1),
719            })
720            .expect("add CNOT gate to circuit");
721
722        let result = analyzer
723            .analyze_circuit_noise(&circuit, "ibm_quantum")
724            .expect("analyze_circuit_noise should succeed");
725        assert!(result.total_fidelity > 0.0 && result.total_fidelity < 1.0);
726        assert!(!result.gate_errors.is_empty());
727    }
728
729    #[test]
730    fn test_single_qubit_error_calculation() {
731        let analyzer = NoiseAnalyzer::new();
732        let model = NoiseModel::ibm_quantum();
733        let h_gate = Hadamard { target: QubitId(0) };
734
735        let error = analyzer
736            .calculate_gate_error(&h_gate, &model)
737            .expect("calculate_gate_error should succeed");
738        assert!(error > 0.0);
739    }
740
741    #[test]
742    fn test_decoherence_params() {
743        let params = DecoherenceParams {
744            t1: 100.0,
745            t2: 80.0,
746            t2_star: 70.0,
747            temperature: 15.0,
748        };
749
750        assert!(params.t1 > params.t2);
751        assert!(params.t2 > params.t2_star);
752    }
753
754    #[test]
755    fn test_readout_error() {
756        let error = ReadoutError {
757            prob_0_to_1: 0.01,
758            prob_1_to_0: 0.02,
759            fidelity: 0.985,
760            duration: 1000.0,
761        };
762
763        assert!(error.fidelity < 1.0);
764        assert!(error.prob_0_to_1 + error.prob_1_to_0 < 1.0);
765    }
766}