use crate::{DeviceError, DeviceResult};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::random::prelude::*;
use scirs2_stats::{mean, median, std};
#[derive(Debug, Clone)]
pub struct HardwareBenchmarkConfig {
pub num_iterations: usize,
pub num_qubits: usize,
pub benchmark_gate_fidelity: bool,
pub benchmark_coherence: bool,
pub benchmark_readout: bool,
pub confidence_level: f64,
}
impl Default for HardwareBenchmarkConfig {
fn default() -> Self {
Self {
num_iterations: 1000,
num_qubits: 2,
benchmark_gate_fidelity: true,
benchmark_coherence: true,
benchmark_readout: true,
confidence_level: 0.95,
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkResult {
pub name: String,
pub mean_value: f64,
pub std_dev: f64,
pub median_value: f64,
pub min_value: f64,
pub max_value: f64,
pub confidence_interval: (f64, f64),
pub num_samples: usize,
pub p_value: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct HardwareBenchmarkReport {
pub device_name: String,
pub benchmarks: Vec<BenchmarkResult>,
pub overall_score: f64,
pub gate_fidelity_analysis: Option<GateFidelityAnalysis>,
pub coherence_analysis: Option<CoherenceAnalysis>,
pub readout_analysis: Option<ReadoutFidelityAnalysis>,
pub timestamp: std::time::SystemTime,
}
#[derive(Debug, Clone)]
pub struct GateFidelityAnalysis {
pub single_qubit_fidelity: Array1<f64>,
pub two_qubit_fidelity: Array1<f64>,
pub avg_single_qubit: f64,
pub avg_two_qubit: f64,
pub error_rates: Array1<f64>,
}
#[derive(Debug, Clone)]
pub struct CoherenceAnalysis {
pub t1_times: Array1<f64>,
pub t2_times: Array1<f64>,
pub avg_t1: f64,
pub avg_t2: f64,
pub t1_t2_ratio: Array1<f64>,
}
#[derive(Debug, Clone)]
pub struct ReadoutFidelityAnalysis {
pub readout_fidelity: Array1<f64>,
pub avg_fidelity: f64,
pub assignment_errors: Array2<f64>,
pub spam_error: f64,
}
pub struct HardwareBenchmarker {
config: HardwareBenchmarkConfig,
rng: StdRng,
}
impl HardwareBenchmarker {
pub fn new(config: HardwareBenchmarkConfig) -> Self {
Self {
config,
rng: StdRng::seed_from_u64(42),
}
}
pub fn default() -> Self {
Self::new(HardwareBenchmarkConfig::default())
}
pub fn run_benchmarks(
&mut self,
device_name: &str,
measurement_data: &BenchmarkMeasurementData,
) -> DeviceResult<HardwareBenchmarkReport> {
let mut benchmarks = Vec::new();
let gate_timing = self.benchmark_gate_timing(&measurement_data.gate_times)?;
benchmarks.push(gate_timing);
let gate_fidelity_analysis = if self.config.benchmark_gate_fidelity {
Some(self.analyze_gate_fidelity(&measurement_data.fidelity_data)?)
} else {
None
};
let coherence_analysis = if self.config.benchmark_coherence {
Some(self.analyze_coherence(&measurement_data.coherence_data)?)
} else {
None
};
let readout_analysis = if self.config.benchmark_readout {
Some(self.analyze_readout_fidelity(&measurement_data.readout_data)?)
} else {
None
};
let overall_score = self.compute_overall_score(
&gate_fidelity_analysis,
&coherence_analysis,
&readout_analysis,
);
Ok(HardwareBenchmarkReport {
device_name: device_name.to_string(),
benchmarks,
overall_score,
gate_fidelity_analysis,
coherence_analysis,
readout_analysis,
timestamp: std::time::SystemTime::now(),
})
}
fn benchmark_gate_timing(&self, gate_times: &Array1<f64>) -> DeviceResult<BenchmarkResult> {
if gate_times.is_empty() {
return Err(DeviceError::InvalidInput(
"Empty gate timing data".to_string(),
));
}
let mean_time = mean(&gate_times.view())?;
let std_time = std(&gate_times.view(), 1, None)?;
let median_time = median(&gate_times.view())?;
let min_time = gate_times.iter().cloned().fold(f64::INFINITY, f64::min);
let max_time = gate_times.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
let n = gate_times.len() as f64;
let z_critical = 1.96; let margin = z_critical * std_time / n.sqrt();
Ok(BenchmarkResult {
name: "Gate Execution Time".to_string(),
mean_value: mean_time,
std_dev: std_time,
median_value: median_time,
min_value: min_time,
max_value: max_time,
confidence_interval: (mean_time - margin, mean_time + margin),
num_samples: gate_times.len(),
p_value: None,
})
}
fn analyze_gate_fidelity(
&self,
fidelity_data: &GateFidelityData,
) -> DeviceResult<GateFidelityAnalysis> {
let avg_single = mean(&fidelity_data.single_qubit_fidelities.view())?;
let avg_two = mean(&fidelity_data.two_qubit_fidelities.view())?;
let error_rates = fidelity_data.single_qubit_fidelities.mapv(|f| 1.0 - f);
Ok(GateFidelityAnalysis {
single_qubit_fidelity: fidelity_data.single_qubit_fidelities.clone(),
two_qubit_fidelity: fidelity_data.two_qubit_fidelities.clone(),
avg_single_qubit: avg_single,
avg_two_qubit: avg_two,
error_rates,
})
}
fn analyze_coherence(&self, coherence_data: &CoherenceData) -> DeviceResult<CoherenceAnalysis> {
let avg_t1 = mean(&coherence_data.t1_times.view())?;
let avg_t2 = mean(&coherence_data.t2_times.view())?;
let t1_t2_ratio = Array1::from_shape_fn(coherence_data.t1_times.len(), |i| {
coherence_data.t1_times[i] / coherence_data.t2_times[i].max(0.001)
});
Ok(CoherenceAnalysis {
t1_times: coherence_data.t1_times.clone(),
t2_times: coherence_data.t2_times.clone(),
avg_t1,
avg_t2,
t1_t2_ratio,
})
}
fn analyze_readout_fidelity(
&self,
readout_data: &ReadoutData,
) -> DeviceResult<ReadoutFidelityAnalysis> {
let avg_fidelity = mean(&readout_data.readout_fidelities.view())?;
let spam_error = if readout_data.assignment_errors.nrows() > 0
&& readout_data.assignment_errors.ncols() > 0
{
let mut sum = 0.0;
let mut count = 0;
for i in 0..readout_data.assignment_errors.nrows() {
for j in 0..readout_data.assignment_errors.ncols() {
if i != j {
sum += readout_data.assignment_errors[[i, j]];
count += 1;
}
}
}
if count > 0 {
sum / count as f64
} else {
0.0
}
} else {
0.0
};
Ok(ReadoutFidelityAnalysis {
readout_fidelity: readout_data.readout_fidelities.clone(),
avg_fidelity,
assignment_errors: readout_data.assignment_errors.clone(),
spam_error,
})
}
fn compute_overall_score(
&self,
gate_fidelity: &Option<GateFidelityAnalysis>,
coherence: &Option<CoherenceAnalysis>,
readout: &Option<ReadoutFidelityAnalysis>,
) -> f64 {
let mut score = 0.0;
let mut weight_sum = 0.0;
if let Some(gf) = gate_fidelity {
let gate_score = (gf.avg_single_qubit + gf.avg_two_qubit) / 2.0 * 100.0;
score += gate_score * 0.4;
weight_sum += 0.4;
}
if let Some(coh) = coherence {
let coherence_score =
((coh.avg_t1 / 100.0).min(1.0) + (coh.avg_t2 / 100.0).min(1.0)) / 2.0 * 100.0;
score += coherence_score * 0.3;
weight_sum += 0.3;
}
if let Some(ro) = readout {
let readout_score = ro.avg_fidelity * 100.0;
score += readout_score * 0.3;
weight_sum += 0.3;
}
if weight_sum > 0.0 {
score / weight_sum
} else {
0.0
}
}
pub fn generate_synthetic_data(&mut self, num_qubits: usize) -> BenchmarkMeasurementData {
let gate_times = Array1::from_shape_fn(self.config.num_iterations, |_| {
let base_time = 100.0;
let noise = self.rng.random::<f64>() * 50.0;
base_time + noise
});
let single_qubit_fidelities =
Array1::from_shape_fn(num_qubits, |_| 0.995 + self.rng.random::<f64>() * 0.0049);
let two_qubit_fidelities = Array1::from_shape_fn(num_qubits.saturating_sub(1), |_| {
0.980 + self.rng.random::<f64>() * 0.015
});
let t1_times =
Array1::from_shape_fn(num_qubits, |_| 30.0 + self.rng.random::<f64>() * 70.0);
let t2_times = Array1::from_shape_fn(num_qubits, |i| {
let max_t2 = t1_times[i] * 0.8;
20.0 + self.rng.random::<f64>() * (max_t2 - 20.0).max(0.0)
});
let readout_fidelities =
Array1::from_shape_fn(num_qubits, |_| 0.95 + self.rng.random::<f64>() * 0.04);
let mut assignment_errors = Array2::zeros((2, 2));
assignment_errors[[0, 0]] = 0.98; assignment_errors[[0, 1]] = 0.02; assignment_errors[[1, 0]] = 0.03; assignment_errors[[1, 1]] = 0.97;
BenchmarkMeasurementData {
gate_times,
fidelity_data: GateFidelityData {
single_qubit_fidelities,
two_qubit_fidelities,
},
coherence_data: CoherenceData { t1_times, t2_times },
readout_data: ReadoutData {
readout_fidelities,
assignment_errors,
},
}
}
}
#[derive(Debug, Clone)]
pub struct BenchmarkMeasurementData {
pub gate_times: Array1<f64>,
pub fidelity_data: GateFidelityData,
pub coherence_data: CoherenceData,
pub readout_data: ReadoutData,
}
#[derive(Debug, Clone)]
pub struct GateFidelityData {
pub single_qubit_fidelities: Array1<f64>,
pub two_qubit_fidelities: Array1<f64>,
}
#[derive(Debug, Clone)]
pub struct CoherenceData {
pub t1_times: Array1<f64>,
pub t2_times: Array1<f64>,
}
#[derive(Debug, Clone)]
pub struct ReadoutData {
pub readout_fidelities: Array1<f64>,
pub assignment_errors: Array2<f64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_benchmarker_creation() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
assert_eq!(benchmarker.config.num_iterations, 1000);
}
#[test]
fn test_synthetic_data_generation() {
let config = HardwareBenchmarkConfig::default();
let mut benchmarker = HardwareBenchmarker::new(config);
let data = benchmarker.generate_synthetic_data(3);
assert_eq!(data.gate_times.len(), 1000);
assert_eq!(data.fidelity_data.single_qubit_fidelities.len(), 3);
assert_eq!(data.coherence_data.t1_times.len(), 3);
assert_eq!(data.readout_data.readout_fidelities.len(), 3);
}
#[test]
fn test_full_benchmark_run() {
let config = HardwareBenchmarkConfig::default();
let mut benchmarker = HardwareBenchmarker::new(config);
let data = benchmarker.generate_synthetic_data(2);
let report = benchmarker.run_benchmarks("TestDevice", &data);
assert!(report.is_ok());
let report = report.expect("Benchmark failed");
assert_eq!(report.device_name, "TestDevice");
assert!(!report.benchmarks.is_empty());
assert!(report.overall_score > 0.0 && report.overall_score <= 100.0);
}
#[test]
fn test_gate_timing_benchmark() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
let gate_times = Array1::from_vec(vec![100.0, 110.0, 105.0, 95.0, 100.0]);
let result = benchmarker.benchmark_gate_timing(&gate_times);
assert!(result.is_ok());
let result = result.expect("Benchmark failed");
assert_eq!(result.name, "Gate Execution Time");
assert!((result.mean_value - 102.0).abs() < 1.0);
assert!(result.std_dev > 0.0);
assert_eq!(result.num_samples, 5);
}
#[test]
fn test_gate_fidelity_analysis() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
let fidelity_data = GateFidelityData {
single_qubit_fidelities: Array1::from_vec(vec![0.999, 0.998, 0.997]),
two_qubit_fidelities: Array1::from_vec(vec![0.99, 0.985]),
};
let analysis = benchmarker.analyze_gate_fidelity(&fidelity_data);
assert!(analysis.is_ok());
let analysis = analysis.expect("Analysis failed");
assert!((analysis.avg_single_qubit - 0.998).abs() < 0.001);
assert!((analysis.avg_two_qubit - 0.9875).abs() < 0.001);
assert_eq!(analysis.error_rates.len(), 3);
}
#[test]
fn test_coherence_analysis() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
let coherence_data = CoherenceData {
t1_times: Array1::from_vec(vec![50.0, 60.0, 55.0]),
t2_times: Array1::from_vec(vec![40.0, 48.0, 44.0]),
};
let analysis = benchmarker.analyze_coherence(&coherence_data);
assert!(analysis.is_ok());
let analysis = analysis.expect("Analysis failed");
assert!((analysis.avg_t1 - 55.0).abs() < 0.1);
assert!((analysis.avg_t2 - 44.0).abs() < 0.1);
assert_eq!(analysis.t1_t2_ratio.len(), 3);
}
#[test]
fn test_readout_fidelity_analysis() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
let mut assignment_errors = Array2::zeros((2, 2));
assignment_errors[[0, 0]] = 0.97;
assignment_errors[[0, 1]] = 0.03;
assignment_errors[[1, 0]] = 0.02;
assignment_errors[[1, 1]] = 0.98;
let readout_data = ReadoutData {
readout_fidelities: Array1::from_vec(vec![0.97, 0.98, 0.96]),
assignment_errors,
};
let analysis = benchmarker.analyze_readout_fidelity(&readout_data);
assert!(analysis.is_ok());
let analysis = analysis.expect("Analysis failed");
assert!((analysis.avg_fidelity - 0.97).abs() < 0.01);
assert!(analysis.spam_error > 0.0);
}
#[test]
fn test_overall_score_computation() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
let gate_analysis = GateFidelityAnalysis {
single_qubit_fidelity: Array1::from_vec(vec![0.999]),
two_qubit_fidelity: Array1::from_vec(vec![0.99]),
avg_single_qubit: 0.999,
avg_two_qubit: 0.99,
error_rates: Array1::from_vec(vec![0.001]),
};
let coherence_analysis = CoherenceAnalysis {
t1_times: Array1::from_vec(vec![50.0]),
t2_times: Array1::from_vec(vec![40.0]),
avg_t1: 50.0,
avg_t2: 40.0,
t1_t2_ratio: Array1::from_vec(vec![1.25]),
};
let readout_analysis = ReadoutFidelityAnalysis {
readout_fidelity: Array1::from_vec(vec![0.97]),
avg_fidelity: 0.97,
assignment_errors: Array2::zeros((2, 2)),
spam_error: 0.025,
};
let score = benchmarker.compute_overall_score(
&Some(gate_analysis),
&Some(coherence_analysis),
&Some(readout_analysis),
);
assert!(score > 50.0 && score < 100.0);
}
#[test]
fn test_empty_gate_times_error() {
let config = HardwareBenchmarkConfig::default();
let benchmarker = HardwareBenchmarker::new(config);
let empty_times = Array1::from_vec(vec![]);
let result = benchmarker.benchmark_gate_timing(&empty_times);
assert!(result.is_err());
}
}