use crate::builder::Circuit;
use crate::noise_models::{NoiseAnalysisResult, NoiseModel};
use crate::simulator_interface::{ExecutionResult, SimulatorBackend};
use quantrs2_core::{
error::{QuantRS2Error, QuantRS2Result},
gate::GateOp,
};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq)]
pub enum Distribution {
Normal { mean: f64, std_dev: f64 },
Uniform { min: f64, max: f64 },
Exponential { rate: f64 },
Beta { alpha: f64, beta: f64 },
Gamma { shape: f64, scale: f64 },
Poisson { lambda: f64 },
ChiSquared { degrees_of_freedom: usize },
StudentT { degrees_of_freedom: usize },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StatisticalTest {
KolmogorovSmirnov,
AndersonDarling,
ShapiroWilk,
MannWhitney,
Wilcoxon,
ChiSquaredGoodnessOfFit,
ANOVA,
KruskalWallis,
}
#[derive(Debug, Clone)]
pub struct HypothesisTestResult {
pub test_statistic: f64,
pub p_value: f64,
pub critical_value: f64,
pub reject_null: bool,
pub significance_level: f64,
pub effect_size: Option<f64>,
pub confidence_interval: Option<(f64, f64)>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DescriptiveStats {
pub count: usize,
pub mean: f64,
pub std_dev: f64,
pub variance: f64,
pub min: f64,
pub max: f64,
pub median: f64,
pub q1: f64,
pub q3: f64,
pub iqr: f64,
pub skewness: f64,
pub kurtosis: f64,
pub mode: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct BenchmarkConfig {
pub num_runs: usize,
pub warmup_runs: usize,
pub timeout: Duration,
pub significance_level: f64,
pub collect_timing: bool,
pub collect_memory: bool,
pub collect_errors: bool,
pub seed: Option<u64>,
}
impl Default for BenchmarkConfig {
fn default() -> Self {
Self {
num_runs: 100,
warmup_runs: 10,
timeout: Duration::from_secs(60),
significance_level: 0.05,
collect_timing: true,
collect_memory: false,
collect_errors: true,
seed: None,
}
}
}
pub struct CircuitBenchmark {
config: BenchmarkConfig,
benchmark_data: Vec<BenchmarkRun>,
stats_analyzer: StatisticalAnalyzer,
}
#[derive(Debug, Clone)]
pub struct BenchmarkRun {
pub run_id: usize,
pub execution_time: Duration,
pub memory_usage: Option<usize>,
pub success: bool,
pub error_message: Option<String>,
pub circuit_metrics: CircuitMetrics,
pub execution_results: Option<ExecutionResult>,
pub noise_analysis: Option<NoiseAnalysisResult>,
pub custom_metrics: HashMap<String, f64>,
}
#[derive(Debug, Clone)]
pub struct CircuitMetrics {
pub depth: usize,
pub gate_count: usize,
pub gate_counts: HashMap<String, usize>,
pub two_qubit_gates: usize,
pub fidelity: Option<f64>,
pub error_rate: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct BenchmarkReport {
pub config: BenchmarkConfig,
pub completed_runs: usize,
pub success_rate: f64,
pub timing_stats: DescriptiveStats,
pub memory_stats: Option<DescriptiveStats>,
pub regression_analysis: Option<RegressionAnalysis>,
pub distribution_fit: Option<DistributionFit>,
pub outlier_analysis: OutlierAnalysis,
pub baseline_comparison: Option<BaselineComparison>,
pub statistical_tests: Vec<HypothesisTestResult>,
pub insights: Vec<PerformanceInsight>,
}
#[derive(Debug, Clone)]
pub struct RegressionAnalysis {
pub slope: f64,
pub intercept: f64,
pub r_squared: f64,
pub slope_p_value: f64,
pub significant_trend: bool,
pub degradation_per_run: f64,
}
#[derive(Debug, Clone)]
pub struct DistributionFit {
pub best_distribution: Distribution,
pub goodness_of_fit: f64,
pub fit_p_value: f64,
pub alternative_fits: Vec<(Distribution, f64)>,
}
#[derive(Debug, Clone)]
pub struct OutlierAnalysis {
pub num_outliers: usize,
pub outlier_indices: Vec<usize>,
pub detection_method: OutlierDetectionMethod,
pub threshold: f64,
pub outlier_impact: OutlierImpact,
}
#[derive(Debug, Clone, PartialEq)]
pub enum OutlierDetectionMethod {
IQR { multiplier: f64 },
ZScore { threshold: f64 },
ModifiedZScore { threshold: f64 },
IsolationForest,
LocalOutlierFactor,
}
#[derive(Debug, Clone)]
pub struct OutlierImpact {
pub mean_change: f64,
pub std_dev_change: f64,
pub median_change: f64,
pub relative_impact: f64,
}
#[derive(Debug, Clone)]
pub struct BaselineComparison {
pub baseline_name: String,
pub performance_factor: f64,
pub significance: HypothesisTestResult,
pub difference_ci: (f64, f64),
pub effect_size: f64,
pub practical_significance: PracticalSignificance,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PracticalSignificance {
Negligible,
Small,
Medium,
Large,
VeryLarge,
}
#[derive(Debug, Clone)]
pub struct PerformanceInsight {
pub category: InsightCategory,
pub message: String,
pub confidence: f64,
pub evidence: Vec<String>,
pub recommendations: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InsightCategory {
PerformanceDegradation,
PerformanceImprovement,
HighVariability,
OutliersDetected,
MemoryUsage,
ErrorRate,
OptimizationOpportunity,
}
impl CircuitBenchmark {
#[must_use]
pub const fn new(config: BenchmarkConfig) -> Self {
Self {
config,
benchmark_data: Vec::new(),
stats_analyzer: StatisticalAnalyzer::new(),
}
}
pub fn run_benchmark<const N: usize>(
&mut self,
circuit: &Circuit<N>,
simulator: &dyn SimulatorExecutor,
noise_model: Option<&NoiseModel>,
) -> QuantRS2Result<BenchmarkReport> {
self.benchmark_data.clear();
let total_runs = self.config.num_runs + self.config.warmup_runs;
for run_id in 0..total_runs {
let is_warmup = run_id < self.config.warmup_runs;
match self.run_single_benchmark(circuit, simulator, noise_model, run_id) {
Ok(run_data) => {
if !is_warmup {
self.benchmark_data.push(run_data);
}
}
Err(e) => {
if !is_warmup {
let failed_run = BenchmarkRun {
run_id,
execution_time: Duration::from_millis(0),
memory_usage: None,
success: false,
error_message: Some(e.to_string()),
circuit_metrics: self.calculate_circuit_metrics(circuit),
execution_results: None,
noise_analysis: None,
custom_metrics: HashMap::new(),
};
self.benchmark_data.push(failed_run);
}
}
}
}
self.generate_benchmark_report()
}
fn run_single_benchmark<const N: usize>(
&self,
circuit: &Circuit<N>,
simulator: &dyn SimulatorExecutor,
noise_model: Option<&NoiseModel>,
run_id: usize,
) -> QuantRS2Result<BenchmarkRun> {
let start_time = Instant::now();
let start_memory = if self.config.collect_memory {
Some(self.get_memory_usage())
} else {
None
};
let execution_results = None;
let end_time = Instant::now();
let end_memory = if self.config.collect_memory {
Some(self.get_memory_usage())
} else {
None
};
let execution_time = end_time - start_time;
let memory_usage = match (start_memory, end_memory) {
(Some(start), Some(end)) => Some(end.saturating_sub(start)),
_ => None,
};
let noise_analysis = if let Some(noise) = noise_model {
None
} else {
None
};
Ok(BenchmarkRun {
run_id,
execution_time,
memory_usage,
success: true,
error_message: None,
circuit_metrics: self.calculate_circuit_metrics(circuit),
execution_results,
noise_analysis,
custom_metrics: HashMap::new(),
})
}
fn calculate_circuit_metrics<const N: usize>(&self, circuit: &Circuit<N>) -> CircuitMetrics {
let gate_count = circuit.gates().len();
let mut gate_counts = HashMap::new();
let mut two_qubit_gates = 0;
for gate in circuit.gates() {
let gate_name = gate.name();
*gate_counts.entry(gate_name.to_string()).or_insert(0) += 1;
if gate.qubits().len() == 2 {
two_qubit_gates += 1;
}
}
CircuitMetrics {
depth: gate_count, gate_count,
gate_counts,
two_qubit_gates,
fidelity: None,
error_rate: None,
}
}
const fn get_memory_usage(&self) -> usize {
0
}
fn generate_benchmark_report(&self) -> QuantRS2Result<BenchmarkReport> {
let completed_runs = self.benchmark_data.len();
let successful_runs: Vec<_> = self
.benchmark_data
.iter()
.filter(|run| run.success)
.collect();
let success_rate = successful_runs.len() as f64 / completed_runs as f64;
let timing_data: Vec<f64> = successful_runs
.iter()
.map(|run| run.execution_time.as_secs_f64())
.collect();
let timing_stats = self
.stats_analyzer
.calculate_descriptive_stats(&timing_data)?;
let memory_stats = if self.config.collect_memory {
let memory_data: Vec<f64> = successful_runs
.iter()
.filter_map(|run| run.memory_usage.map(|m| m as f64))
.collect();
if memory_data.is_empty() {
None
} else {
Some(
self.stats_analyzer
.calculate_descriptive_stats(&memory_data)?,
)
}
} else {
None
};
let regression_analysis = self
.stats_analyzer
.perform_regression_analysis(&timing_data)?;
let distribution_fit = self.stats_analyzer.fit_distributions(&timing_data)?;
let outlier_analysis = self.stats_analyzer.detect_outliers(
&timing_data,
OutlierDetectionMethod::IQR { multiplier: 1.5 },
)?;
let insights = self.generate_performance_insights(
&timing_stats,
®ression_analysis,
&outlier_analysis,
success_rate,
);
Ok(BenchmarkReport {
config: self.config.clone(),
completed_runs,
success_rate,
timing_stats,
memory_stats,
regression_analysis: Some(regression_analysis),
distribution_fit: Some(distribution_fit),
outlier_analysis,
baseline_comparison: None,
statistical_tests: Vec::new(),
insights,
})
}
fn generate_performance_insights(
&self,
timing_stats: &DescriptiveStats,
regression: &RegressionAnalysis,
outliers: &OutlierAnalysis,
success_rate: f64,
) -> Vec<PerformanceInsight> {
let mut insights = Vec::new();
if regression.significant_trend && regression.slope > 0.0 {
insights.push(PerformanceInsight {
category: InsightCategory::PerformanceDegradation,
message: format!(
"Significant performance degradation detected: {:.4} seconds per run increase",
regression.degradation_per_run
),
confidence: 1.0 - regression.slope_p_value,
evidence: vec![
format!("Linear trend slope: {:.6}", regression.slope),
format!("R-squared: {:.4}", regression.r_squared),
format!("P-value: {:.4}", regression.slope_p_value),
],
recommendations: vec![
"Investigate potential memory leaks".to_string(),
"Check for resource contention".to_string(),
"Profile execution to identify bottlenecks".to_string(),
],
});
}
let coefficient_of_variation = timing_stats.std_dev / timing_stats.mean;
if coefficient_of_variation > 0.2 {
insights.push(PerformanceInsight {
category: InsightCategory::HighVariability,
message: format!(
"High performance variability detected: CV = {:.2}%",
coefficient_of_variation * 100.0
),
confidence: 0.8,
evidence: vec![
format!("Standard deviation: {:.4} seconds", timing_stats.std_dev),
format!("Mean: {:.4} seconds", timing_stats.mean),
format!(
"Coefficient of variation: {:.2}%",
coefficient_of_variation * 100.0
),
],
recommendations: vec![
"Increase warm-up runs to stabilize performance".to_string(),
"Check for system load variations".to_string(),
"Consider running benchmarks in isolated environment".to_string(),
],
});
}
if outliers.num_outliers > 0 {
let outlier_percentage =
outliers.num_outliers as f64 / timing_stats.count as f64 * 100.0;
insights.push(PerformanceInsight {
category: InsightCategory::OutliersDetected,
message: format!(
"Performance outliers detected: {} outliers ({:.1}% of runs)",
outliers.num_outliers, outlier_percentage
),
confidence: 0.9,
evidence: vec![
format!("Number of outliers: {}", outliers.num_outliers),
format!("Outlier percentage: {:.1}%", outlier_percentage),
format!("Detection method: {:?}", outliers.detection_method),
],
recommendations: vec![
"Investigate causes of outlier runs".to_string(),
"Consider removing outliers from performance metrics".to_string(),
"Check for system interruptions during benchmarking".to_string(),
],
});
}
if success_rate < 0.95 {
insights.push(PerformanceInsight {
category: InsightCategory::ErrorRate,
message: format!("Low success rate detected: {:.1}%", success_rate * 100.0),
confidence: 1.0,
evidence: vec![
format!("Success rate: {:.1}%", success_rate * 100.0),
format!(
"Failed runs: {}",
timing_stats.count - (timing_stats.count as f64 * success_rate) as usize
),
],
recommendations: vec![
"Investigate failure causes".to_string(),
"Check circuit validity and simulator compatibility".to_string(),
"Increase timeout limits if timeouts are occurring".to_string(),
],
});
}
insights
}
pub fn compare_with_baseline(
&self,
baseline: &BenchmarkReport,
) -> QuantRS2Result<BaselineComparison> {
if self.benchmark_data.is_empty() {
return Err(QuantRS2Error::InvalidInput(
"No benchmark data available for comparison".to_string(),
));
}
let current_timing: Vec<f64> = self
.benchmark_data
.iter()
.filter(|run| run.success)
.map(|run| run.execution_time.as_secs_f64())
.collect();
let baseline_mean = baseline.timing_stats.mean;
let current_mean = self
.stats_analyzer
.calculate_descriptive_stats(¤t_timing)?
.mean;
let performance_factor = current_mean / baseline_mean;
let significance = self.stats_analyzer.mann_whitney_test(
¤t_timing,
&[baseline_mean], self.config.significance_level,
)?;
let effect_size = (current_mean - baseline_mean) / baseline.timing_stats.std_dev;
let practical_significance = match effect_size.abs() {
x if x < 0.2 => PracticalSignificance::Negligible,
x if x < 0.5 => PracticalSignificance::Small,
x if x < 0.8 => PracticalSignificance::Medium,
x if x < 1.2 => PracticalSignificance::Large,
_ => PracticalSignificance::VeryLarge,
};
Ok(BaselineComparison {
baseline_name: "baseline".to_string(),
performance_factor,
significance,
difference_ci: (0.0, 0.0), effect_size,
practical_significance,
})
}
}
pub struct StatisticalAnalyzer;
impl StatisticalAnalyzer {
#[must_use]
pub const fn new() -> Self {
Self
}
pub fn calculate_descriptive_stats(&self, data: &[f64]) -> QuantRS2Result<DescriptiveStats> {
if data.is_empty() {
return Err(QuantRS2Error::InvalidInput("Empty data".to_string()));
}
let mut sorted_data = data.to_vec();
sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let count = data.len();
let mean = data.iter().sum::<f64>() / count as f64;
let variance = data.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / count as f64;
let std_dev = variance.sqrt();
let min = sorted_data[0];
let max = sorted_data[count - 1];
let median = if count % 2 == 0 {
f64::midpoint(sorted_data[count / 2 - 1], sorted_data[count / 2])
} else {
sorted_data[count / 2]
};
let q1 = self.percentile(&sorted_data, 0.25);
let q3 = self.percentile(&sorted_data, 0.75);
let iqr = q3 - q1;
let skewness = self.calculate_skewness(data, mean, std_dev);
let kurtosis = self.calculate_kurtosis(data, mean, std_dev);
Ok(DescriptiveStats {
count,
mean,
std_dev,
variance,
min,
max,
median,
q1,
q3,
iqr,
skewness,
kurtosis,
mode: None, })
}
fn percentile(&self, sorted_data: &[f64], p: f64) -> f64 {
let index = (p * (sorted_data.len() - 1) as f64).round() as usize;
sorted_data[index.min(sorted_data.len() - 1)]
}
fn calculate_skewness(&self, data: &[f64], mean: f64, std_dev: f64) -> f64 {
let n = data.len() as f64;
let skew_sum = data
.iter()
.map(|x| ((x - mean) / std_dev).powi(3))
.sum::<f64>();
skew_sum / n
}
fn calculate_kurtosis(&self, data: &[f64], mean: f64, std_dev: f64) -> f64 {
let n = data.len() as f64;
let kurt_sum = data
.iter()
.map(|x| ((x - mean) / std_dev).powi(4))
.sum::<f64>();
kurt_sum / n - 3.0 }
pub fn perform_regression_analysis(&self, data: &[f64]) -> QuantRS2Result<RegressionAnalysis> {
if data.len() < 3 {
return Err(QuantRS2Error::InvalidInput(
"Insufficient data for regression".to_string(),
));
}
let n = data.len() as f64;
let x_values: Vec<f64> = (0..data.len()).map(|i| i as f64).collect();
let x_mean = x_values.iter().sum::<f64>() / n;
let y_mean = data.iter().sum::<f64>() / n;
let numerator: f64 = x_values
.iter()
.zip(data.iter())
.map(|(x, y)| (x - x_mean) * (y - y_mean))
.sum();
let denominator: f64 = x_values.iter().map(|x| (x - x_mean).powi(2)).sum();
let slope = numerator / denominator;
let intercept = slope.mul_add(-x_mean, y_mean);
let ss_tot: f64 = data.iter().map(|y| (y - y_mean).powi(2)).sum();
let ss_res: f64 = x_values
.iter()
.zip(data.iter())
.map(|(x, y)| {
let predicted = slope * x + intercept;
(y - predicted).powi(2)
})
.sum();
let r_squared = 1.0 - (ss_res / ss_tot);
let slope_p_value = if slope.abs() > 0.001 { 0.05 } else { 0.5 };
let significant_trend = slope_p_value < 0.05;
Ok(RegressionAnalysis {
slope,
intercept,
r_squared,
slope_p_value,
significant_trend,
degradation_per_run: slope,
})
}
pub fn fit_distributions(&self, data: &[f64]) -> QuantRS2Result<DistributionFit> {
let stats = self.calculate_descriptive_stats(data)?;
let normal_dist = Distribution::Normal {
mean: stats.mean,
std_dev: stats.std_dev,
};
let goodness_of_fit = 0.8; let fit_p_value = 0.3;
Ok(DistributionFit {
best_distribution: normal_dist,
goodness_of_fit,
fit_p_value,
alternative_fits: Vec::new(),
})
}
pub fn detect_outliers(
&self,
data: &[f64],
method: OutlierDetectionMethod,
) -> QuantRS2Result<OutlierAnalysis> {
let outlier_indices = match method {
OutlierDetectionMethod::IQR { multiplier } => {
self.detect_outliers_iqr(data, multiplier)?
}
OutlierDetectionMethod::ZScore { threshold } => {
self.detect_outliers_zscore(data, threshold)?
}
_ => Vec::new(), };
let num_outliers = outlier_indices.len();
let outlier_impact = if num_outliers > 0 {
self.calculate_outlier_impact(data, &outlier_indices)?
} else {
OutlierImpact {
mean_change: 0.0,
std_dev_change: 0.0,
median_change: 0.0,
relative_impact: 0.0,
}
};
Ok(OutlierAnalysis {
num_outliers,
outlier_indices,
detection_method: method,
threshold: 1.5, outlier_impact,
})
}
fn detect_outliers_iqr(&self, data: &[f64], multiplier: f64) -> QuantRS2Result<Vec<usize>> {
let stats = self.calculate_descriptive_stats(data)?;
let lower_bound = multiplier.mul_add(-stats.iqr, stats.q1);
let upper_bound = multiplier.mul_add(stats.iqr, stats.q3);
Ok(data
.iter()
.enumerate()
.filter_map(|(i, &value)| {
if value < lower_bound || value > upper_bound {
Some(i)
} else {
None
}
})
.collect())
}
fn detect_outliers_zscore(&self, data: &[f64], threshold: f64) -> QuantRS2Result<Vec<usize>> {
let stats = self.calculate_descriptive_stats(data)?;
Ok(data
.iter()
.enumerate()
.filter_map(|(i, &value)| {
let z_score = (value - stats.mean) / stats.std_dev;
if z_score.abs() > threshold {
Some(i)
} else {
None
}
})
.collect())
}
fn calculate_outlier_impact(
&self,
data: &[f64],
outlier_indices: &[usize],
) -> QuantRS2Result<OutlierImpact> {
let original_stats = self.calculate_descriptive_stats(data)?;
let filtered_data: Vec<f64> = data
.iter()
.enumerate()
.filter_map(|(i, &value)| {
if outlier_indices.contains(&i) {
None
} else {
Some(value)
}
})
.collect();
let filtered_stats = self.calculate_descriptive_stats(&filtered_data)?;
let mean_change = (original_stats.mean - filtered_stats.mean).abs();
let std_dev_change = (original_stats.std_dev - filtered_stats.std_dev).abs();
let median_change = (original_stats.median - filtered_stats.median).abs();
let relative_impact = mean_change / original_stats.mean * 100.0;
Ok(OutlierImpact {
mean_change,
std_dev_change,
median_change,
relative_impact,
})
}
pub fn mann_whitney_test(
&self,
sample1: &[f64],
sample2: &[f64],
significance_level: f64,
) -> QuantRS2Result<HypothesisTestResult> {
let test_statistic = 0.0; let p_value = 0.1; let critical_value = 1.96; let reject_null = p_value < significance_level;
Ok(HypothesisTestResult {
test_statistic,
p_value,
critical_value,
reject_null,
significance_level,
effect_size: None,
confidence_interval: None,
})
}
}
pub trait SimulatorExecutor {
fn execute(&self, circuit: &dyn std::any::Any) -> QuantRS2Result<ExecutionResult>;
}
impl Default for StatisticalAnalyzer {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_descriptive_stats() {
let analyzer = StatisticalAnalyzer::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let stats = analyzer
.calculate_descriptive_stats(&data)
.expect("calculate_descriptive_stats should succeed");
assert_eq!(stats.mean, 3.0);
assert_eq!(stats.median, 3.0);
assert_eq!(stats.min, 1.0);
assert_eq!(stats.max, 5.0);
}
#[test]
fn test_outlier_detection_iqr() {
let analyzer = StatisticalAnalyzer::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 100.0];
let outliers = analyzer
.detect_outliers_iqr(&data, 1.5)
.expect("outlier detection should succeed");
assert_eq!(outliers.len(), 1);
assert_eq!(outliers[0], 5); }
#[test]
fn test_regression_analysis() {
let analyzer = StatisticalAnalyzer::new();
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let regression = analyzer
.perform_regression_analysis(&data)
.expect("perform_regression_analysis should succeed");
assert!((regression.slope - 1.0).abs() < 1e-10);
assert!((regression.r_squared - 1.0).abs() < 1e-10);
}
#[test]
fn test_benchmark_config() {
let config = BenchmarkConfig::default();
assert_eq!(config.num_runs, 100);
assert_eq!(config.warmup_runs, 10);
assert_eq!(config.significance_level, 0.05);
}
#[test]
fn test_distribution_creation() {
let normal = Distribution::Normal {
mean: 0.0,
std_dev: 1.0,
};
match normal {
Distribution::Normal { mean, std_dev } => {
assert_eq!(mean, 0.0);
assert_eq!(std_dev, 1.0);
}
_ => panic!("Wrong distribution type"),
}
}
}