use crate::{DeviceError, DeviceResult};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::random::prelude::*;
use scirs2_core::Complex64;
use scirs2_stats::distributions; use scirs2_stats::{mean, std};
#[derive(Debug, Clone)]
pub struct NoiseCharacterizationConfig {
pub num_samples: usize,
pub confidence_level: f64,
pub advanced_analysis: bool,
pub min_sample_size: usize,
}
impl Default for NoiseCharacterizationConfig {
fn default() -> Self {
Self {
num_samples: 10000,
confidence_level: 0.95,
advanced_analysis: true,
min_sample_size: 100,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoiseModelType {
Depolarizing,
AmplitudeDamping,
PhaseDamping,
Thermal,
ReadoutError,
Crosstalk,
}
#[derive(Debug, Clone)]
pub struct NoiseCharacterizationResult {
pub noise_parameters: NoiseParameters,
pub confidence_intervals: ConfidenceIntervals,
pub fit_quality: FitQuality,
pub correlations: CorrelationAnalysis,
}
#[derive(Debug, Clone)]
pub struct NoiseParameters {
pub mean_error_rate: f64,
pub std_error_rate: f64,
pub t1_time: Option<f64>,
pub t2_time: Option<f64>,
pub readout_fidelity: f64,
pub model_params: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct ConfidenceIntervals {
pub confidence_level: f64,
pub error_rate_lower: f64,
pub error_rate_upper: f64,
pub t1_interval: Option<(f64, f64)>,
pub t2_interval: Option<(f64, f64)>,
}
#[derive(Debug, Clone)]
pub struct FitQuality {
pub chi_squared: f64,
pub p_value: f64,
pub r_squared: f64,
pub rmse: f64,
}
#[derive(Debug, Clone)]
pub struct CorrelationAnalysis {
pub correlation_matrix: Array2<f64>,
pub covariance_matrix: Array2<f64>,
pub significant_correlations: Vec<(usize, usize, f64)>,
}
pub struct NoiseCharacterizer {
config: NoiseCharacterizationConfig,
rng: StdRng,
}
impl NoiseCharacterizer {
pub fn new(config: NoiseCharacterizationConfig) -> Self {
Self {
config,
rng: StdRng::seed_from_u64(42),
}
}
pub fn default() -> Self {
Self::new(NoiseCharacterizationConfig::default())
}
pub fn characterize_noise(
&mut self,
measurement_data: &Array1<f64>,
expected_data: &Array1<f64>,
noise_model: NoiseModelType,
) -> DeviceResult<NoiseCharacterizationResult> {
if measurement_data.len() < self.config.min_sample_size {
return Err(DeviceError::InvalidInput(format!(
"Insufficient samples: {} < minimum {}",
measurement_data.len(),
self.config.min_sample_size
)));
}
if measurement_data.len() != expected_data.len() {
return Err(DeviceError::InvalidInput(
"Measurement and expected data must have same length".to_string(),
));
}
let errors: Array1<f64> =
(measurement_data - expected_data).mapv(|x| if x.abs() > 0.5 { 1.0 } else { 0.0 });
let mean_error = mean(&errors.view())?;
let std_error = std(&errors.view(), 1, None)?;
let noise_params =
self.estimate_noise_parameters(measurement_data, expected_data, &errors, noise_model)?;
let confidence_intervals = self.compute_confidence_intervals(&errors, &noise_params)?;
let fit_quality =
self.assess_fit_quality(measurement_data, expected_data, &noise_params, noise_model)?;
let correlations = if self.config.advanced_analysis {
self.analyze_correlations(measurement_data)?
} else {
CorrelationAnalysis {
correlation_matrix: Array2::zeros((1, 1)),
covariance_matrix: Array2::zeros((1, 1)),
significant_correlations: Vec::new(),
}
};
Ok(NoiseCharacterizationResult {
noise_parameters: noise_params,
confidence_intervals,
fit_quality,
correlations,
})
}
fn estimate_noise_parameters(
&self,
measurement_data: &Array1<f64>,
expected_data: &Array1<f64>,
errors: &Array1<f64>,
noise_model: NoiseModelType,
) -> DeviceResult<NoiseParameters> {
let mean_error = mean(&errors.view())?;
let std_error = std(&errors.view(), 1, None)?;
let correct_measurements = errors.iter().filter(|&&e| e < 0.5).count();
let readout_fidelity = correct_measurements as f64 / errors.len() as f64;
let (t1_time, t2_time, model_params) = match noise_model {
NoiseModelType::Depolarizing => {
let p = mean_error;
(None, None, vec![p])
}
NoiseModelType::AmplitudeDamping => {
let t1 = self.estimate_t1_time(measurement_data)?;
(Some(t1), None, vec![t1])
}
NoiseModelType::PhaseDamping => {
let t2 = self.estimate_t2_time(measurement_data)?;
(None, Some(t2), vec![t2])
}
NoiseModelType::Thermal => {
let thermal_prob = mean_error;
(None, None, vec![thermal_prob])
}
NoiseModelType::ReadoutError => {
let p_0_given_1 =
self.estimate_readout_error(measurement_data, expected_data, 0)?;
let p_1_given_0 =
self.estimate_readout_error(measurement_data, expected_data, 1)?;
(None, None, vec![p_0_given_1, p_1_given_0])
}
NoiseModelType::Crosstalk => {
let crosstalk_strength = std_error;
(None, None, vec![crosstalk_strength])
}
};
Ok(NoiseParameters {
mean_error_rate: mean_error,
std_error_rate: std_error,
t1_time,
t2_time,
readout_fidelity,
model_params,
})
}
fn estimate_t1_time(&self, measurement_data: &Array1<f64>) -> DeviceResult<f64> {
let n = measurement_data.len();
if n < 10 {
return Ok(30.0); }
let times: Array1<f64> = Array1::from_shape_fn(n, |i| i as f64);
let log_probs: Array1<f64> = measurement_data.mapv(|p| (p.max(0.01)).ln());
let mean_t = mean(×.view())?;
let mean_log_p = mean(&log_probs.view())?;
let numerator: f64 = times
.iter()
.zip(log_probs.iter())
.map(|(&t, &lp)| (t - mean_t) * (lp - mean_log_p))
.sum();
let denominator: f64 = times.iter().map(|&t| (t - mean_t).powi(2)).sum();
if denominator.abs() < 1e-10 {
return Ok(30.0);
}
let slope = numerator / denominator;
let t1 = -1.0 / slope;
Ok(t1.clamp(1.0, 1000.0))
}
fn estimate_t2_time(&self, measurement_data: &Array1<f64>) -> DeviceResult<f64> {
let t2_estimate = self.estimate_t1_time(measurement_data)? * 0.8;
Ok(t2_estimate.clamp(1.0, 500.0))
}
fn estimate_readout_error(
&self,
measurement_data: &Array1<f64>,
expected_data: &Array1<f64>,
state: usize,
) -> DeviceResult<f64> {
let expected_state = state as f64;
let count_expected: usize = expected_data
.iter()
.filter(|&&e| (e - expected_state).abs() < 0.5)
.count();
if count_expected == 0 {
return Ok(0.01); }
let count_errors: usize = expected_data
.iter()
.zip(measurement_data.iter())
.filter(|(&e, &m)| (e - expected_state).abs() < 0.5 && (m - expected_state).abs() > 0.5)
.count();
Ok((count_errors as f64 / count_expected as f64).clamp(0.0, 0.5))
}
fn compute_confidence_intervals(
&self,
errors: &Array1<f64>,
noise_params: &NoiseParameters,
) -> DeviceResult<ConfidenceIntervals> {
let n = errors.len() as f64;
let mean_err = noise_params.mean_error_rate;
let std_err = noise_params.std_error_rate;
let z_critical = if self.config.confidence_level >= 0.99 {
2.576 } else if self.config.confidence_level >= 0.95 {
1.96 } else {
1.645 };
let margin_of_error = z_critical * std_err / n.sqrt();
Ok(ConfidenceIntervals {
confidence_level: self.config.confidence_level,
error_rate_lower: (mean_err - margin_of_error).max(0.0),
error_rate_upper: (mean_err + margin_of_error).min(1.0),
t1_interval: noise_params.t1_time.map(|t1| {
let margin = 0.1 * t1; (t1 - margin, t1 + margin)
}),
t2_interval: noise_params.t2_time.map(|t2| {
let margin = 0.1 * t2;
(t2 - margin, t2 + margin)
}),
})
}
fn assess_fit_quality(
&self,
measurement_data: &Array1<f64>,
expected_data: &Array1<f64>,
noise_params: &NoiseParameters,
noise_model: NoiseModelType,
) -> DeviceResult<FitQuality> {
let residuals: Array1<f64> = measurement_data - expected_data;
let rmse = (residuals.mapv(|r| r * r).mean().unwrap_or(0.0)).sqrt();
let mean_measured = mean(&measurement_data.view())?;
let ss_tot: f64 = measurement_data
.iter()
.map(|&y| (y - mean_measured).powi(2))
.sum();
let ss_res: f64 = residuals.iter().map(|&r| r.powi(2)).sum();
let r_squared = if ss_tot > 1e-10 {
1.0 - (ss_res / ss_tot)
} else {
0.0
};
let chi_squared = ss_res / noise_params.std_error_rate.max(0.01).powi(2);
let p_value = (-chi_squared / (2.0 * measurement_data.len() as f64)).exp();
Ok(FitQuality {
chi_squared,
p_value,
r_squared: r_squared.clamp(0.0, 1.0),
rmse,
})
}
fn analyze_correlations(
&self,
measurement_data: &Array1<f64>,
) -> DeviceResult<CorrelationAnalysis> {
let chunk_size = (measurement_data.len() / 5).max(10);
let num_chunks = measurement_data.len() / chunk_size;
if num_chunks < 2 {
return Ok(CorrelationAnalysis {
correlation_matrix: Array2::eye(1),
covariance_matrix: Array2::zeros((1, 1)),
significant_correlations: Vec::new(),
});
}
let mut data_matrix = Array2::zeros((num_chunks, chunk_size));
for i in 0..num_chunks {
let start = i * chunk_size;
let end = (start + chunk_size).min(measurement_data.len());
let chunk_len = end - start;
for j in 0..chunk_len {
data_matrix[[i, j]] = measurement_data[start + j];
}
}
let corr_matrix = self.compute_covariance_matrix(&data_matrix)?;
let mut significant = Vec::new();
for i in 0..num_chunks {
for j in (i + 1)..num_chunks {
let corr = if i < corr_matrix.nrows() && j < corr_matrix.ncols() {
corr_matrix[[i, j]]
} else {
0.0
};
if corr.abs() > 0.5 {
significant.push((i, j, corr));
}
}
}
Ok(CorrelationAnalysis {
correlation_matrix: corr_matrix.clone(),
covariance_matrix: corr_matrix,
significant_correlations: significant,
})
}
fn compute_covariance_matrix(&self, data: &Array2<f64>) -> DeviceResult<Array2<f64>> {
let n = data.nrows();
let m = data.ncols();
if n < 2 {
return Ok(Array2::eye(1));
}
let mut means = Array1::zeros(m);
for j in 0..m {
let col_sum: f64 = (0..n).map(|i| data[[i, j]]).sum();
means[j] = col_sum / n as f64;
}
let mut cov = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut sum = 0.0;
for k in 0..m {
sum += (data[[i, k]] - means[k]) * (data[[j, k]] - means[k]);
}
cov[[i, j]] = sum / (m - 1) as f64;
}
}
Ok(cov)
}
pub fn generate_noise_samples(
&mut self,
noise_model: NoiseModelType,
num_samples: usize,
noise_strength: f64,
) -> Array1<f64> {
match noise_model {
NoiseModelType::Depolarizing => {
Array1::from_shape_fn(num_samples, |_| {
if self.rng.random::<f64>() < noise_strength {
1.0
} else {
0.0
}
})
}
NoiseModelType::AmplitudeDamping | NoiseModelType::PhaseDamping => {
Array1::from_shape_fn(num_samples, |i| {
let decay = (-(i as f64) / 50.0).exp();
let gaussian_noise =
self.rng.random::<f64>() * noise_strength - noise_strength / 2.0;
(decay + gaussian_noise).clamp(0.0, 1.0)
})
}
_ => {
Array1::from_shape_fn(num_samples, |_| {
(self.rng.random::<f64>() * noise_strength)
.abs()
.clamp(0.0, 1.0)
})
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_noise_characterizer_creation() {
let config = NoiseCharacterizationConfig::default();
let characterizer = NoiseCharacterizer::new(config);
assert_eq!(characterizer.config.num_samples, 10000);
}
#[test]
fn test_depolarizing_noise_characterization() {
let config = NoiseCharacterizationConfig {
num_samples: 1000,
confidence_level: 0.95,
advanced_analysis: false,
min_sample_size: 100,
};
let mut characterizer = NoiseCharacterizer::new(config);
let expected = Array1::zeros(1000);
let mut measurement = expected.clone();
for i in 0..100 {
measurement[i * 10] = 1.0; }
let result =
characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
assert!(result.is_ok());
let result = result.expect("Characterization failed");
assert!((result.noise_parameters.mean_error_rate - 0.1).abs() < 0.05);
assert!(result.confidence_intervals.error_rate_lower < 0.1);
assert!(result.confidence_intervals.error_rate_upper > 0.1);
}
#[test]
fn test_amplitude_damping_characterization() {
let config = NoiseCharacterizationConfig::default();
let mut characterizer = NoiseCharacterizer::new(config);
let n = 500;
let expected = Array1::ones(n);
let measurement = Array1::from_shape_fn(n, |i| (-(i as f64) / 50.0).exp());
let result = characterizer.characterize_noise(
&measurement,
&expected,
NoiseModelType::AmplitudeDamping,
);
assert!(result.is_ok());
let result = result.expect("Characterization failed");
assert!(result.noise_parameters.t1_time.is_some());
let t1 = result.noise_parameters.t1_time.expect("No T1 estimate");
assert!(t1 > 1.0 && t1 < 1000.0);
}
#[test]
fn test_readout_error_characterization() {
let config = NoiseCharacterizationConfig::default();
let mut characterizer = NoiseCharacterizer::new(config);
let n = 1000;
let mut expected = Array1::zeros(n);
let mut measurement = expected.clone();
for i in 0..50 {
expected[i * 20] = 0.0;
measurement[i * 20] = 1.0; }
let result =
characterizer.characterize_noise(&measurement, &expected, NoiseModelType::ReadoutError);
assert!(result.is_ok());
let result = result.expect("Characterization failed");
assert_eq!(result.noise_parameters.model_params.len(), 2);
}
#[test]
fn test_confidence_intervals() {
let config = NoiseCharacterizationConfig {
confidence_level: 0.95,
..Default::default()
};
let mut characterizer = NoiseCharacterizer::new(config);
let expected = Array1::zeros(1000);
let measurement = Array1::from_shape_fn(1000, |i| if i % 10 == 0 { 1.0 } else { 0.0 });
let result =
characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
assert!(result.is_ok());
let result = result.expect("Characterization failed");
assert_eq!(result.confidence_intervals.confidence_level, 0.95);
assert!(result.confidence_intervals.error_rate_lower >= 0.0);
assert!(result.confidence_intervals.error_rate_upper <= 1.0);
assert!(
result.confidence_intervals.error_rate_lower
< result.confidence_intervals.error_rate_upper
);
}
#[test]
fn test_fit_quality_metrics() {
let config = NoiseCharacterizationConfig::default();
let mut characterizer = NoiseCharacterizer::new(config);
let expected = Array1::zeros(500);
let measurement = expected.clone();
let result =
characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
assert!(result.is_ok());
let result = result.expect("Characterization failed");
assert!(result.fit_quality.r_squared >= 0.0);
assert!(result.fit_quality.rmse >= 0.0);
}
#[test]
fn test_noise_sample_generation() {
let config = NoiseCharacterizationConfig::default();
let mut characterizer = NoiseCharacterizer::new(config);
let samples = characterizer.generate_noise_samples(NoiseModelType::Depolarizing, 1000, 0.1);
assert_eq!(samples.len(), 1000);
for &s in samples.iter() {
assert!((0.0..=1.0).contains(&s));
}
}
#[test]
fn test_insufficient_samples_error() {
let config = NoiseCharacterizationConfig {
min_sample_size: 100,
..Default::default()
};
let mut characterizer = NoiseCharacterizer::new(config);
let expected = Array1::zeros(50);
let measurement = expected.clone();
let result =
characterizer.characterize_noise(&measurement, &expected, NoiseModelType::Depolarizing);
assert!(result.is_err());
}
}