quantrs2_sim/
device_noise_models.rs

1//! Realistic device noise models for quantum hardware simulation.
2//!
3//! This module provides comprehensive noise models that accurately represent
4//! the characteristics of real quantum devices, including superconducting
5//! transmon qubits, trapped ions, photonic systems, and other quantum
6//! computing platforms. It integrates with SciRS2 for high-performance
7//! noise simulation and calibration data analysis.
8
9use ndarray::{Array1, Array2};
10use num_complex::Complex64;
11use serde::{Deserialize, Serialize};
12use std::collections::HashMap;
13
14use crate::error::{Result, SimulatorError};
15use crate::scirs2_integration::SciRS2Backend;
16
17/// Device types supported for noise modeling
18#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
19pub enum DeviceType {
20    /// Superconducting transmon qubits
21    Superconducting,
22    /// Trapped ion systems
23    TrappedIon,
24    /// Photonic quantum systems
25    Photonic,
26    /// Silicon spin qubits
27    SiliconSpin,
28    /// Neutral atom arrays
29    NeutralAtom,
30    /// Nitrogen-vacancy centers
31    NVCenter,
32    /// Custom device type
33    Custom(String),
34}
35
36/// Device noise model configuration
37#[derive(Debug, Clone)]
38pub struct DeviceNoiseConfig {
39    /// Device type
40    pub device_type: DeviceType,
41    /// Device topology (connectivity graph)
42    pub topology: DeviceTopology,
43    /// Temperature in Kelvin
44    pub temperature: f64,
45    /// Enable correlated noise
46    pub enable_correlated_noise: bool,
47    /// Enable time-dependent noise
48    pub enable_time_dependent_noise: bool,
49    /// Noise calibration data
50    pub calibration_data: Option<CalibrationData>,
51    /// Random seed for noise sampling
52    pub random_seed: Option<u64>,
53    /// Real-time noise adaptation
54    pub real_time_adaptation: bool,
55}
56
57impl Default for DeviceNoiseConfig {
58    fn default() -> Self {
59        Self {
60            device_type: DeviceType::Superconducting,
61            topology: DeviceTopology::default(),
62            temperature: 0.015, // ~15 mK
63            enable_correlated_noise: true,
64            enable_time_dependent_noise: true,
65            calibration_data: None,
66            random_seed: None,
67            real_time_adaptation: false,
68        }
69    }
70}
71
72/// Device topology representation
73#[derive(Debug, Clone, PartialEq)]
74pub struct DeviceTopology {
75    /// Number of qubits
76    pub num_qubits: usize,
77    /// Connectivity matrix (symmetric)
78    pub connectivity: Array2<bool>,
79    /// Physical positions of qubits (x, y, z)
80    pub positions: Vec<(f64, f64, f64)>,
81    /// Coupling strengths between connected qubits
82    pub coupling_strengths: HashMap<(usize, usize), f64>,
83    /// Qubit frequencies
84    pub frequencies: Vec<f64>,
85}
86
87impl Default for DeviceTopology {
88    fn default() -> Self {
89        Self {
90            num_qubits: 0,
91            connectivity: Array2::default((0, 0)),
92            positions: Vec::new(),
93            coupling_strengths: HashMap::new(),
94            frequencies: Vec::new(),
95        }
96    }
97}
98
99impl DeviceTopology {
100    /// Create a linear chain topology
101    pub fn linear_chain(num_qubits: usize) -> Self {
102        let mut connectivity = Array2::from_elem((num_qubits, num_qubits), false);
103        let mut coupling_strengths = HashMap::new();
104
105        for i in 0..num_qubits - 1 {
106            connectivity[[i, i + 1]] = true;
107            connectivity[[i + 1, i]] = true;
108            coupling_strengths.insert((i, i + 1), 1.0);
109            coupling_strengths.insert((i + 1, i), 1.0);
110        }
111
112        let positions = (0..num_qubits).map(|i| (i as f64, 0.0, 0.0)).collect();
113
114        let frequencies = (0..num_qubits)
115            .map(|i| 5.0 + 0.1 * i as f64) // GHz, with detuning
116            .collect();
117
118        Self {
119            num_qubits,
120            connectivity,
121            positions,
122            coupling_strengths,
123            frequencies,
124        }
125    }
126
127    /// Create a square lattice topology
128    pub fn square_lattice(width: usize, height: usize) -> Self {
129        let num_qubits = width * height;
130        let mut connectivity = Array2::from_elem((num_qubits, num_qubits), false);
131        let mut coupling_strengths = HashMap::new();
132
133        for i in 0..height {
134            for j in 0..width {
135                let qubit = i * width + j;
136
137                // Horizontal connections
138                if j < width - 1 {
139                    let neighbor = i * width + (j + 1);
140                    connectivity[[qubit, neighbor]] = true;
141                    connectivity[[neighbor, qubit]] = true;
142                    coupling_strengths.insert((qubit, neighbor), 1.0);
143                    coupling_strengths.insert((neighbor, qubit), 1.0);
144                }
145
146                // Vertical connections
147                if i < height - 1 {
148                    let neighbor = (i + 1) * width + j;
149                    connectivity[[qubit, neighbor]] = true;
150                    connectivity[[neighbor, qubit]] = true;
151                    coupling_strengths.insert((qubit, neighbor), 1.0);
152                    coupling_strengths.insert((neighbor, qubit), 1.0);
153                }
154            }
155        }
156
157        let positions = (0..height)
158            .flat_map(|i| (0..width).map(move |j| (j as f64, i as f64, 0.0)))
159            .collect();
160
161        let frequencies = (0..num_qubits)
162            .map(|i| 5.0 + 0.1 * (i % 7) as f64) // Frequency comb to avoid crosstalk
163            .collect();
164
165        Self {
166            num_qubits,
167            connectivity,
168            positions,
169            coupling_strengths,
170            frequencies,
171        }
172    }
173
174    /// Create IBM heavy-hex topology
175    pub fn heavy_hex(distance: usize) -> Self {
176        // Simplified heavy-hex implementation
177        let num_qubits = distance * distance * 2;
178        let connectivity = Array2::from_elem((num_qubits, num_qubits), false);
179
180        // This would be implemented with the actual heavy-hex connectivity pattern
181        Self {
182            num_qubits,
183            connectivity,
184            positions: (0..num_qubits).map(|i| (i as f64, 0.0, 0.0)).collect(),
185            coupling_strengths: HashMap::new(),
186            frequencies: (0..num_qubits).map(|i| 5.0 + 0.1 * i as f64).collect(),
187        }
188    }
189
190    /// Get nearest neighbors of a qubit
191    pub fn get_neighbors(&self, qubit: usize) -> Vec<usize> {
192        (0..self.num_qubits)
193            .filter(|&i| i != qubit && self.connectivity[[qubit, i]])
194            .collect()
195    }
196
197    /// Calculate distance between two qubits
198    pub fn distance(&self, qubit1: usize, qubit2: usize) -> f64 {
199        let pos1 = &self.positions[qubit1];
200        let pos2 = &self.positions[qubit2];
201        ((pos1.0 - pos2.0).powi(2) + (pos1.1 - pos2.1).powi(2) + (pos1.2 - pos2.2).powi(2)).sqrt()
202    }
203}
204
205/// Calibration data from real devices
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct CalibrationData {
208    /// Device identifier
209    pub device_id: String,
210    /// Calibration timestamp
211    pub timestamp: std::time::SystemTime,
212    /// Single-qubit gate fidelities
213    pub single_qubit_fidelities: Vec<f64>,
214    /// Two-qubit gate fidelities
215    pub two_qubit_fidelities: HashMap<(usize, usize), f64>,
216    /// Coherence times T1 (relaxation)
217    pub t1_times: Vec<f64>,
218    /// Coherence times T2 (dephasing)
219    pub t2_times: Vec<f64>,
220    /// Readout fidelities
221    pub readout_fidelities: Vec<f64>,
222    /// Gate times
223    pub gate_times: GateTimes,
224    /// Cross-talk matrices
225    pub crosstalk_matrices: HashMap<String, Array2<f64>>,
226    /// Frequency drift parameters
227    pub frequency_drift: Vec<FrequencyDrift>,
228}
229
230/// Gate timing information
231#[derive(Debug, Clone, Serialize, Deserialize)]
232pub struct GateTimes {
233    /// Single-qubit gate time (ns)
234    pub single_qubit: f64,
235    /// Two-qubit gate time (ns)
236    pub two_qubit: f64,
237    /// Measurement time (ns)
238    pub measurement: f64,
239    /// Reset time (ns)
240    pub reset: f64,
241}
242
243impl Default for GateTimes {
244    fn default() -> Self {
245        Self {
246            single_qubit: 20.0,  // 20 ns
247            two_qubit: 100.0,    // 100 ns
248            measurement: 1000.0, // 1 μs
249            reset: 500.0,        // 500 ns
250        }
251    }
252}
253
254/// Frequency drift model
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct FrequencyDrift {
257    /// Qubit index
258    pub qubit: usize,
259    /// Drift rate (Hz/s)
260    pub drift_rate: f64,
261    /// Random walk coefficient
262    pub random_walk_coeff: f64,
263}
264
265/// Device-specific noise models
266pub trait DeviceNoiseModel: Send + Sync {
267    /// Get the device type
268    fn device_type(&self) -> DeviceType;
269
270    /// Apply single-qubit gate noise
271    fn apply_single_qubit_noise(
272        &self,
273        qubit: usize,
274        gate_time: f64,
275        state: &mut Array1<Complex64>,
276    ) -> Result<()>;
277
278    /// Apply two-qubit gate noise
279    fn apply_two_qubit_noise(
280        &self,
281        qubit1: usize,
282        qubit2: usize,
283        gate_time: f64,
284        state: &mut Array1<Complex64>,
285    ) -> Result<()>;
286
287    /// Apply measurement noise
288    fn apply_measurement_noise(&self, qubit: usize, measurement_result: bool) -> Result<bool>;
289
290    /// Apply idle noise (during gates on other qubits)
291    fn apply_idle_noise(
292        &self,
293        qubit: usize,
294        idle_time: f64,
295        state: &mut Array1<Complex64>,
296    ) -> Result<()>;
297
298    /// Update time-dependent parameters
299    fn update_time_dependent_parameters(&mut self, current_time: f64) -> Result<()>;
300}
301
302/// Superconducting transmon noise model
303#[derive(Debug, Clone)]
304pub struct SuperconductingNoiseModel {
305    /// Configuration
306    config: DeviceNoiseConfig,
307    /// Coherence parameters
308    coherence_params: CoherenceParameters,
309    /// Gate error rates
310    gate_errors: GateErrorRates,
311    /// Readout error matrix
312    readout_errors: Array2<f64>,
313    /// Cross-talk matrices
314    crosstalk: HashMap<String, Array2<f64>>,
315    /// Current time for time-dependent effects
316    current_time: f64,
317    /// Random number generator state
318    rng_state: u64,
319}
320
321/// Coherence parameters for superconducting qubits
322#[derive(Debug, Clone)]
323pub struct CoherenceParameters {
324    /// T1 times for each qubit (μs)
325    pub t1_times: Vec<f64>,
326    /// T2 times for each qubit (μs)
327    pub t2_times: Vec<f64>,
328    /// Pure dephasing times T2* (μs)
329    pub t2_star_times: Vec<f64>,
330    /// Temperature-dependent scaling
331    pub temperature_scaling: f64,
332}
333
334/// Gate error rates
335#[derive(Debug, Clone)]
336pub struct GateErrorRates {
337    /// Single-qubit gate error rates
338    pub single_qubit: Vec<f64>,
339    /// Two-qubit gate error rates
340    pub two_qubit: HashMap<(usize, usize), f64>,
341    /// Depolarizing error rates
342    pub depolarizing: Vec<f64>,
343    /// Dephasing error rates
344    pub dephasing: Vec<f64>,
345    /// Amplitude damping rates
346    pub amplitude_damping: Vec<f64>,
347}
348
349impl SuperconductingNoiseModel {
350    /// Create new superconducting noise model
351    pub fn new(config: DeviceNoiseConfig) -> Result<Self> {
352        let num_qubits = config.topology.num_qubits;
353
354        // Initialize coherence parameters based on typical superconducting values
355        let coherence_params = CoherenceParameters {
356            t1_times: (0..num_qubits)
357                .map(|_| 50.0 + fastrand::f64() * 50.0)
358                .collect(), // 50-100 μs
359            t2_times: (0..num_qubits)
360                .map(|_| 20.0 + fastrand::f64() * 30.0)
361                .collect(), // 20-50 μs
362            t2_star_times: (0..num_qubits)
363                .map(|_| 10.0 + fastrand::f64() * 10.0)
364                .collect(), // 10-20 μs
365            temperature_scaling: Self::calculate_temperature_scaling(config.temperature),
366        };
367
368        // Initialize gate error rates
369        let gate_errors = GateErrorRates {
370            single_qubit: (0..num_qubits)
371                .map(|_| 1e-4 + fastrand::f64() * 1e-3)
372                .collect(), // 0.01-0.1%
373            two_qubit: HashMap::new(), // Will be populated based on connectivity
374            depolarizing: (0..num_qubits).map(|_| 1e-5).collect(),
375            dephasing: (0..num_qubits).map(|_| 1e-4).collect(),
376            amplitude_damping: (0..num_qubits).map(|_| 1e-5).collect(),
377        };
378
379        // Initialize readout error matrix
380        let readout_errors = Array2::from_elem((num_qubits, 2), 0.05); // 5% readout error
381
382        let rng_state = config.random_seed.unwrap_or_else(|| {
383            use std::time::{SystemTime, UNIX_EPOCH};
384            SystemTime::now()
385                .duration_since(UNIX_EPOCH)
386                .unwrap()
387                .as_nanos() as u64
388        });
389
390        Ok(Self {
391            config,
392            coherence_params,
393            gate_errors,
394            readout_errors,
395            crosstalk: HashMap::new(),
396            current_time: 0.0,
397            rng_state,
398        })
399    }
400
401    /// Create from calibration data
402    pub fn from_calibration_data(
403        config: DeviceNoiseConfig,
404        calibration: &CalibrationData,
405    ) -> Result<Self> {
406        let mut model = Self::new(config)?;
407
408        // Update parameters from calibration data
409        model.coherence_params.t1_times = calibration.t1_times.clone();
410        model.coherence_params.t2_times = calibration.t2_times.clone();
411        model.gate_errors.single_qubit = calibration
412            .single_qubit_fidelities
413            .iter()
414            .map(|&f| 1.0 - f)
415            .collect();
416
417        // Update two-qubit gate errors
418        for (&(q1, q2), &fidelity) in &calibration.two_qubit_fidelities {
419            model.gate_errors.two_qubit.insert((q1, q2), 1.0 - fidelity);
420        }
421
422        // Update readout errors
423        for (i, &fidelity) in calibration.readout_fidelities.iter().enumerate() {
424            model.readout_errors[[i, 0]] = 1.0 - fidelity; // |0⟩ → |1⟩ error
425            model.readout_errors[[i, 1]] = 1.0 - fidelity; // |1⟩ → |0⟩ error
426        }
427
428        model.crosstalk = calibration.crosstalk_matrices.clone();
429
430        Ok(model)
431    }
432
433    /// Calculate temperature scaling factor
434    fn calculate_temperature_scaling(temperature: f64) -> f64 {
435        // Thermal population of excited state
436        let kb_t_over_hf = temperature * 1.38e-23 / (6.626e-34 * 5e9); // Assuming 5 GHz qubit
437        (-1.0 / kb_t_over_hf).exp() / (1.0 + (-1.0 / kb_t_over_hf).exp())
438    }
439
440    /// Sample from random number generator
441    fn random(&mut self) -> f64 {
442        // Simple linear congruential generator
443        self.rng_state = self.rng_state.wrapping_mul(1103515245).wrapping_add(12345);
444        (self.rng_state as f64) / (u64::MAX as f64)
445    }
446
447    /// Apply decoherence channel
448    fn apply_decoherence(
449        &mut self,
450        qubit: usize,
451        time_ns: f64,
452        state: &mut Array1<Complex64>,
453    ) -> Result<()> {
454        let t1 = self.coherence_params.t1_times[qubit] * 1000.0; // Convert to ns
455        let t2 = self.coherence_params.t2_times[qubit] * 1000.0;
456
457        // Apply amplitude damping (T1 process)
458        let gamma_1 = time_ns / t1;
459        if gamma_1 > 0.0 {
460            self.apply_amplitude_damping(qubit, gamma_1, state)?;
461        }
462
463        // Apply dephasing (T2 process)
464        let gamma_2 = time_ns / t2;
465        if gamma_2 > 0.0 {
466            self.apply_dephasing(qubit, gamma_2, state)?;
467        }
468
469        Ok(())
470    }
471
472    /// Apply amplitude damping channel
473    fn apply_amplitude_damping(
474        &mut self,
475        qubit: usize,
476        gamma: f64,
477        state: &mut Array1<Complex64>,
478    ) -> Result<()> {
479        let state_size = state.len();
480        let qubit_mask = 1 << qubit;
481
482        let mut new_state = state.clone();
483
484        for i in 0..state_size {
485            if i & qubit_mask != 0 {
486                // |1⟩ state - apply damping
487                let j = i & !qubit_mask; // Corresponding |0⟩ state
488
489                if self.random() < gamma {
490                    // Decay |1⟩ → |0⟩
491                    new_state[j] += state[i];
492                    new_state[i] = Complex64::new(0.0, 0.0);
493                } else {
494                    // No decay, but renormalize
495                    new_state[i] *= (1.0 - gamma).sqrt();
496                }
497            }
498        }
499
500        *state = new_state;
501        Ok(())
502    }
503
504    /// Apply dephasing channel
505    fn apply_dephasing(
506        &mut self,
507        qubit: usize,
508        gamma: f64,
509        state: &mut Array1<Complex64>,
510    ) -> Result<()> {
511        let state_size = state.len();
512        let qubit_mask = 1 << qubit;
513
514        for i in 0..state_size {
515            if i & qubit_mask != 0 {
516                // Apply random phase to |1⟩ states
517                if self.random() < gamma {
518                    let phase = self.random() * 2.0 * std::f64::consts::PI;
519                    state[i] *= Complex64::new(0.0, phase).exp();
520                }
521            }
522        }
523
524        Ok(())
525    }
526
527    /// Apply cross-talk effects
528    fn apply_crosstalk(
529        &mut self,
530        active_qubits: &[usize],
531        state: &mut Array1<Complex64>,
532    ) -> Result<()> {
533        if !self.config.enable_correlated_noise {
534            return Ok(());
535        }
536
537        // Apply nearest-neighbor cross-talk
538        for &qubit in active_qubits {
539            let neighbors = self.config.topology.get_neighbors(qubit);
540
541            for neighbor in neighbors {
542                if !active_qubits.contains(&neighbor) {
543                    // Apply small rotation due to cross-talk
544                    let crosstalk_strength = 0.01; // 1% cross-talk
545                    let angle = crosstalk_strength * self.random() * std::f64::consts::PI;
546
547                    // Apply random rotation
548                    self.apply_small_rotation(neighbor, angle, state)?;
549                }
550            }
551        }
552
553        Ok(())
554    }
555
556    /// Apply small rotation due to cross-talk
557    fn apply_small_rotation(
558        &mut self,
559        qubit: usize,
560        angle: f64,
561        state: &mut Array1<Complex64>,
562    ) -> Result<()> {
563        let qubit_mask = 1 << qubit;
564        let state_size = state.len();
565
566        let cos_half = (angle / 2.0).cos();
567        let sin_half = (angle / 2.0).sin();
568
569        for i in 0..state_size {
570            if i & qubit_mask == 0 {
571                let j = i | qubit_mask;
572                if j < state_size {
573                    let amp_0 = state[i];
574                    let amp_1 = state[j];
575
576                    state[i] = cos_half * amp_0 - Complex64::new(0.0, sin_half) * amp_1;
577                    state[j] = cos_half * amp_1 - Complex64::new(0.0, sin_half) * amp_0;
578                }
579            }
580        }
581
582        Ok(())
583    }
584}
585
586impl DeviceNoiseModel for SuperconductingNoiseModel {
587    fn device_type(&self) -> DeviceType {
588        DeviceType::Superconducting
589    }
590
591    fn apply_single_qubit_noise(
592        &self,
593        qubit: usize,
594        gate_time: f64,
595        state: &mut Array1<Complex64>,
596    ) -> Result<()> {
597        let mut model = self.clone();
598
599        // Apply decoherence during gate
600        model.apply_decoherence(qubit, gate_time, state)?;
601
602        // Apply gate error
603        let error_rate = model.gate_errors.single_qubit[qubit];
604        if model.random() < error_rate {
605            // Apply random Pauli error
606            let pauli_error = (model.random() * 3.0) as usize;
607            model.apply_pauli_error(qubit, pauli_error, state)?;
608        }
609
610        // Apply cross-talk to neighboring qubits
611        model.apply_crosstalk(&[qubit], state)?;
612
613        Ok(())
614    }
615
616    fn apply_two_qubit_noise(
617        &self,
618        qubit1: usize,
619        qubit2: usize,
620        gate_time: f64,
621        state: &mut Array1<Complex64>,
622    ) -> Result<()> {
623        let mut model = self.clone();
624
625        // Apply decoherence to both qubits
626        model.apply_decoherence(qubit1, gate_time, state)?;
627        model.apply_decoherence(qubit2, gate_time, state)?;
628
629        // Apply two-qubit gate error
630        let error_rate = model
631            .gate_errors
632            .two_qubit
633            .get(&(qubit1, qubit2))
634            .or_else(|| model.gate_errors.two_qubit.get(&(qubit2, qubit1)))
635            .copied()
636            .unwrap_or(0.01); // Default 1% error
637
638        if model.random() < error_rate {
639            // Apply correlated two-qubit error
640            model.apply_two_qubit_error(qubit1, qubit2, state)?;
641        }
642
643        // Apply cross-talk
644        model.apply_crosstalk(&[qubit1, qubit2], state)?;
645
646        Ok(())
647    }
648
649    fn apply_measurement_noise(&self, qubit: usize, measurement_result: bool) -> Result<bool> {
650        let error_prob = if measurement_result {
651            self.readout_errors[[qubit, 1]] // |1⟩ → |0⟩ error
652        } else {
653            self.readout_errors[[qubit, 0]] // |0⟩ → |1⟩ error
654        };
655
656        if fastrand::f64() < error_prob {
657            Ok(!measurement_result) // Flip the result
658        } else {
659            Ok(measurement_result)
660        }
661    }
662
663    fn apply_idle_noise(
664        &self,
665        qubit: usize,
666        idle_time: f64,
667        state: &mut Array1<Complex64>,
668    ) -> Result<()> {
669        let mut model = self.clone();
670        model.apply_decoherence(qubit, idle_time, state)?;
671        Ok(())
672    }
673
674    fn update_time_dependent_parameters(&mut self, current_time: f64) -> Result<()> {
675        self.current_time = current_time;
676
677        if !self.config.enable_time_dependent_noise {
678            return Ok(());
679        }
680
681        // Update frequency drift if calibration data is available
682        if let Some(calibration) = &self.config.calibration_data {
683            for drift in &calibration.frequency_drift {
684                let frequency_shift = drift.drift_rate * current_time / 1e9; // Convert to Hz
685                                                                             // This would update the qubit frequency and affect gate fidelities
686                                                                             // For now, we just track the time
687            }
688        }
689
690        Ok(())
691    }
692}
693
694impl SuperconductingNoiseModel {
695    /// Apply Pauli error
696    fn apply_pauli_error(
697        &mut self,
698        qubit: usize,
699        pauli_type: usize,
700        state: &mut Array1<Complex64>,
701    ) -> Result<()> {
702        let qubit_mask = 1 << qubit;
703        let state_size = state.len();
704
705        match pauli_type {
706            0 => {
707                // Pauli X
708                for i in 0..state_size {
709                    let j = i ^ qubit_mask;
710                    if i < j {
711                        let temp = state[i];
712                        state[i] = state[j];
713                        state[j] = temp;
714                    }
715                }
716            }
717            1 => {
718                // Pauli Y
719                for i in 0..state_size {
720                    let j = i ^ qubit_mask;
721                    if i < j {
722                        let temp = state[i];
723                        state[i] = Complex64::new(0.0, 1.0) * state[j];
724                        state[j] = Complex64::new(0.0, -1.0) * temp;
725                    }
726                }
727            }
728            2 => {
729                // Pauli Z
730                for i in 0..state_size {
731                    if i & qubit_mask != 0 {
732                        state[i] = -state[i];
733                    }
734                }
735            }
736            _ => {} // Identity (no error)
737        }
738
739        Ok(())
740    }
741
742    /// Apply two-qubit correlated error
743    fn apply_two_qubit_error(
744        &mut self,
745        qubit1: usize,
746        qubit2: usize,
747        state: &mut Array1<Complex64>,
748    ) -> Result<()> {
749        // Apply correlated Pauli errors
750        let error1 = (self.random() * 4.0) as usize;
751        let error2 = (self.random() * 4.0) as usize;
752
753        if error1 < 3 {
754            self.apply_pauli_error(qubit1, error1, state)?;
755        }
756        if error2 < 3 {
757            self.apply_pauli_error(qubit2, error2, state)?;
758        }
759
760        Ok(())
761    }
762}
763
764/// Comprehensive device noise simulator
765pub struct DeviceNoiseSimulator {
766    /// Device noise model
767    noise_model: Box<dyn DeviceNoiseModel>,
768    /// SciRS2 backend for optimization
769    backend: Option<SciRS2Backend>,
770    /// Simulation statistics
771    stats: NoiseSimulationStats,
772    /// Current simulation time
773    current_time: f64,
774}
775
776/// Noise simulation statistics
777#[derive(Debug, Clone, Default, Serialize, Deserialize)]
778pub struct NoiseSimulationStats {
779    /// Total noise operations applied
780    pub total_noise_ops: usize,
781    /// Single-qubit noise events
782    pub single_qubit_noise_events: usize,
783    /// Two-qubit noise events
784    pub two_qubit_noise_events: usize,
785    /// Measurement errors
786    pub measurement_errors: usize,
787    /// Coherence events (T1/T2)
788    pub coherence_events: usize,
789    /// Cross-talk events
790    pub crosstalk_events: usize,
791    /// Total simulation time
792    pub total_simulation_time_ms: f64,
793}
794
795impl DeviceNoiseSimulator {
796    /// Create new device noise simulator
797    pub fn new(noise_model: Box<dyn DeviceNoiseModel>) -> Result<Self> {
798        Ok(Self {
799            noise_model,
800            backend: None,
801            stats: NoiseSimulationStats::default(),
802            current_time: 0.0,
803        })
804    }
805
806    /// Initialize with SciRS2 backend
807    pub fn with_backend(mut self) -> Result<Self> {
808        self.backend = Some(SciRS2Backend::new());
809        Ok(self)
810    }
811
812    /// Apply gate noise
813    pub fn apply_gate_noise(
814        &mut self,
815        gate_type: &str,
816        qubits: &[usize],
817        gate_time: f64,
818        state: &mut Array1<Complex64>,
819    ) -> Result<()> {
820        let start_time = std::time::Instant::now();
821
822        match qubits.len() {
823            1 => {
824                self.noise_model
825                    .apply_single_qubit_noise(qubits[0], gate_time, state)?;
826                self.stats.single_qubit_noise_events += 1;
827            }
828            2 => {
829                self.noise_model
830                    .apply_two_qubit_noise(qubits[0], qubits[1], gate_time, state)?;
831                self.stats.two_qubit_noise_events += 1;
832            }
833            _ => {
834                return Err(SimulatorError::UnsupportedOperation(format!(
835                    "Noise model for {}-qubit gates not implemented",
836                    qubits.len()
837                )));
838            }
839        }
840
841        self.current_time += gate_time;
842        self.noise_model
843            .update_time_dependent_parameters(self.current_time)?;
844
845        self.stats.total_noise_ops += 1;
846        self.stats.total_simulation_time_ms += start_time.elapsed().as_secs_f64() * 1000.0;
847
848        Ok(())
849    }
850
851    /// Apply measurement noise
852    pub fn apply_measurement_noise(
853        &mut self,
854        qubits: &[usize],
855        results: &mut [bool],
856    ) -> Result<()> {
857        for (i, &qubit) in qubits.iter().enumerate() {
858            let noisy_result = self
859                .noise_model
860                .apply_measurement_noise(qubit, results[i])?;
861            if noisy_result != results[i] {
862                self.stats.measurement_errors += 1;
863            }
864            results[i] = noisy_result;
865        }
866
867        Ok(())
868    }
869
870    /// Apply idle noise to all qubits
871    pub fn apply_idle_noise(
872        &mut self,
873        idle_time: f64,
874        state: &mut Array1<Complex64>,
875        num_qubits: usize,
876    ) -> Result<()> {
877        for qubit in 0..num_qubits {
878            self.noise_model.apply_idle_noise(qubit, idle_time, state)?;
879        }
880
881        self.current_time += idle_time;
882        self.noise_model
883            .update_time_dependent_parameters(self.current_time)?;
884
885        Ok(())
886    }
887
888    /// Get simulation statistics
889    pub fn get_stats(&self) -> &NoiseSimulationStats {
890        &self.stats
891    }
892
893    /// Reset simulation statistics
894    pub fn reset_stats(&mut self) {
895        self.stats = NoiseSimulationStats::default();
896        self.current_time = 0.0;
897    }
898}
899
900/// Device noise model utilities
901pub struct DeviceNoiseUtils;
902
903impl DeviceNoiseUtils {
904    /// Create IBM device model from real calibration data
905    pub fn create_ibm_device(
906        device_name: &str,
907        num_qubits: usize,
908    ) -> Result<SuperconductingNoiseModel> {
909        let topology = match device_name {
910            "ibm_washington" => DeviceTopology::heavy_hex(3),
911            "ibm_montreal" => DeviceTopology::linear_chain(num_qubits),
912            _ => DeviceTopology::square_lattice(4, 4),
913        };
914
915        let config = DeviceNoiseConfig {
916            device_type: DeviceType::Superconducting,
917            topology,
918            temperature: 0.015, // 15 mK
919            enable_correlated_noise: true,
920            enable_time_dependent_noise: true,
921            calibration_data: None,
922            random_seed: None,
923            real_time_adaptation: false,
924        };
925
926        SuperconductingNoiseModel::new(config)
927    }
928
929    /// Create Google Sycamore-like device
930    pub fn create_google_sycamore(num_qubits: usize) -> Result<SuperconductingNoiseModel> {
931        let width = (num_qubits as f64).sqrt().ceil() as usize;
932        let topology = DeviceTopology::square_lattice(width, width);
933
934        let config = DeviceNoiseConfig {
935            device_type: DeviceType::Superconducting,
936            topology,
937            temperature: 0.020, // 20 mK
938            enable_correlated_noise: true,
939            enable_time_dependent_noise: true,
940            calibration_data: None,
941            random_seed: None,
942            real_time_adaptation: false,
943        };
944
945        SuperconductingNoiseModel::new(config)
946    }
947
948    /// Create trapped ion device model
949    pub fn create_trapped_ion_device(num_ions: usize) -> Result<Box<dyn DeviceNoiseModel>> {
950        // Placeholder for trapped ion implementation
951        let topology = DeviceTopology::linear_chain(num_ions);
952        let config = DeviceNoiseConfig {
953            device_type: DeviceType::TrappedIon,
954            topology,
955            temperature: 1e-6, // μK range
956            enable_correlated_noise: true,
957            enable_time_dependent_noise: false,
958            calibration_data: None,
959            random_seed: None,
960            real_time_adaptation: false,
961        };
962
963        Ok(Box::new(SuperconductingNoiseModel::new(config)?))
964    }
965
966    /// Benchmark noise models
967    pub fn benchmark_noise_models() -> Result<NoiseBenchmarkResults> {
968        let mut results = NoiseBenchmarkResults::default();
969
970        let device_types = vec![
971            ("ibm_montreal", 27),
972            ("google_sycamore", 53),
973            ("trapped_ion", 20),
974        ];
975
976        for (device_name, num_qubits) in device_types {
977            let model = if device_name.starts_with("ibm") {
978                Box::new(Self::create_ibm_device(device_name, num_qubits)?)
979                    as Box<dyn DeviceNoiseModel>
980            } else if device_name.starts_with("google") {
981                Box::new(Self::create_google_sycamore(num_qubits)?) as Box<dyn DeviceNoiseModel>
982            } else {
983                Self::create_trapped_ion_device(num_qubits)?
984            };
985
986            let mut simulator = DeviceNoiseSimulator::new(model)?;
987            let mut state = Array1::from_elem(1 << num_qubits.min(10), Complex64::new(0.0, 0.0));
988            state[0] = Complex64::new(1.0, 0.0);
989
990            let start = std::time::Instant::now();
991
992            // Apply some gates with noise
993            for i in 0..num_qubits.min(10) {
994                simulator.apply_gate_noise("x", &[i], 20.0, &mut state)?;
995            }
996
997            let execution_time = start.elapsed().as_secs_f64() * 1000.0;
998            results
999                .device_benchmarks
1000                .insert(device_name.to_string(), execution_time);
1001        }
1002
1003        Ok(results)
1004    }
1005}
1006
1007/// Noise benchmark results
1008#[derive(Debug, Clone, Default)]
1009pub struct NoiseBenchmarkResults {
1010    /// Execution times by device type
1011    pub device_benchmarks: HashMap<String, f64>,
1012}
1013
1014#[cfg(test)]
1015mod tests {
1016    use super::*;
1017    use approx::assert_abs_diff_eq;
1018
1019    #[test]
1020    fn test_device_topology_linear_chain() {
1021        let topology = DeviceTopology::linear_chain(5);
1022        assert_eq!(topology.num_qubits, 5);
1023        assert!(topology.connectivity[[0, 1]]);
1024        assert!(!topology.connectivity[[0, 2]]);
1025        assert_eq!(topology.get_neighbors(0), vec![1]);
1026        assert_eq!(topology.get_neighbors(2), vec![1, 3]);
1027    }
1028
1029    #[test]
1030    fn test_device_topology_square_lattice() {
1031        let topology = DeviceTopology::square_lattice(3, 3);
1032        assert_eq!(topology.num_qubits, 9);
1033        assert!(topology.connectivity[[0, 1]]); // Horizontal connection
1034        assert!(topology.connectivity[[0, 3]]); // Vertical connection
1035        assert!(!topology.connectivity[[0, 4]]); // Diagonal not connected
1036    }
1037
1038    #[test]
1039    fn test_superconducting_noise_model() {
1040        let config = DeviceNoiseConfig {
1041            device_type: DeviceType::Superconducting,
1042            topology: DeviceTopology::linear_chain(3),
1043            ..Default::default()
1044        };
1045
1046        let model = SuperconductingNoiseModel::new(config).unwrap();
1047        assert_eq!(model.device_type(), DeviceType::Superconducting);
1048        assert_eq!(model.coherence_params.t1_times.len(), 3);
1049    }
1050
1051    #[test]
1052    fn test_device_noise_simulator() {
1053        let config = DeviceNoiseConfig {
1054            device_type: DeviceType::Superconducting,
1055            topology: DeviceTopology::linear_chain(2),
1056            random_seed: Some(12345),
1057            ..Default::default()
1058        };
1059
1060        let model = SuperconductingNoiseModel::new(config).unwrap();
1061        let mut simulator = DeviceNoiseSimulator::new(Box::new(model)).unwrap();
1062
1063        let mut state = Array1::from_elem(4, Complex64::new(0.0, 0.0));
1064        state[0] = Complex64::new(1.0, 0.0);
1065
1066        // Apply single-qubit noise
1067        let result = simulator.apply_gate_noise("x", &[0], 20.0, &mut state);
1068        assert!(result.is_ok());
1069        assert_eq!(simulator.stats.single_qubit_noise_events, 1);
1070
1071        // Apply two-qubit noise
1072        let result = simulator.apply_gate_noise("cnot", &[0, 1], 100.0, &mut state);
1073        assert!(result.is_ok());
1074        assert_eq!(simulator.stats.two_qubit_noise_events, 1);
1075    }
1076
1077    #[test]
1078    fn test_measurement_noise() {
1079        let config = DeviceNoiseConfig {
1080            device_type: DeviceType::Superconducting,
1081            topology: DeviceTopology::linear_chain(1),
1082            random_seed: Some(12345),
1083            ..Default::default()
1084        };
1085
1086        let model = SuperconductingNoiseModel::new(config).unwrap();
1087        let mut simulator = DeviceNoiseSimulator::new(Box::new(model)).unwrap();
1088
1089        let mut results = vec![false, true, false];
1090        simulator
1091            .apply_measurement_noise(&[0, 0, 0], &mut results)
1092            .unwrap();
1093
1094        // Some results might have flipped due to readout errors
1095        // We can't predict exactly which due to randomness, but the function should work
1096    }
1097
1098    #[test]
1099    fn test_gate_times_default() {
1100        let gate_times = GateTimes::default();
1101        assert_eq!(gate_times.single_qubit, 20.0);
1102        assert_eq!(gate_times.two_qubit, 100.0);
1103        assert_eq!(gate_times.measurement, 1000.0);
1104    }
1105
1106    #[test]
1107    fn test_device_noise_utils() {
1108        let result = DeviceNoiseUtils::create_ibm_device("ibm_montreal", 5);
1109        assert!(result.is_ok());
1110
1111        let result = DeviceNoiseUtils::create_google_sycamore(9);
1112        assert!(result.is_ok());
1113
1114        let result = DeviceNoiseUtils::create_trapped_ion_device(5);
1115        assert!(result.is_ok());
1116    }
1117
1118    #[test]
1119    fn test_calibration_data_integration() {
1120        let calibration = CalibrationData {
1121            device_id: "test_device".to_string(),
1122            timestamp: std::time::SystemTime::now(),
1123            single_qubit_fidelities: vec![0.999, 0.998, 0.999],
1124            two_qubit_fidelities: HashMap::new(),
1125            t1_times: vec![50.0, 45.0, 55.0],
1126            t2_times: vec![25.0, 20.0, 30.0],
1127            readout_fidelities: vec![0.95, 0.96, 0.94],
1128            gate_times: GateTimes::default(),
1129            crosstalk_matrices: HashMap::new(),
1130            frequency_drift: Vec::new(),
1131        };
1132
1133        let config = DeviceNoiseConfig {
1134            device_type: DeviceType::Superconducting,
1135            topology: DeviceTopology::linear_chain(3),
1136            calibration_data: Some(calibration.clone()),
1137            ..Default::default()
1138        };
1139
1140        let model = SuperconductingNoiseModel::from_calibration_data(config, &calibration).unwrap();
1141        assert_eq!(model.coherence_params.t1_times, calibration.t1_times);
1142        assert_eq!(model.coherence_params.t2_times, calibration.t2_times);
1143    }
1144
1145    #[test]
1146    fn test_temperature_scaling() {
1147        let scaling_15mk = SuperconductingNoiseModel::calculate_temperature_scaling(0.015);
1148        let scaling_50mk = SuperconductingNoiseModel::calculate_temperature_scaling(0.050);
1149
1150        // Higher temperature should give higher thermal population
1151        assert!(scaling_50mk > scaling_15mk);
1152        assert!(scaling_15mk > 0.0);
1153        assert!(scaling_15mk < 1.0);
1154    }
1155}