quantrs2_device/
noise_scirs2_characterization.rs

1//! Noise Characterization with SciRS2 Statistics
2//!
3//! This module provides comprehensive quantum noise characterization using SciRS2's
4//! advanced statistical analysis capabilities for error modeling, noise parameter
5//! estimation, and hardware performance characterization.
6
7use crate::{DeviceError, DeviceResult};
8use scirs2_core::ndarray::{Array1, Array2};
9use scirs2_core::random::prelude::*;
10use scirs2_core::Complex64;
11use scirs2_stats::distributions; // For creating distributions
12use scirs2_stats::{mean, std}; // Descriptive statistics at root level
13
14/// Noise characterization configuration
15#[derive(Debug, Clone)]
16pub struct NoiseCharacterizationConfig {
17    /// Number of samples for statistical analysis
18    pub num_samples: usize,
19    /// Confidence level for statistical tests (e.g., 0.95 for 95%)
20    pub confidence_level: f64,
21    /// Enable advanced statistical analysis
22    pub advanced_analysis: bool,
23    /// Minimum sample size for reliable statistics
24    pub min_sample_size: usize,
25}
26
27impl Default for NoiseCharacterizationConfig {
28    fn default() -> Self {
29        Self {
30            num_samples: 10000,
31            confidence_level: 0.95,
32            advanced_analysis: true,
33            min_sample_size: 100,
34        }
35    }
36}
37
38/// Noise model types
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum NoiseModelType {
41    /// Depolarizing noise channel
42    Depolarizing,
43    /// Amplitude damping (T1 decay)
44    AmplitudeDamping,
45    /// Phase damping (T2 dephasing)
46    PhaseDamping,
47    /// Thermal noise
48    Thermal,
49    /// Readout error
50    ReadoutError,
51    /// Crosstalk noise
52    Crosstalk,
53}
54
55/// Noise characterization result
56#[derive(Debug, Clone)]
57pub struct NoiseCharacterizationResult {
58    /// Estimated noise parameters
59    pub noise_parameters: NoiseParameters,
60    /// Statistical confidence intervals
61    pub confidence_intervals: ConfidenceIntervals,
62    /// Goodness-of-fit metrics
63    pub fit_quality: FitQuality,
64    /// Correlation analysis
65    pub correlations: CorrelationAnalysis,
66}
67
68/// Noise parameters estimated from data
69#[derive(Debug, Clone)]
70pub struct NoiseParameters {
71    /// Mean error rate
72    pub mean_error_rate: f64,
73    /// Standard deviation of error rate
74    pub std_error_rate: f64,
75    /// T1 relaxation time (microseconds)
76    pub t1_time: Option<f64>,
77    /// T2 dephasing time (microseconds)
78    pub t2_time: Option<f64>,
79    /// Readout fidelity
80    pub readout_fidelity: f64,
81    /// Model-specific parameters
82    pub model_params: Vec<f64>,
83}
84
85/// Statistical confidence intervals
86#[derive(Debug, Clone)]
87pub struct ConfidenceIntervals {
88    /// Confidence level (e.g., 0.95)
89    pub confidence_level: f64,
90    /// Lower bound for mean error rate
91    pub error_rate_lower: f64,
92    /// Upper bound for mean error rate
93    pub error_rate_upper: f64,
94    /// T1 confidence interval
95    pub t1_interval: Option<(f64, f64)>,
96    /// T2 confidence interval
97    pub t2_interval: Option<(f64, f64)>,
98}
99
100/// Goodness-of-fit quality metrics
101#[derive(Debug, Clone)]
102pub struct FitQuality {
103    /// Chi-squared statistic
104    pub chi_squared: f64,
105    /// P-value from chi-squared test
106    pub p_value: f64,
107    /// R-squared (coefficient of determination)
108    pub r_squared: f64,
109    /// Root mean square error
110    pub rmse: f64,
111}
112
113/// Correlation analysis between noise sources
114#[derive(Debug, Clone)]
115pub struct CorrelationAnalysis {
116    /// Pearson correlation coefficients
117    pub correlation_matrix: Array2<f64>,
118    /// Covariance matrix
119    pub covariance_matrix: Array2<f64>,
120    /// Significant correlations (above threshold)
121    pub significant_correlations: Vec<(usize, usize, f64)>,
122}
123
124/// Noise characterizer using SciRS2 statistics
125pub struct NoiseCharacterizer {
126    config: NoiseCharacterizationConfig,
127    rng: StdRng,
128}
129
130impl NoiseCharacterizer {
131    /// Create a new noise characterizer
132    pub fn new(config: NoiseCharacterizationConfig) -> Self {
133        Self {
134            config,
135            rng: StdRng::seed_from_u64(42),
136        }
137    }
138
139    /// Create characterizer with default configuration
140    pub fn default() -> Self {
141        Self::new(NoiseCharacterizationConfig::default())
142    }
143
144    /// Characterize noise from measurement data using SciRS2 statistics
145    ///
146    /// # Arguments
147    /// * `measurement_data` - Array of measurement outcomes (0.0 or 1.0 for qubits)
148    /// * `expected_data` - Expected ideal outcomes
149    /// * `noise_model` - Type of noise model to fit
150    ///
151    /// # Returns
152    /// Comprehensive noise characterization with statistical analysis
153    pub fn characterize_noise(
154        &mut self,
155        measurement_data: &Array1<f64>,
156        expected_data: &Array1<f64>,
157        noise_model: NoiseModelType,
158    ) -> DeviceResult<NoiseCharacterizationResult> {
159        // Validate input data
160        if measurement_data.len() < self.config.min_sample_size {
161            return Err(DeviceError::InvalidInput(format!(
162                "Insufficient samples: {} < minimum {}",
163                measurement_data.len(),
164                self.config.min_sample_size
165            )));
166        }
167
168        if measurement_data.len() != expected_data.len() {
169            return Err(DeviceError::InvalidInput(
170                "Measurement and expected data must have same length".to_string(),
171            ));
172        }
173
174        // Compute error rates
175        let errors: Array1<f64> =
176            (measurement_data - expected_data).mapv(|x| if x.abs() > 0.5 { 1.0 } else { 0.0 });
177
178        let mean_error = mean(&errors.view())?;
179        let std_error = std(&errors.view(), 1, None)?; // Sample std dev (ddof=1)
180
181        // Estimate noise parameters based on model type
182        let noise_params =
183            self.estimate_noise_parameters(measurement_data, expected_data, &errors, noise_model)?;
184
185        // Compute confidence intervals using SciRS2 statistics
186        let confidence_intervals = self.compute_confidence_intervals(&errors, &noise_params)?;
187
188        // Assess goodness-of-fit
189        let fit_quality =
190            self.assess_fit_quality(measurement_data, expected_data, &noise_params, noise_model)?;
191
192        // Perform correlation analysis if advanced analysis is enabled
193        let correlations = if self.config.advanced_analysis {
194            self.analyze_correlations(measurement_data)?
195        } else {
196            CorrelationAnalysis {
197                correlation_matrix: Array2::zeros((1, 1)),
198                covariance_matrix: Array2::zeros((1, 1)),
199                significant_correlations: Vec::new(),
200            }
201        };
202
203        Ok(NoiseCharacterizationResult {
204            noise_parameters: noise_params,
205            confidence_intervals,
206            fit_quality,
207            correlations,
208        })
209    }
210
211    /// Estimate noise parameters using maximum likelihood estimation
212    fn estimate_noise_parameters(
213        &self,
214        measurement_data: &Array1<f64>,
215        expected_data: &Array1<f64>,
216        errors: &Array1<f64>,
217        noise_model: NoiseModelType,
218    ) -> DeviceResult<NoiseParameters> {
219        let mean_error = mean(&errors.view())?;
220        let std_error = std(&errors.view(), 1, None)?;
221
222        // Compute readout fidelity
223        let correct_measurements = errors.iter().filter(|&&e| e < 0.5).count();
224        let readout_fidelity = correct_measurements as f64 / errors.len() as f64;
225
226        // Model-specific parameter estimation
227        let (t1_time, t2_time, model_params) = match noise_model {
228            NoiseModelType::Depolarizing => {
229                // Depolarizing probability p
230                let p = mean_error;
231                (None, None, vec![p])
232            }
233            NoiseModelType::AmplitudeDamping => {
234                // Estimate T1 from amplitude decay
235                let t1 = self.estimate_t1_time(measurement_data)?;
236                (Some(t1), None, vec![t1])
237            }
238            NoiseModelType::PhaseDamping => {
239                // Estimate T2 from phase decay
240                let t2 = self.estimate_t2_time(measurement_data)?;
241                (None, Some(t2), vec![t2])
242            }
243            NoiseModelType::Thermal => {
244                // Thermal excitation probability
245                let thermal_prob = mean_error;
246                (None, None, vec![thermal_prob])
247            }
248            NoiseModelType::ReadoutError => {
249                // Assignment error probabilities
250                let p_0_given_1 =
251                    self.estimate_readout_error(measurement_data, expected_data, 0)?;
252                let p_1_given_0 =
253                    self.estimate_readout_error(measurement_data, expected_data, 1)?;
254                (None, None, vec![p_0_given_1, p_1_given_0])
255            }
256            NoiseModelType::Crosstalk => {
257                // Crosstalk strength
258                let crosstalk_strength = std_error;
259                (None, None, vec![crosstalk_strength])
260            }
261        };
262
263        Ok(NoiseParameters {
264            mean_error_rate: mean_error,
265            std_error_rate: std_error,
266            t1_time,
267            t2_time,
268            readout_fidelity,
269            model_params,
270        })
271    }
272
273    /// Estimate T1 relaxation time from exponential decay fit
274    fn estimate_t1_time(&self, measurement_data: &Array1<f64>) -> DeviceResult<f64> {
275        // Simple exponential decay fit: P(t) = exp(-t/T1)
276        // Using linear regression on log-transformed data
277        let n = measurement_data.len();
278        if n < 10 {
279            return Ok(30.0); // Default T1 = 30 microseconds
280        }
281
282        // Time points (assuming uniform spacing)
283        let times: Array1<f64> = Array1::from_shape_fn(n, |i| i as f64);
284
285        // Log-transform (with small offset to avoid log(0))
286        let log_probs: Array1<f64> = measurement_data.mapv(|p| (p.max(0.01)).ln());
287
288        // Simple linear fit: log(P) = -t/T1 + const
289        let mean_t = mean(&times.view())?;
290        let mean_log_p = mean(&log_probs.view())?;
291
292        let numerator: f64 = times
293            .iter()
294            .zip(log_probs.iter())
295            .map(|(&t, &lp)| (t - mean_t) * (lp - mean_log_p))
296            .sum();
297
298        let denominator: f64 = times.iter().map(|&t| (t - mean_t).powi(2)).sum();
299
300        if denominator.abs() < 1e-10 {
301            return Ok(30.0);
302        }
303
304        let slope = numerator / denominator;
305        let t1 = -1.0 / slope;
306
307        // Clamp to reasonable range (1-1000 microseconds)
308        Ok(t1.clamp(1.0, 1000.0))
309    }
310
311    /// Estimate T2 dephasing time
312    fn estimate_t2_time(&self, measurement_data: &Array1<f64>) -> DeviceResult<f64> {
313        // Similar to T1 estimation but for phase coherence
314        // T2 <= 2*T1 typically
315        let t2_estimate = self.estimate_t1_time(measurement_data)? * 0.8;
316        Ok(t2_estimate.clamp(1.0, 500.0))
317    }
318
319    /// Estimate readout error probability
320    fn estimate_readout_error(
321        &self,
322        measurement_data: &Array1<f64>,
323        expected_data: &Array1<f64>,
324        state: usize,
325    ) -> DeviceResult<f64> {
326        let expected_state = state as f64;
327        let count_expected: usize = expected_data
328            .iter()
329            .filter(|&&e| (e - expected_state).abs() < 0.5)
330            .count();
331
332        if count_expected == 0 {
333            return Ok(0.01); // Default 1% error
334        }
335
336        let count_errors: usize = expected_data
337            .iter()
338            .zip(measurement_data.iter())
339            .filter(|(&e, &m)| (e - expected_state).abs() < 0.5 && (m - expected_state).abs() > 0.5)
340            .count();
341
342        Ok((count_errors as f64 / count_expected as f64).clamp(0.0, 0.5))
343    }
344
345    /// Compute confidence intervals using t-distribution
346    fn compute_confidence_intervals(
347        &self,
348        errors: &Array1<f64>,
349        noise_params: &NoiseParameters,
350    ) -> DeviceResult<ConfidenceIntervals> {
351        let n = errors.len() as f64;
352        let mean_err = noise_params.mean_error_rate;
353        let std_err = noise_params.std_error_rate;
354
355        // Compute critical value for t-distribution (approximate with z for large n)
356        let z_critical = if self.config.confidence_level >= 0.99 {
357            2.576 // 99% confidence
358        } else if self.config.confidence_level >= 0.95 {
359            1.96 // 95% confidence
360        } else {
361            1.645 // 90% confidence
362        };
363
364        let margin_of_error = z_critical * std_err / n.sqrt();
365
366        Ok(ConfidenceIntervals {
367            confidence_level: self.config.confidence_level,
368            error_rate_lower: (mean_err - margin_of_error).max(0.0),
369            error_rate_upper: (mean_err + margin_of_error).min(1.0),
370            t1_interval: noise_params.t1_time.map(|t1| {
371                let margin = 0.1 * t1; // 10% margin
372                (t1 - margin, t1 + margin)
373            }),
374            t2_interval: noise_params.t2_time.map(|t2| {
375                let margin = 0.1 * t2;
376                (t2 - margin, t2 + margin)
377            }),
378        })
379    }
380
381    /// Assess goodness-of-fit using chi-squared test and R-squared
382    fn assess_fit_quality(
383        &self,
384        measurement_data: &Array1<f64>,
385        expected_data: &Array1<f64>,
386        noise_params: &NoiseParameters,
387        noise_model: NoiseModelType,
388    ) -> DeviceResult<FitQuality> {
389        // Compute residuals
390        let residuals: Array1<f64> = measurement_data - expected_data;
391
392        // Root mean square error
393        let rmse = (residuals.mapv(|r| r * r).mean().unwrap_or(0.0)).sqrt();
394
395        // R-squared calculation
396        let mean_measured = mean(&measurement_data.view())?;
397        let ss_tot: f64 = measurement_data
398            .iter()
399            .map(|&y| (y - mean_measured).powi(2))
400            .sum();
401
402        let ss_res: f64 = residuals.iter().map(|&r| r.powi(2)).sum();
403
404        let r_squared = if ss_tot > 1e-10 {
405            1.0 - (ss_res / ss_tot)
406        } else {
407            0.0
408        };
409
410        // Chi-squared statistic (simplified)
411        let chi_squared = ss_res / noise_params.std_error_rate.max(0.01).powi(2);
412
413        // Approximate p-value (would use proper chi-squared distribution in production)
414        let p_value = (-chi_squared / (2.0 * measurement_data.len() as f64)).exp();
415
416        Ok(FitQuality {
417            chi_squared,
418            p_value,
419            r_squared: r_squared.clamp(0.0, 1.0),
420            rmse,
421        })
422    }
423
424    /// Analyze correlations between noise sources
425    fn analyze_correlations(
426        &self,
427        measurement_data: &Array1<f64>,
428    ) -> DeviceResult<CorrelationAnalysis> {
429        // For demonstration, create chunks to analyze correlation patterns
430        let chunk_size = (measurement_data.len() / 5).max(10);
431        let num_chunks = measurement_data.len() / chunk_size;
432
433        if num_chunks < 2 {
434            return Ok(CorrelationAnalysis {
435                correlation_matrix: Array2::eye(1),
436                covariance_matrix: Array2::zeros((1, 1)),
437                significant_correlations: Vec::new(),
438            });
439        }
440
441        // Create data matrix (each row is a chunk)
442        let mut data_matrix = Array2::zeros((num_chunks, chunk_size));
443        for i in 0..num_chunks {
444            let start = i * chunk_size;
445            let end = (start + chunk_size).min(measurement_data.len());
446            let chunk_len = end - start;
447            for j in 0..chunk_len {
448                data_matrix[[i, j]] = measurement_data[start + j];
449            }
450        }
451
452        // Compute covariance matrix manually (simplified)
453        let corr_matrix = self.compute_covariance_matrix(&data_matrix)?;
454
455        // Find significant correlations (|r| > 0.5)
456        let mut significant = Vec::new();
457        for i in 0..num_chunks {
458            for j in (i + 1)..num_chunks {
459                let corr = if i < corr_matrix.nrows() && j < corr_matrix.ncols() {
460                    corr_matrix[[i, j]]
461                } else {
462                    0.0
463                };
464                if corr.abs() > 0.5 {
465                    significant.push((i, j, corr));
466                }
467            }
468        }
469
470        Ok(CorrelationAnalysis {
471            correlation_matrix: corr_matrix.clone(),
472            covariance_matrix: corr_matrix,
473            significant_correlations: significant,
474        })
475    }
476
477    /// Compute covariance matrix manually
478    fn compute_covariance_matrix(&self, data: &Array2<f64>) -> DeviceResult<Array2<f64>> {
479        let n = data.nrows();
480        let m = data.ncols();
481
482        if n < 2 {
483            return Ok(Array2::eye(1));
484        }
485
486        // Compute means for each column
487        let mut means = Array1::zeros(m);
488        for j in 0..m {
489            let col_sum: f64 = (0..n).map(|i| data[[i, j]]).sum();
490            means[j] = col_sum / n as f64;
491        }
492
493        // Compute covariance matrix
494        let mut cov = Array2::zeros((n, n));
495        for i in 0..n {
496            for j in 0..n {
497                let mut sum = 0.0;
498                for k in 0..m {
499                    sum += (data[[i, k]] - means[k]) * (data[[j, k]] - means[k]);
500                }
501                cov[[i, j]] = sum / (m - 1) as f64;
502            }
503        }
504
505        Ok(cov)
506    }
507
508    /// Generate noise samples for testing/simulation
509    pub fn generate_noise_samples(
510        &mut self,
511        noise_model: NoiseModelType,
512        num_samples: usize,
513        noise_strength: f64,
514    ) -> Array1<f64> {
515        match noise_model {
516            NoiseModelType::Depolarizing => {
517                // Bernoulli noise
518                Array1::from_shape_fn(num_samples, |_| {
519                    if self.rng.gen::<f64>() < noise_strength {
520                        1.0
521                    } else {
522                        0.0
523                    }
524                })
525            }
526            NoiseModelType::AmplitudeDamping | NoiseModelType::PhaseDamping => {
527                // Exponential decay with Gaussian noise
528                Array1::from_shape_fn(num_samples, |i| {
529                    let decay = (-(i as f64) / 50.0).exp();
530                    let gaussian_noise =
531                        self.rng.gen::<f64>() * noise_strength - noise_strength / 2.0;
532                    (decay + gaussian_noise).clamp(0.0, 1.0)
533                })
534            }
535            _ => {
536                // Gaussian noise
537                Array1::from_shape_fn(num_samples, |_| {
538                    (self.rng.gen::<f64>() * noise_strength)
539                        .abs()
540                        .clamp(0.0, 1.0)
541                })
542            }
543        }
544    }
545}
546
547#[cfg(test)]
548mod tests {
549    use super::*;
550
551    #[test]
552    fn test_noise_characterizer_creation() {
553        let config = NoiseCharacterizationConfig::default();
554        let characterizer = NoiseCharacterizer::new(config);
555        assert_eq!(characterizer.config.num_samples, 10000);
556    }
557
558    #[test]
559    fn test_depolarizing_noise_characterization() {
560        let config = NoiseCharacterizationConfig {
561            num_samples: 1000,
562            confidence_level: 0.95,
563            advanced_analysis: false,
564            min_sample_size: 100,
565        };
566
567        let mut characterizer = NoiseCharacterizer::new(config);
568
569        // Generate test data with 10% error rate
570        let expected = Array1::zeros(1000);
571        let mut measurement = expected.clone();
572        for i in 0..100 {
573            measurement[i * 10] = 1.0; // 10% errors
574        }
575
576        let result =
577            characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
578
579        assert!(result.is_ok());
580        let result = result.expect("Characterization failed");
581
582        // Should detect ~10% error rate
583        assert!((result.noise_parameters.mean_error_rate - 0.1).abs() < 0.05);
584        assert!(result.confidence_intervals.error_rate_lower < 0.1);
585        assert!(result.confidence_intervals.error_rate_upper > 0.1);
586    }
587
588    #[test]
589    fn test_amplitude_damping_characterization() {
590        let config = NoiseCharacterizationConfig::default();
591        let mut characterizer = NoiseCharacterizer::new(config);
592
593        // Generate exponential decay data
594        let n = 500;
595        let expected = Array1::ones(n);
596        let measurement = Array1::from_shape_fn(n, |i| (-(i as f64) / 50.0).exp());
597
598        let result = characterizer.characterize_noise(
599            &measurement,
600            &expected,
601            NoiseModelType::AmplitudeDamping,
602        );
603
604        assert!(result.is_ok());
605        let result = result.expect("Characterization failed");
606
607        // Should estimate T1 time
608        assert!(result.noise_parameters.t1_time.is_some());
609        let t1 = result.noise_parameters.t1_time.expect("No T1 estimate");
610        assert!(t1 > 1.0 && t1 < 1000.0);
611    }
612
613    #[test]
614    fn test_readout_error_characterization() {
615        let config = NoiseCharacterizationConfig::default();
616        let mut characterizer = NoiseCharacterizer::new(config);
617
618        // 5% readout error: measure 1 when expecting 0
619        let n = 1000;
620        let mut expected = Array1::zeros(n);
621        let mut measurement = expected.clone();
622
623        for i in 0..50 {
624            expected[i * 20] = 0.0;
625            measurement[i * 20] = 1.0; // Readout error
626        }
627
628        let result =
629            characterizer.characterize_noise(&measurement, &expected, NoiseModelType::ReadoutError);
630
631        assert!(result.is_ok());
632        let result = result.expect("Characterization failed");
633        assert_eq!(result.noise_parameters.model_params.len(), 2);
634    }
635
636    #[test]
637    fn test_confidence_intervals() {
638        let config = NoiseCharacterizationConfig {
639            confidence_level: 0.95,
640            ..Default::default()
641        };
642        let mut characterizer = NoiseCharacterizer::new(config);
643
644        let expected = Array1::zeros(1000);
645        let measurement = Array1::from_shape_fn(1000, |i| if i % 10 == 0 { 1.0 } else { 0.0 });
646
647        let result =
648            characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
649
650        assert!(result.is_ok());
651        let result = result.expect("Characterization failed");
652
653        assert_eq!(result.confidence_intervals.confidence_level, 0.95);
654        assert!(result.confidence_intervals.error_rate_lower >= 0.0);
655        assert!(result.confidence_intervals.error_rate_upper <= 1.0);
656        assert!(
657            result.confidence_intervals.error_rate_lower
658                < result.confidence_intervals.error_rate_upper
659        );
660    }
661
662    #[test]
663    fn test_fit_quality_metrics() {
664        let config = NoiseCharacterizationConfig::default();
665        let mut characterizer = NoiseCharacterizer::new(config);
666
667        let expected = Array1::zeros(500);
668        let measurement = expected.clone();
669
670        let result =
671            characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
672
673        assert!(result.is_ok());
674        let result = result.expect("Characterization failed");
675
676        // Perfect fit should have high R-squared
677        assert!(result.fit_quality.r_squared >= 0.0);
678        assert!(result.fit_quality.rmse >= 0.0);
679    }
680
681    #[test]
682    fn test_noise_sample_generation() {
683        let config = NoiseCharacterizationConfig::default();
684        let mut characterizer = NoiseCharacterizer::new(config);
685
686        let samples = characterizer.generate_noise_samples(NoiseModelType::Depolarizing, 1000, 0.1);
687
688        assert_eq!(samples.len(), 1000);
689
690        // All samples should be in [0, 1]
691        for &s in samples.iter() {
692            assert!((0.0..=1.0).contains(&s));
693        }
694    }
695
696    #[test]
697    fn test_insufficient_samples_error() {
698        let config = NoiseCharacterizationConfig {
699            min_sample_size: 100,
700            ..Default::default()
701        };
702        let mut characterizer = NoiseCharacterizer::new(config);
703
704        let expected = Array1::zeros(50);
705        let measurement = expected.clone();
706
707        let result =
708            characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
709
710        assert!(result.is_err());
711    }
712}