1use std::collections::HashMap;
7use std::time::{Duration, Instant};
8
9use scirs2_core::random::prelude::*;
10use scirs2_core::random::thread_rng;
11use serde::{Deserialize, Serialize};
12
13use quantrs2_circuit::prelude::*;
14use quantrs2_core::{
15 error::{QuantRS2Error, QuantRS2Result},
16 gate::GateOp,
17 qubit::QubitId,
18};
19
20use scirs2_graph::{
21 betweenness_centrality, closeness_centrality, clustering_coefficient, dijkstra_path,
22 graph_density, spectral_radius, Graph,
23};
24use scirs2_linalg::{
25 correlationmatrix, det, eigvals, inv, matrix_norm, svd, LinalgError, LinalgResult,
26};
27use scirs2_stats::{
28 distributions, mean, median, pearsonr, spearmanr, std, ttest::Alternative, ttest_1samp, var,
29 TTestResult,
30};
31
32use scirs2_core::ndarray::{s, Array1, Array2, ArrayView1, ArrayView2};
33
34use crate::{
35 backend_traits::{query_backend_capabilities, BackendCapabilities},
36 calibration::{CalibrationManager, DeviceCalibration},
37 noise_model::CalibrationNoiseModel,
38 topology::HardwareTopology,
39 CircuitResult, DeviceError, DeviceResult,
40};
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct BenchmarkConfig {
45 pub iterations: usize,
47 pub confidence_level: f64,
49 pub max_runtime: Duration,
51 pub circuit_depths: Vec<usize>,
53 pub qubit_counts: Vec<usize>,
55 pub gate_types: Vec<String>,
57 pub enable_advanced_stats: bool,
59 pub enable_graph_analysis: bool,
61 pub enable_noise_analysis: bool,
63}
64
65impl Default for BenchmarkConfig {
66 fn default() -> Self {
67 Self {
68 iterations: 100,
69 confidence_level: 0.95,
70 max_runtime: Duration::from_secs(300),
71 circuit_depths: vec![5, 10, 20, 50, 100],
72 qubit_counts: vec![2, 4, 8, 16],
73 gate_types: vec!["H".to_string(), "CNOT".to_string(), "RZ".to_string()],
74 enable_advanced_stats: true,
75 enable_graph_analysis: true,
76 enable_noise_analysis: true,
77 }
78 }
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct BenchmarkSuite {
84 pub device_id: String,
86 pub backend_capabilities: BackendCapabilities,
88 pub config: BenchmarkConfig,
90 pub benchmark_results: Vec<BenchmarkResult>,
92 pub statistical_analysis: StatisticalAnalysis,
94 pub graph_analysis: Option<GraphAnalysis>,
96 pub noise_analysis: Option<NoiseAnalysis>,
98 pub performance_metrics: PerformanceMetrics,
100 pub execution_time: Duration,
102}
103
104#[derive(Debug, Clone, Serialize, Deserialize)]
106pub struct BenchmarkResult {
107 pub name: String,
109 pub circuit_description: String,
111 pub num_qubits: usize,
113 pub circuit_depth: usize,
115 pub gate_count: usize,
117 pub execution_times: Vec<f64>,
119 pub fidelities: Vec<f64>,
121 pub error_rates: Vec<f64>,
123 pub success_probabilities: Vec<f64>,
125 pub queue_times: Vec<f64>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct StatisticalAnalysis {
132 pub execution_time_stats: DescriptiveStats,
134 pub fidelity_stats: DescriptiveStats,
136 pub error_rate_stats: DescriptiveStats,
138 pub correlationmatrix: Array2<f64>,
140 pub statistical_tests: HashMap<String, TestResult>,
142 pub distribution_fits: HashMap<String, DistributionFit>,
144}
145
146#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct GraphAnalysis {
149 pub connectivity_metrics: ConnectivityMetrics,
151 pub topology_correlations: HashMap<String, f64>,
153 pub critical_paths: Vec<CriticalPath>,
155 pub spectral_properties: SpectralProperties,
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct NoiseAnalysis {
162 pub crosstalk_correlations: Array2<f64>,
164 pub temporal_correlations: Array1<f64>,
166 pub spatial_patterns: HashMap<String, Array2<f64>>,
168 pub model_validation: NoiseModelValidation,
170}
171
172#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct PerformanceMetrics {
175 pub overall_score: f64,
177 pub reliability_score: f64,
179 pub speed_score: f64,
181 pub accuracy_score: f64,
183 pub efficiency_score: f64,
185 pub scalability_metrics: ScalabilityMetrics,
187}
188
189#[derive(Debug, Clone, Serialize, Deserialize)]
191pub struct DescriptiveStats {
192 pub mean: f64,
193 pub median: f64,
194 pub std_dev: f64,
195 pub variance: f64,
196 pub min: f64,
197 pub max: f64,
198 pub q25: f64,
199 pub q75: f64,
200 pub confidence_interval: (f64, f64),
201}
202
203#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct TestResult {
206 pub test_name: String,
207 pub statistic: f64,
208 pub p_value: f64,
209 pub significant: bool,
210 pub effect_size: Option<f64>,
211}
212
213#[derive(Debug, Clone, Serialize, Deserialize)]
215pub struct DistributionFit {
216 pub distribution_name: String,
217 pub parameters: Vec<f64>,
218 pub goodness_of_fit: f64,
219 pub p_value: f64,
220}
221
222#[derive(Debug, Clone, Serialize, Deserialize)]
224pub struct ConnectivityMetrics {
225 pub average_path_length: f64,
226 pub clustering_coefficient: f64,
227 pub graph_density: f64,
228 pub diameter: usize,
229 pub spectral_gap: f64,
230 pub centrality_measures: HashMap<usize, CentralityMeasures>,
231}
232
233#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct CentralityMeasures {
236 pub betweenness: f64,
237 pub closeness: f64,
238 pub eigenvector: f64,
239 pub degree: f64,
240}
241
242#[derive(Debug, Clone, Serialize, Deserialize)]
244pub struct CriticalPath {
245 pub path: Vec<usize>,
246 pub length: usize,
247 pub bottleneck_qubits: Vec<usize>,
248 pub expected_performance_impact: f64,
249}
250
251#[derive(Debug, Clone, Serialize, Deserialize)]
253pub struct SpectralProperties {
254 pub eigenvalues: Array1<f64>,
255 pub spectral_radius: f64,
256 pub algebraic_connectivity: f64,
257 pub spectral_gap: f64,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
262pub struct NoiseModelValidation {
263 pub model_accuracy: f64,
264 pub prediction_errors: Array1<f64>,
265 pub residual_analysis: ResidualAnalysis,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct ResidualAnalysis {
271 pub normality_test: TestResult,
272 pub autocorrelation: Array1<f64>,
273 pub heteroscedasticity_test: TestResult,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
278pub struct ScalabilityMetrics {
279 pub depth_scaling_coefficient: f64,
280 pub width_scaling_coefficient: f64,
281 pub resource_efficiency: f64,
282 pub parallelization_factor: f64,
283}
284
285pub struct HardwareBenchmarkSuite {
287 calibration_manager: CalibrationManager,
288 config: BenchmarkConfig,
289}
290
291impl HardwareBenchmarkSuite {
292 pub const fn new(calibration_manager: CalibrationManager, config: BenchmarkConfig) -> Self {
294 Self {
295 calibration_manager,
296 config,
297 }
298 }
299
300 pub async fn run_benchmark_suite<E: DeviceExecutor>(
302 &self,
303 device_id: &str,
304 executor: &E,
305 ) -> DeviceResult<BenchmarkSuite> {
306 let start_time = Instant::now();
307
308 let backend_capabilities = query_backend_capabilities(Self::get_backend_type(device_id)?);
310
311 let calibration = self
312 .calibration_manager
313 .get_calibration(device_id)
314 .ok_or_else(|| DeviceError::APIError("No calibration data".into()))?;
315
316 let mut benchmark_results = Vec::new();
318
319 for gate_type in &self.config.gate_types {
321 for &num_qubits in &self.config.qubit_counts {
322 if num_qubits <= backend_capabilities.features.max_qubits {
323 let result = self
324 .benchmark_gate_type(gate_type, num_qubits, executor, calibration)
325 .await?;
326 benchmark_results.push(result);
327 }
328 }
329 }
330
331 for &depth in &self.config.circuit_depths {
333 for &num_qubits in &self.config.qubit_counts {
334 if num_qubits <= backend_capabilities.features.max_qubits {
335 let result = self
336 .benchmark_circuit_depth(depth, num_qubits, executor, calibration)
337 .await?;
338 benchmark_results.push(result);
339 }
340 }
341 }
342
343 let random_results = self
345 .benchmark_random_circuits(executor, calibration)
346 .await?;
347 benchmark_results.extend(random_results);
348
349 let statistical_analysis = Self::perform_statistical_analysis(&benchmark_results)?;
351
352 let graph_analysis = if self.config.enable_graph_analysis {
354 Some(Self::perform_graph_analysis(
355 calibration,
356 &benchmark_results,
357 )?)
358 } else {
359 None
360 };
361
362 let noise_analysis = if self.config.enable_noise_analysis {
364 Some(Self::perform_noise_analysis(
365 calibration,
366 &benchmark_results,
367 )?)
368 } else {
369 None
370 };
371
372 let performance_metrics = Self::calculate_performance_metrics(
374 &benchmark_results,
375 &statistical_analysis,
376 graph_analysis.as_ref(),
377 )?;
378
379 let execution_time = start_time.elapsed();
380
381 Ok(BenchmarkSuite {
382 device_id: device_id.to_string(),
383 backend_capabilities,
384 config: self.config.clone(),
385 benchmark_results,
386 statistical_analysis,
387 graph_analysis,
388 noise_analysis,
389 performance_metrics,
390 execution_time,
391 })
392 }
393
394 async fn benchmark_gate_type<E: DeviceExecutor>(
396 &self,
397 gate_type: &str,
398 num_qubits: usize,
399 executor: &E,
400 calibration: &DeviceCalibration,
401 ) -> DeviceResult<BenchmarkResult> {
402 let mut execution_times = Vec::new();
403 let mut fidelities = Vec::new();
404 let mut error_rates = Vec::new();
405 let mut success_probabilities = Vec::new();
406 let mut queue_times = Vec::new();
407
408 for _ in 0..self.config.iterations {
409 let circuit: Circuit<8> = Self::create_gate_benchmark_circuit(gate_type, num_qubits)?;
410
411 let queue_start = Instant::now();
412 let exec_start = Instant::now();
413
414 let result = executor.execute_circuit(&circuit, 1000).await?;
416
417 let exec_time = exec_start.elapsed().as_secs_f64();
418 let queue_time = queue_start.elapsed().as_secs_f64();
419
420 let fidelity = Self::calculate_fidelity(&result, &circuit, calibration)?;
422 let error_rate = 1.0 - fidelity;
423 let success_prob = Self::calculate_success_probability(&result)?;
424
425 execution_times.push(exec_time);
426 fidelities.push(fidelity);
427 error_rates.push(error_rate);
428 success_probabilities.push(success_prob);
429 queue_times.push(queue_time);
430 }
431
432 Ok(BenchmarkResult {
433 name: format!("{gate_type}_gate_benchmark"),
434 circuit_description: format!("{gate_type} gate on {num_qubits} qubits"),
435 num_qubits,
436 circuit_depth: 1,
437 gate_count: 1,
438 execution_times,
439 fidelities,
440 error_rates,
441 success_probabilities,
442 queue_times,
443 })
444 }
445
446 async fn benchmark_circuit_depth<E: DeviceExecutor>(
448 &self,
449 depth: usize,
450 num_qubits: usize,
451 executor: &E,
452 calibration: &DeviceCalibration,
453 ) -> DeviceResult<BenchmarkResult> {
454 let mut execution_times = Vec::new();
455 let mut fidelities = Vec::new();
456 let mut error_rates = Vec::new();
457 let mut success_probabilities = Vec::new();
458 let mut queue_times = Vec::new();
459
460 for _ in 0..self.config.iterations {
461 let circuit: Circuit<8> = Self::create_depth_benchmark_circuit(depth, num_qubits)?;
462
463 let queue_start = Instant::now();
464 let exec_start = Instant::now();
465
466 let result = executor.execute_circuit(&circuit, 1000).await?;
467
468 let exec_time = exec_start.elapsed().as_secs_f64();
469 let queue_time = queue_start.elapsed().as_secs_f64();
470
471 let fidelity = Self::calculate_fidelity(&result, &circuit, calibration)?;
472 let error_rate = 1.0 - fidelity;
473 let success_prob = Self::calculate_success_probability(&result)?;
474
475 execution_times.push(exec_time);
476 fidelities.push(fidelity);
477 error_rates.push(error_rate);
478 success_probabilities.push(success_prob);
479 queue_times.push(queue_time);
480 }
481
482 Ok(BenchmarkResult {
483 name: format!("depth_{depth}_benchmark"),
484 circuit_description: format!("Depth {depth} circuit on {num_qubits} qubits"),
485 num_qubits,
486 circuit_depth: depth,
487 gate_count: depth * num_qubits / 2, execution_times,
489 fidelities,
490 error_rates,
491 success_probabilities,
492 queue_times,
493 })
494 }
495
496 async fn benchmark_random_circuits<E: DeviceExecutor>(
498 &self,
499 executor: &E,
500 calibration: &DeviceCalibration,
501 ) -> DeviceResult<Vec<BenchmarkResult>> {
502 let mut results = Vec::new();
503
504 for &num_qubits in &self.config.qubit_counts {
506 for &depth in &self.config.circuit_depths {
507 if num_qubits <= 8 && depth <= 50 {
508 let result = self
510 .benchmark_random_circuit_volume(num_qubits, depth, executor, calibration)
511 .await?;
512 results.push(result);
513 }
514 }
515 }
516
517 Ok(results)
518 }
519
520 async fn benchmark_random_circuit_volume<E: DeviceExecutor>(
522 &self,
523 num_qubits: usize,
524 depth: usize,
525 executor: &E,
526 calibration: &DeviceCalibration,
527 ) -> DeviceResult<BenchmarkResult> {
528 let mut execution_times = Vec::new();
529 let mut fidelities = Vec::new();
530 let mut error_rates = Vec::new();
531 let mut success_probabilities = Vec::new();
532 let mut queue_times = Vec::new();
533
534 for _ in 0..self.config.iterations.min(20) {
535 let circuit = Self::create_random_circuit(num_qubits, depth)?;
537
538 let queue_start = Instant::now();
539 let exec_start = Instant::now();
540
541 let result = executor.execute_circuit(&circuit, 1000).await?;
542
543 let exec_time = exec_start.elapsed().as_secs_f64();
544 let queue_time = queue_start.elapsed().as_secs_f64();
545
546 let fidelity = Self::calculate_fidelity(&result, &circuit, calibration)?;
547 let error_rate = 1.0 - fidelity;
548 let success_prob = Self::calculate_success_probability(&result)?;
549
550 execution_times.push(exec_time);
551 fidelities.push(fidelity);
552 error_rates.push(error_rate);
553 success_probabilities.push(success_prob);
554 queue_times.push(queue_time);
555 }
556
557 Ok(BenchmarkResult {
558 name: format!("random_circuit_{num_qubits}q_{depth}d"),
559 circuit_description: format!("Random {num_qubits} qubit, depth {depth} circuit"),
560 num_qubits,
561 circuit_depth: depth,
562 gate_count: depth * num_qubits,
563 execution_times,
564 fidelities,
565 error_rates,
566 success_probabilities,
567 queue_times,
568 })
569 }
570
571 fn perform_statistical_analysis(
573 results: &[BenchmarkResult],
574 ) -> DeviceResult<StatisticalAnalysis> {
575 let all_exec_times: Vec<f64> = results
577 .iter()
578 .flat_map(|r| r.execution_times.iter())
579 .copied()
580 .collect();
581
582 let all_fidelities: Vec<f64> = results
583 .iter()
584 .flat_map(|r| r.fidelities.iter())
585 .copied()
586 .collect();
587
588 let all_error_rates: Vec<f64> = results
589 .iter()
590 .flat_map(|r| r.error_rates.iter())
591 .copied()
592 .collect();
593
594 let exec_times_array = Array1::from_vec(all_exec_times);
596 let fidelities_array = Array1::from_vec(all_fidelities);
597 let error_rates_array = Array1::from_vec(all_error_rates);
598
599 let execution_time_stats = Self::compute_descriptive_stats(&exec_times_array)?;
601 let fidelity_stats = Self::compute_descriptive_stats(&fidelities_array)?;
602 let error_rate_stats = Self::compute_descriptive_stats(&error_rates_array)?;
603
604 let exec_slice = exec_times_array
606 .as_slice()
607 .ok_or_else(|| DeviceError::APIError("Failed to get exec_times slice".to_string()))?;
608 let fid_slice = fidelities_array
609 .as_slice()
610 .ok_or_else(|| DeviceError::APIError("Failed to get fidelities slice".to_string()))?;
611 let err_slice = error_rates_array
612 .as_slice()
613 .ok_or_else(|| DeviceError::APIError("Failed to get error_rates slice".to_string()))?;
614
615 let data_matrix = Array2::from_shape_vec(
616 (exec_times_array.len(), 3),
617 [exec_slice, fid_slice, err_slice].concat(),
618 )
619 .map_err(|e| DeviceError::APIError(format!("Array creation error: {e}")))?;
620
621 let correlationmatrix = correlationmatrix(&data_matrix.view(), None)
622 .map_err(|e| DeviceError::APIError(format!("Correlation computation error: {e:?}")))?;
623
624 let mut statistical_tests = HashMap::new();
626
627 if exec_times_array.len() >= 8 {
629 let baseline_time = 1.0; let t_test = ttest_1samp(
632 &exec_times_array.view(),
633 baseline_time,
634 Alternative::TwoSided,
635 "propagate",
636 )
637 .map_err(|e| DeviceError::APIError(format!("T-test error: {e:?}")))?;
638
639 statistical_tests.insert(
640 "execution_time_t_test".to_string(),
641 TestResult {
642 test_name: "One-sample t-test (execution time)".to_string(),
643 statistic: t_test.statistic,
644 p_value: t_test.pvalue,
645 significant: t_test.pvalue < 0.05,
646 effect_size: Some(
647 (execution_time_stats.mean - baseline_time) / execution_time_stats.std_dev,
648 ),
649 },
650 );
651 }
652
653 let mut distribution_fits = HashMap::new();
655
656 if let Ok(normal_dist) =
658 distributions::norm(execution_time_stats.mean, execution_time_stats.std_dev)
659 {
660 let goodness_of_fit = Self::calculate_goodness_of_fit(&exec_times_array, &normal_dist)?;
662
663 distribution_fits.insert(
664 "execution_time_normal".to_string(),
665 DistributionFit {
666 distribution_name: "Normal".to_string(),
667 parameters: vec![execution_time_stats.mean, execution_time_stats.std_dev],
668 goodness_of_fit,
669 p_value: 0.5, },
671 );
672 }
673
674 Ok(StatisticalAnalysis {
675 execution_time_stats,
676 fidelity_stats,
677 error_rate_stats,
678 correlationmatrix,
679 statistical_tests,
680 distribution_fits,
681 })
682 }
683
684 fn perform_graph_analysis(
686 calibration: &DeviceCalibration,
687 results: &[BenchmarkResult],
688 ) -> DeviceResult<GraphAnalysis> {
689 let mut graph = Graph::new();
691 let mut node_map = HashMap::new();
692
693 for i in 0..calibration.topology.num_qubits {
695 let node = graph.add_node(i);
696 node_map.insert(i, node);
697 }
698
699 for &(q1, q2) in &calibration.topology.coupling_map {
701 let q1_idx = q1.0 as usize;
702 let q2_idx = q2.0 as usize;
703 if let (Some(&n1), Some(&n2)) = (node_map.get(&q1_idx), node_map.get(&q2_idx)) {
704 let _ = graph.add_edge(n1.index(), n2.index(), 1.0);
705 }
706 }
707
708 let graph_density_val = graph_density(&graph)
710 .map_err(|e| DeviceError::APIError(format!("Graph density error: {e:?}")))?;
711
712 let clustering_coeff = clustering_coefficient(&graph)
713 .map_err(|e| DeviceError::APIError(format!("Clustering coefficient error: {e:?}")))?;
714
715 let betweenness_values = betweenness_centrality(&graph, false);
717
718 let closeness_values = closeness_centrality(&graph, true);
719
720 let mut centrality_measures = HashMap::new();
721 for i in 0..calibration.topology.num_qubits {
722 if let Some(&node) = node_map.get(&i) {
723 let node_idx = node.index();
724 centrality_measures.insert(
725 i,
726 CentralityMeasures {
727 betweenness: betweenness_values.get(&node_idx).copied().unwrap_or(0.0),
728 closeness: closeness_values.get(&node_idx).copied().unwrap_or(0.0),
729 eigenvector: 0.0, degree: graph
731 .neighbors(&node_idx)
732 .map(|neighbors| neighbors.len() as f64)
733 .unwrap_or(0.0),
734 },
735 );
736 }
737 }
738
739 let spectral_properties = Self::calculate_spectral_properties(calibration)?;
741
742 let compatible_node_map: HashMap<usize, usize> =
744 node_map.iter().map(|(&k, &v)| (k, v.index())).collect();
745 let critical_paths = Self::find_critical_paths(&graph, &compatible_node_map, results)?;
746
747 let mut topology_correlations = HashMap::new();
749
750 let connectivity_scores: Vec<f64> = centrality_measures
752 .values()
753 .map(|c| c.betweenness + c.closeness)
754 .collect();
755
756 let avg_fidelities: Vec<f64> = results
757 .iter()
758 .take(connectivity_scores.len())
759 .map(|r| r.fidelities.iter().sum::<f64>() / r.fidelities.len() as f64)
760 .collect();
761
762 if connectivity_scores.len() == avg_fidelities.len() && connectivity_scores.len() >= 3 {
763 let conn_array = Array1::from_vec(connectivity_scores);
764 let fid_array = Array1::from_vec(avg_fidelities);
765
766 if let Ok((correlation, _)) =
767 pearsonr(&conn_array.view(), &fid_array.view(), "two-sided")
768 {
769 topology_correlations.insert("connectivity_fidelity".to_string(), correlation);
770 }
771 }
772
773 let connectivity_metrics = ConnectivityMetrics {
774 average_path_length: 0.0, clustering_coefficient: clustering_coeff.values().sum::<f64>()
776 / clustering_coeff.len() as f64,
777 graph_density: graph_density_val,
778 diameter: 0, spectral_gap: spectral_properties.spectral_gap,
780 centrality_measures,
781 };
782
783 Ok(GraphAnalysis {
784 connectivity_metrics,
785 topology_correlations,
786 critical_paths,
787 spectral_properties,
788 })
789 }
790
791 fn perform_noise_analysis(
793 calibration: &DeviceCalibration,
794 results: &[BenchmarkResult],
795 ) -> DeviceResult<NoiseAnalysis> {
796 let num_qubits = calibration.topology.num_qubits;
797
798 let crosstalk_correlations = Array2::from_shape_fn((num_qubits, num_qubits), |(i, j)| {
800 if i == j {
801 1.0
802 } else {
803 calibration
805 .crosstalk_matrix
806 .matrix
807 .get(i)
808 .and_then(|row| row.get(j))
809 .copied()
810 .unwrap_or(0.0)
811 }
812 });
813
814 let temporal_correlations = Array1::zeros(results.len());
816
817 let mut spatial_patterns = HashMap::new();
819
820 let error_pattern = Array2::from_shape_fn((num_qubits, num_qubits), |(i, j)| {
822 let distance = ((i as f64) - (j as f64)).abs();
824 (-distance * 0.1).exp() * 0.01 });
826 spatial_patterns.insert("error_rates".to_string(), error_pattern);
827
828 let noise_model = CalibrationNoiseModel::from_calibration(calibration);
830 let model_validation = Self::validate_noise_model(&noise_model, results)?;
831
832 Ok(NoiseAnalysis {
833 crosstalk_correlations,
834 temporal_correlations,
835 spatial_patterns,
836 model_validation,
837 })
838 }
839
840 fn calculate_performance_metrics(
842 results: &[BenchmarkResult],
843 stats: &StatisticalAnalysis,
844 graph_analysis: Option<&GraphAnalysis>,
845 ) -> DeviceResult<PerformanceMetrics> {
846 let reliability_score = (stats.fidelity_stats.mean * 100.0).min(100.0);
848 let speed_score = (1.0 / stats.execution_time_stats.mean * 10.0).min(100.0);
849 let accuracy_score = ((1.0 - stats.error_rate_stats.mean) * 100.0).min(100.0);
850
851 let efficiency_score = graph_analysis.map_or(50.0, |graph| {
853 (graph.connectivity_metrics.clustering_coefficient * 100.0).min(100.0)
855 });
856
857 let scalability_metrics = Self::calculate_scalability_metrics(results)?;
859
860 let overall_score = (reliability_score * 0.3
862 + speed_score * 0.2
863 + accuracy_score * 0.3
864 + efficiency_score * 0.2)
865 .min(100.0);
866
867 Ok(PerformanceMetrics {
868 overall_score,
869 reliability_score,
870 speed_score,
871 accuracy_score,
872 efficiency_score,
873 scalability_metrics,
874 })
875 }
876
877 fn get_backend_type(device_id: &str) -> DeviceResult<crate::translation::HardwareBackend> {
880 if device_id.contains("ibm") {
882 Ok(crate::translation::HardwareBackend::IBMQuantum)
883 } else if device_id.contains("ionq") {
884 Ok(crate::translation::HardwareBackend::IonQ)
885 } else {
886 Ok(crate::translation::HardwareBackend::IBMQuantum) }
888 }
889
890 fn create_gate_benchmark_circuit(
891 gate_type: &str,
892 num_qubits: usize,
893 ) -> DeviceResult<Circuit<8>> {
894 let mut circuit = Circuit::<8>::new();
895
896 match gate_type {
897 "H" => {
898 let _ = circuit.h(QubitId(0));
899 }
900 "CNOT" => {
901 let _ = circuit.cnot(QubitId(0), QubitId(1));
902 }
903 "RZ" => {
904 let _ = circuit.rz(QubitId(0), std::f64::consts::PI / 4.0);
905 }
906 _ => {
907 return Err(DeviceError::UnsupportedOperation(format!(
908 "Gate type: {gate_type}"
909 )))
910 }
911 }
912
913 Ok(circuit)
914 }
915
916 fn create_depth_benchmark_circuit(depth: usize, num_qubits: usize) -> DeviceResult<Circuit<8>> {
917 let mut circuit = Circuit::<8>::new();
918
919 for layer in 0..depth {
920 for qubit in 0..num_qubits {
921 if layer % 2 == 0 {
922 let _ = circuit.h(QubitId(qubit as u32));
923 } else if qubit + 1 < num_qubits {
924 let _ = circuit.cnot(QubitId(qubit as u32), QubitId((qubit + 1) as u32));
925 }
926 }
927 }
928
929 Ok(circuit)
930 }
931
932 fn create_random_circuit<const N: usize>(
933 num_qubits: usize,
934 depth: usize,
935 ) -> DeviceResult<Circuit<N>> {
936 use scirs2_core::random::prelude::*;
937 let mut rng = thread_rng();
938 let mut circuit = Circuit::<N>::new();
939
940 let gates = ["H", "X", "Y", "Z", "CNOT", "RZ"];
941
942 for _ in 0..depth {
943 let gate_idx = rng.gen_range(0..gates.len());
944 let gate = gates[gate_idx];
945 let qubit = rng.gen_range(0..num_qubits) as u32;
946
947 match gate {
948 "H" => {
949 let _ = circuit.h(QubitId(qubit));
950 }
951 "X" => {
952 let _ = circuit.x(QubitId(qubit));
953 }
954 "Y" => {
955 let _ = circuit.y(QubitId(qubit));
956 }
957 "Z" => {
958 let _ = circuit.z(QubitId(qubit));
959 }
960 "CNOT" => {
961 let target = rng.gen_range(0..num_qubits) as u32;
962 if target != qubit {
963 let _ = circuit.cnot(QubitId(qubit), QubitId(target));
964 }
965 }
966 "RZ" => {
967 let angle = rng.gen_range(-std::f64::consts::PI..std::f64::consts::PI);
968 let _ = circuit.rz(QubitId(qubit), angle);
969 }
970 _ => {}
971 }
972 }
973
974 Ok(circuit)
975 }
976
977 fn calculate_fidelity(
978 result: &CircuitResult,
979 circuit: &Circuit<8>,
980 calibration: &DeviceCalibration,
981 ) -> DeviceResult<f64> {
982 let mut total_fidelity = 1.0;
987
988 for gate in circuit.gates() {
989 let qubits = gate.qubits();
990
991 if qubits.len() == 1 {
992 if let Some(gate_cal) = calibration.single_qubit_gates.get(gate.name()) {
993 if let Some(qubit_data) = gate_cal.qubit_data.get(&qubits[0]) {
994 total_fidelity *= qubit_data.fidelity;
995 }
996 }
997 } else if qubits.len() == 2 {
998 let qubit_pair = (qubits[0], qubits[1]);
999 if let Some(gate_cal) = calibration.two_qubit_gates.get(&qubit_pair) {
1000 total_fidelity *= gate_cal.fidelity;
1001 }
1002 }
1003 }
1004
1005 Ok(total_fidelity)
1006 }
1007
1008 fn calculate_success_probability(result: &CircuitResult) -> DeviceResult<f64> {
1009 let total_shots = result.shots as f64;
1011 let successful_outcomes = result
1012 .counts
1013 .values()
1014 .map(|&count| count as f64)
1015 .sum::<f64>();
1016
1017 Ok(successful_outcomes / total_shots)
1018 }
1019
1020 fn compute_descriptive_stats(data: &Array1<f64>) -> DeviceResult<DescriptiveStats> {
1021 let mean_val = mean(&data.view())
1022 .map_err(|e| DeviceError::APIError(format!("Mean calculation error: {e:?}")))?;
1023
1024 let median_val = median(&data.view())
1025 .map_err(|e| DeviceError::APIError(format!("Median calculation error: {e:?}")))?;
1026
1027 let std_val = std(&data.view(), 1, None)
1028 .map_err(|e| DeviceError::APIError(format!("Std calculation error: {e:?}")))?;
1029
1030 let var_val = var(&data.view(), 1, None)
1031 .map_err(|e| DeviceError::APIError(format!("Variance calculation error: {e:?}")))?;
1032
1033 let min_val = data.iter().fold(f64::INFINITY, |a, &b| a.min(b));
1034 let max_val = data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
1035
1036 let mut sorted_data = data.to_vec();
1038 sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1039
1040 let n = sorted_data.len();
1041 let q25 = sorted_data[n / 4];
1042 let q75 = sorted_data[3 * n / 4];
1043
1044 let t_critical = 1.96; let sem = std_val / (n as f64).sqrt();
1047 let margin = t_critical * sem;
1048 let confidence_interval = (mean_val - margin, mean_val + margin);
1049
1050 Ok(DescriptiveStats {
1051 mean: mean_val,
1052 median: median_val,
1053 std_dev: std_val,
1054 variance: var_val,
1055 min: min_val,
1056 max: max_val,
1057 q25,
1058 q75,
1059 confidence_interval,
1060 })
1061 }
1062
1063 fn calculate_goodness_of_fit(
1064 data: &Array1<f64>,
1065 distribution: &dyn scirs2_stats::traits::Distribution<f64>,
1066 ) -> DeviceResult<f64> {
1067 let mut sorted_data = data.to_vec();
1071 sorted_data.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1072
1073 let n = sorted_data.len() as f64;
1074 let mut max_diff: f64 = 0.0;
1075
1076 for (i, &value) in sorted_data.iter().enumerate() {
1077 let empirical_cdf = (i + 1) as f64 / n;
1078 let theoretical_cdf = 0.5; let diff = (empirical_cdf - theoretical_cdf).abs();
1080 max_diff = max_diff.max(diff);
1081 }
1082
1083 Ok(1.0 - max_diff) }
1085
1086 fn calculate_spectral_properties(
1087 calibration: &DeviceCalibration,
1088 ) -> DeviceResult<SpectralProperties> {
1089 let n = calibration.topology.num_qubits;
1090
1091 let mut adj_matrix = Array2::zeros((n, n));
1093 for &(q1, q2) in &calibration.topology.coupling_map {
1094 adj_matrix[[q1.0 as usize, q2.0 as usize]] = 1.0;
1095 adj_matrix[[q2.0 as usize, q1.0 as usize]] = 1.0;
1096 }
1097
1098 let eigenvalues = match eigvals(&adj_matrix.view(), None) {
1100 Ok(vals) => vals.mapv(|c| c.re), Err(_) => Array1::zeros(n), };
1103
1104 let spectral_radius = eigenvalues
1105 .iter()
1106 .fold(0.0_f64, |max, &val: &f64| max.max(val.abs()));
1107
1108 let mut sorted_eigenvals = eigenvalues.to_vec();
1110 sorted_eigenvals.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
1111
1112 let algebraic_connectivity = if sorted_eigenvals.len() > 1 {
1113 sorted_eigenvals[sorted_eigenvals.len() - 2]
1114 } else {
1115 0.0
1116 };
1117
1118 let spectral_gap = if sorted_eigenvals.len() > 1 {
1119 sorted_eigenvals[0] - sorted_eigenvals[1]
1120 } else {
1121 0.0
1122 };
1123
1124 Ok(SpectralProperties {
1125 eigenvalues: Array1::from_vec(sorted_eigenvals),
1126 spectral_radius,
1127 algebraic_connectivity,
1128 spectral_gap,
1129 })
1130 }
1131
1132 fn find_critical_paths(
1133 graph: &Graph<usize, f64>,
1134 node_map: &HashMap<usize, usize>,
1135 results: &[BenchmarkResult],
1136 ) -> DeviceResult<Vec<CriticalPath>> {
1137 let mut critical_paths = Vec::new();
1138
1139 for i in 0..3 {
1143 let path = vec![i, i + 1, i + 2]; critical_paths.push(CriticalPath {
1147 path: path.clone(),
1148 length: path.len(),
1149 bottleneck_qubits: vec![i + 1], expected_performance_impact: 0.1 * (i + 1) as f64,
1151 });
1152 }
1153
1154 Ok(critical_paths)
1155 }
1156
1157 fn validate_noise_model(
1158 noise_model: &CalibrationNoiseModel,
1159 results: &[BenchmarkResult],
1160 ) -> DeviceResult<NoiseModelValidation> {
1161 let predicted_errors: Vec<f64> = results
1163 .iter()
1164 .map(|r| {
1165 let base_error = 0.001;
1167 base_error * r.gate_count as f64
1168 })
1169 .collect();
1170
1171 let actual_errors: Vec<f64> = results
1172 .iter()
1173 .map(|r| r.error_rates.iter().sum::<f64>() / r.error_rates.len() as f64)
1174 .collect();
1175
1176 let prediction_errors = Array1::from_vec(
1177 predicted_errors
1178 .iter()
1179 .zip(actual_errors.iter())
1180 .map(|(pred, actual)| (pred - actual).abs())
1181 .collect(),
1182 );
1183
1184 let model_accuracy = 1.0 - (prediction_errors.mean().unwrap_or(0.5));
1185
1186 let residuals = Array1::from_vec(
1188 predicted_errors
1189 .iter()
1190 .zip(actual_errors.iter())
1191 .map(|(pred, actual)| pred - actual)
1192 .collect(),
1193 );
1194
1195 let normality_test = TestResult {
1196 test_name: "Residual normality test".to_string(),
1197 statistic: 0.0,
1198 p_value: 0.5,
1199 significant: false,
1200 effect_size: None,
1201 };
1202
1203 let residual_analysis = ResidualAnalysis {
1204 normality_test,
1205 autocorrelation: Array1::zeros(5), heteroscedasticity_test: TestResult {
1207 test_name: "Heteroscedasticity test".to_string(),
1208 statistic: 0.0,
1209 p_value: 0.5,
1210 significant: false,
1211 effect_size: None,
1212 },
1213 };
1214
1215 Ok(NoiseModelValidation {
1216 model_accuracy,
1217 prediction_errors,
1218 residual_analysis,
1219 })
1220 }
1221
1222 fn calculate_scalability_metrics(
1223 results: &[BenchmarkResult],
1224 ) -> DeviceResult<ScalabilityMetrics> {
1225 let mut depth_times = HashMap::new();
1229 let mut width_times = HashMap::new();
1230
1231 for result in results {
1232 depth_times
1233 .entry(result.circuit_depth)
1234 .or_insert_with(Vec::new)
1235 .extend(&result.execution_times);
1236
1237 width_times
1238 .entry(result.num_qubits)
1239 .or_insert_with(Vec::new)
1240 .extend(&result.execution_times);
1241 }
1242
1243 let depth_scaling_coefficient = Self::calculate_scaling_coefficient(&depth_times)?;
1245 let width_scaling_coefficient = Self::calculate_scaling_coefficient(&width_times)?;
1246
1247 let resource_efficiency =
1248 1.0 / (depth_scaling_coefficient + width_scaling_coefficient).max(0.1);
1249 let parallelization_factor = 0.8; Ok(ScalabilityMetrics {
1252 depth_scaling_coefficient,
1253 width_scaling_coefficient,
1254 resource_efficiency,
1255 parallelization_factor,
1256 })
1257 }
1258
1259 fn calculate_scaling_coefficient(data: &HashMap<usize, Vec<f64>>) -> DeviceResult<f64> {
1260 if data.len() < 2 {
1261 return Ok(1.0); }
1263
1264 let mut x_values = Vec::new();
1268 let mut y_values = Vec::new();
1269
1270 for (&size, times) in data {
1271 let avg_time = times.iter().sum::<f64>() / times.len() as f64;
1272 x_values.push(size as f64);
1273 y_values.push(avg_time);
1274 }
1275
1276 let n = x_values.len() as f64;
1278 let sum_x: f64 = x_values.iter().sum();
1279 let sum_y: f64 = y_values.iter().sum();
1280 let sum_xy: f64 = x_values
1281 .iter()
1282 .zip(y_values.iter())
1283 .map(|(x, y)| x * y)
1284 .sum();
1285 let sum_x_sq: f64 = x_values.iter().map(|x| x * x).sum();
1286
1287 let slope = n.mul_add(sum_xy, -(sum_x * sum_y)) / n.mul_add(sum_x_sq, -(sum_x * sum_x));
1288
1289 Ok(slope.max(0.1)) }
1291}
1292
1293#[async_trait::async_trait]
1295pub trait DeviceExecutor {
1296 async fn execute_circuit<const N: usize>(
1297 &self,
1298 circuit: &Circuit<N>,
1299 shots: usize,
1300 ) -> DeviceResult<CircuitResult>;
1301}
1302
1303#[cfg(test)]
1304mod tests {
1305 use super::*;
1306 use crate::calibration::create_ideal_calibration;
1307
1308 #[test]
1309 fn test_benchmark_config_default() {
1310 let config = BenchmarkConfig::default();
1311 assert_eq!(config.iterations, 100);
1312 assert!(config.enable_advanced_stats);
1313 }
1314
1315 #[test]
1316 fn test_descriptive_stats_calculation() {
1317 let data = Array1::from_vec(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
1318 let suite =
1319 HardwareBenchmarkSuite::new(CalibrationManager::new(), BenchmarkConfig::default());
1320
1321 let stats = HardwareBenchmarkSuite::compute_descriptive_stats(&data)
1322 .expect("Descriptive stats computation should succeed");
1323 assert!((stats.mean - 3.0).abs() < 1e-10);
1324 assert!((stats.median - 3.0).abs() < 1e-10);
1325 }
1326
1327 #[test]
1328 fn test_spectral_properties() {
1329 let calibration = create_ideal_calibration("test".to_string(), 4);
1330 let suite =
1331 HardwareBenchmarkSuite::new(CalibrationManager::new(), BenchmarkConfig::default());
1332
1333 let spectral_props = HardwareBenchmarkSuite::calculate_spectral_properties(&calibration)
1334 .expect("Spectral properties calculation should succeed");
1335 assert!(spectral_props.eigenvalues.len() == 4);
1336 assert!(spectral_props.spectral_radius >= 0.0);
1337 }
1338}