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