Skip to main content

quantrs2_device/unified_benchmarking/
analysis.rs

1//! Analysis utilities for the unified benchmarking system
2//!
3//! This module provides helper constructors, default-value builders, and
4//! pure analysis functions used by `system.rs`. Keeping them here prevents
5//! `system.rs` from growing past the 2 000-line limit.
6
7use std::collections::HashMap;
8use std::time::Duration;
9
10use scirs2_core::ndarray::Array2;
11
12use super::results::{
13    AccuracyComparison, AlgorithmLevelResults, AnomalyDetectionResults, BarrenPlateauAnalysis,
14    BreakEvenAnalysis, CapacityPlanningResult, CapacityRecommendation, CentralityAnalysisResult,
15    CircuitLevelResults, ClassicalComparisonResult, ClassificationResults, ClusteringResults,
16    CommunityDetectionResult, ConnectivityAnalysisResult, ConvergenceAnalysis,
17    CorrelationAnalysisResult, CostAnalysisResult, CostMetrics, CostOptimizationAnalysisResult,
18    CrossEntropyResult, CrossPlatformAnalysis, CrossPlatformComparison, CrossValidationResult,
19    DepthScalingResult, EnsembleResult, ExponentialFit, FailurePattern, FeatureImportanceResults,
20    ForecastingResults, GateLevelResults, GraphAnalysisResult, HeavyOutputResult,
21    HypothesisTestResult, LinearRegressionResult, MLAnalysisResult, MLModelResult,
22    MLRegressionResults, ModelComparisonResult, NISQPerformanceResult, NonlinearRegressionResult,
23    OptimizationAnalysisResult, OptimizationResult, ParameterSensitivityAnalysis,
24    ParetoAnalysisResult, PerturbationResult, PlatformBenchmarkResult, PlatformPerformanceMetrics,
25    PlatformRanking, PolynomialFit, QuantumAdvantageResult, ROIAnalysis, ROIAnalysisResult,
26    RandomizedBenchmarkingResult, RegressionAnalysisResult, ReliabilityMetrics,
27    ResourceAnalysisResult, ResourceUtilizationMetrics, RobustnessAnalysisResult,
28    ScalabilityAnalysis, ScalingMetric, SciRS2AnalysisResult, SeasonalityAnalysisResult,
29    SensitivityAnalysisResult, StabilityAnalysis, StationarityTestResults,
30    StatisticalAnalysisResult, StatisticalSummary, SystemCostEfficiency, SystemLevelResults,
31    SystemReliabilityAnalysis, SystemResourceUtilization, SystemScalabilityAnalysis,
32    TimeSeriesAnalysisResult, TopologyOptimizationResult, TrendAnalysisResult,
33    UncertaintyPropagation, VariationalAlgorithmResult, VolumeBenchmarkResult, WidthScalingResult,
34};
35use super::types::QuantumPlatform;
36
37// ─── Primitive builders ───────────────────────────────────────────────────────
38
39/// Build a zero-valued `StatisticalSummary`.
40pub fn zero_statistical_summary() -> StatisticalSummary {
41    StatisticalSummary {
42        mean: 0.0,
43        std_dev: 0.0,
44        median: 0.0,
45        min: 0.0,
46        max: 0.0,
47        percentiles: HashMap::new(),
48        confidence_interval: (0.0, 0.0),
49    }
50}
51
52/// Build a `StatisticalSummary` from a non-empty slice of `f64` values.
53/// If the slice is empty, returns a zero summary.
54pub fn statistical_summary_from_slice(values: &[f64]) -> StatisticalSummary {
55    if values.is_empty() {
56        return zero_statistical_summary();
57    }
58    let n = values.len() as f64;
59    let mean = values.iter().sum::<f64>() / n;
60    let variance = values.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / n;
61    let std_dev = variance.sqrt();
62    let mut sorted = values.to_vec();
63    sorted.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
64    let min = sorted[0];
65    let max = *sorted.last().unwrap_or(&0.0);
66    let median = if sorted.len() % 2 == 0 {
67        (sorted[sorted.len() / 2 - 1] + sorted[sorted.len() / 2]) / 2.0
68    } else {
69        sorted[sorted.len() / 2]
70    };
71    let p95_idx = ((sorted.len() as f64 * 0.95) as usize).min(sorted.len() - 1);
72    let p50_idx = ((sorted.len() as f64 * 0.50) as usize).min(sorted.len() - 1);
73    let mut percentiles = HashMap::new();
74    percentiles.insert(50u8, sorted[p50_idx]);
75    percentiles.insert(95u8, sorted[p95_idx]);
76    // 95 % confidence interval (approximate, normal assumption)
77    let ci_half = 1.96 * std_dev / n.sqrt();
78    StatisticalSummary {
79        mean,
80        std_dev,
81        median,
82        min,
83        max,
84        percentiles,
85        confidence_interval: (mean - ci_half, mean + ci_half),
86    }
87}
88
89// ─── Default result constructors ─────────────────────────────────────────────
90
91/// Build a minimal, valid `GateLevelResults` representing a device that
92/// has been characterised with generic default values.
93pub fn default_gate_level_results() -> GateLevelResults {
94    GateLevelResults {
95        single_qubit_results: HashMap::new(),
96        two_qubit_results: HashMap::new(),
97        multi_qubit_results: HashMap::new(),
98        randomized_benchmarking: RandomizedBenchmarkingResult {
99            clifford_fidelity: 0.99,
100            decay_parameter: 0.001,
101            confidence_interval: (0.985, 0.995),
102            sequence_lengths: vec![1, 2, 4, 8, 16],
103            survival_probabilities: vec![1.0, 0.998, 0.992, 0.984, 0.968],
104        },
105        process_tomography: None,
106    }
107}
108
109/// Build a minimal, valid `CircuitLevelResults`.
110pub fn default_circuit_level_results() -> CircuitLevelResults {
111    CircuitLevelResults {
112        depth_scaling: DepthScalingResult {
113            depth_vs_fidelity: vec![(1, 0.99), (10, 0.90), (50, 0.70)],
114            depth_vs_execution_time: vec![
115                (1, Duration::from_micros(50)),
116                (10, Duration::from_micros(500)),
117                (50, Duration::from_millis(3)),
118            ],
119            scaling_exponent: 1.2,
120            coherence_limited_depth: 100,
121        },
122        width_scaling: WidthScalingResult {
123            width_vs_fidelity: vec![(1, 0.99), (5, 0.95), (20, 0.80)],
124            width_vs_execution_time: vec![
125                (1, Duration::from_micros(50)),
126                (5, Duration::from_micros(150)),
127                (20, Duration::from_millis(1)),
128            ],
129            scaling_exponent: 0.8,
130            connectivity_limited_width: 50,
131        },
132        circuit_type_results: HashMap::new(),
133        parametric_results: HashMap::new(),
134        volume_benchmarks: VolumeBenchmarkResult {
135            heavy_output: HeavyOutputResult {
136                heavy_output_probability: 0.66,
137                theoretical_threshold: 0.5,
138                statistical_significance: 0.95,
139            },
140            cross_entropy: CrossEntropyResult {
141                cross_entropy_benchmarking_fidelity: 0.95,
142                linear_xeb_fidelity: 0.94,
143                log_xeb_fidelity: 0.93,
144            },
145            quantum_volume: 32,
146        },
147    }
148}
149
150/// Build a minimal, valid `AlgorithmLevelResults`.
151pub fn default_algorithm_level_results() -> AlgorithmLevelResults {
152    AlgorithmLevelResults {
153        algorithm_results: HashMap::new(),
154        nisq_performance: NISQPerformanceResult {
155            noise_resilience: 0.85,
156            error_mitigation_effectiveness: 0.70,
157            depth_limited_performance: {
158                let mut m = HashMap::new();
159                m.insert(10_usize, 0.95_f64);
160                m.insert(50, 0.80);
161                m.insert(100, 0.60);
162                m
163            },
164            variational_optimization_convergence: ConvergenceAnalysis {
165                convergence_achieved: true,
166                iterations_to_convergence: Some(150),
167                final_cost: -1.0,
168                cost_history: vec![-0.1, -0.5, -0.8, -1.0],
169                gradient_norms: vec![0.5, 0.2, 0.05, 0.001],
170            },
171        },
172        quantum_advantage: QuantumAdvantageResult {
173            advantage_demonstrated: false,
174            speedup_factor: None,
175            confidence_level: 0.0,
176            problem_instances_tested: 0,
177        },
178        classical_comparison: ClassicalComparisonResult {
179            classical_runtime: Duration::from_secs(1),
180            quantum_runtime: Duration::from_millis(100),
181            speedup_ratio: 10.0,
182            accuracy_comparison: AccuracyComparison {
183                classical_accuracy: 1.0,
184                quantum_accuracy: 0.95,
185                relative_error: 0.05,
186            },
187        },
188        variational_algorithm_performance: VariationalAlgorithmResult {
189            optimization_landscapes: HashMap::new(),
190            convergence_analysis: ConvergenceAnalysis {
191                convergence_achieved: true,
192                iterations_to_convergence: Some(200),
193                final_cost: -0.9,
194                cost_history: vec![-0.1, -0.5, -0.8, -0.9],
195                gradient_norms: vec![0.5, 0.2, 0.05, 0.01],
196            },
197            parameter_sensitivity: ParameterSensitivityAnalysis {
198                sensitivity_matrix: Array2::eye(2),
199                most_sensitive_parameters: vec![0],
200                robustness_score: 0.75,
201            },
202            barren_plateau_analysis: BarrenPlateauAnalysis {
203                plateau_detected: false,
204                gradient_variance: 0.1,
205                effective_dimension: 4.0,
206                mitigation_strategies: vec![],
207            },
208        },
209    }
210}
211
212/// Build a minimal, valid `SystemLevelResults`.
213pub fn default_system_level_results(platform: &QuantumPlatform) -> SystemLevelResults {
214    SystemLevelResults {
215        cross_platform_comparison: CrossPlatformComparison {
216            platform_rankings: vec![PlatformRanking {
217                platform: platform.clone(),
218                overall_score: 0.85,
219                category_scores: {
220                    let mut m = HashMap::new();
221                    m.insert("fidelity".to_string(), 0.90);
222                    m.insert("speed".to_string(), 0.80);
223                    m
224                },
225                rank: 1,
226            }],
227            relative_performance: {
228                let mut m = HashMap::new();
229                m.insert(format!("{platform:?}"), 1.0);
230                m
231            },
232            statistical_significance: HashMap::new(),
233        },
234        resource_utilization: SystemResourceUtilization {
235            average_queue_time: Duration::from_secs(60),
236            throughput: 10.0,
237            utilization_rate: 0.75,
238            peak_usage_times: vec![],
239        },
240        reliability_analysis: SystemReliabilityAnalysis {
241            uptime: 0.995,
242            error_frequency: 0.001,
243            recovery_time: Duration::from_secs(300),
244            failure_patterns: vec![],
245        },
246        scalability_analysis: SystemScalabilityAnalysis {
247            max_supported_qubits: 127,
248            max_circuit_depth: 1000,
249            performance_scaling: HashMap::new(),
250        },
251        cost_efficiency: SystemCostEfficiency {
252            cost_per_shot: 0.0001,
253            cost_per_gate: 0.000001,
254            cost_efficiency_score: 0.80,
255            roi_analysis: ROIAnalysis {
256                investment_cost: 1000.0,
257                operational_cost: 100.0,
258                performance_benefit: 5000.0,
259                roi_ratio: 4.5,
260            },
261        },
262    }
263}
264
265// ─── Aggregate metrics calculators ───────────────────────────────────────────
266
267/// Compute `PlatformPerformanceMetrics` from the four benchmark result sets.
268pub fn compute_performance_metrics(
269    gate: &GateLevelResults,
270    circuit: &CircuitLevelResults,
271    algo: &AlgorithmLevelResults,
272    system: &SystemLevelResults,
273) -> PlatformPerformanceMetrics {
274    // Fidelity: use RB clifford fidelity as the primary signal.
275    let rb_fidelity = gate.randomized_benchmarking.clifford_fidelity;
276
277    // Error rate: 1 − fidelity is a simple lower bound.
278    let error_rate = (1.0 - rb_fidelity).max(0.0);
279
280    // Throughput: take from system-level resource utilisation.
281    let throughput = system.resource_utilization.throughput;
282
283    // Availability: taken from system reliability analysis.
284    let availability = system.reliability_analysis.uptime.clamp(0.0, 1.0);
285
286    // Average execution time: use the smallest depth point in depth-vs-exec-time.
287    let avg_execution_time = circuit
288        .depth_scaling
289        .depth_vs_execution_time
290        .first()
291        .map(|(_, d)| *d)
292        .unwrap_or(Duration::from_millis(100));
293
294    // Blend algo fidelity if available.
295    let fidelity = if !algo.nisq_performance.depth_limited_performance.is_empty() {
296        let vals: Vec<f64> = algo
297            .nisq_performance
298            .depth_limited_performance
299            .values()
300            .copied()
301            .collect();
302        let algo_fidelity = vals.iter().sum::<f64>() / vals.len() as f64;
303        (rb_fidelity + algo_fidelity) / 2.0
304    } else {
305        rb_fidelity
306    };
307
308    PlatformPerformanceMetrics {
309        overall_fidelity: fidelity.clamp(0.0, 1.0),
310        average_execution_time: avg_execution_time,
311        throughput,
312        error_rate,
313        availability,
314    }
315}
316
317/// Compute `ReliabilityMetrics` from the three benchmark result sets.
318pub fn compute_reliability_metrics(
319    gate: &GateLevelResults,
320    _circuit: &CircuitLevelResults,
321    _algo: &AlgorithmLevelResults,
322) -> ReliabilityMetrics {
323    let error_rate = (1.0 - gate.randomized_benchmarking.clifford_fidelity).max(0.0);
324    // MTBF: rough heuristic — 1 / error_rate hours if error_rate > 0.
325    let mtbf_hours = if error_rate > 1e-9 {
326        (1.0 / error_rate).min(876_000.0) // cap at 100 years in hours
327    } else {
328        876_000.0
329    };
330    let mtbf = Duration::from_secs_f64(mtbf_hours * 3600.0);
331    ReliabilityMetrics {
332        uptime: (1.0 - error_rate).clamp(0.0, 1.0),
333        mtbf,
334        mttr: Duration::from_secs(300), // default 5-minute recovery
335        availability: (1.0 - error_rate).clamp(0.0, 1.0),
336    }
337}
338
339/// Compute `CostMetrics` from the three benchmark result sets.
340pub fn compute_cost_metrics(
341    _gate: &GateLevelResults,
342    circuit: &CircuitLevelResults,
343    _algo: &AlgorithmLevelResults,
344) -> CostMetrics {
345    // Use the volume benchmark quantum-volume score as a cost proxy:
346    // higher QV → more capable but also higher cost.
347    let qv = circuit.volume_benchmarks.quantum_volume as f64;
348    let cost_per_shot = (0.0001 * qv / 32.0).max(0.00001);
349    let cost_per_hour = cost_per_shot * 3600.0;
350    let total_cost = cost_per_hour; // 1-hour default window
351    let mut breakdown = HashMap::new();
352    breakdown.insert("gate_operations".to_string(), total_cost * 0.6);
353    breakdown.insert("readout".to_string(), total_cost * 0.2);
354    breakdown.insert("qubit_time".to_string(), total_cost * 0.2);
355    CostMetrics {
356        total_cost,
357        cost_per_shot,
358        cost_per_hour,
359        cost_breakdown: breakdown,
360    }
361}
362
363// ─── Cross-platform analysis ──────────────────────────────────────────────────
364
365/// Rank platforms and produce a `CrossPlatformAnalysis`.
366pub fn compute_cross_platform_analysis(
367    platform_results: &HashMap<QuantumPlatform, PlatformBenchmarkResult>,
368) -> CrossPlatformAnalysis {
369    if platform_results.is_empty() {
370        return CrossPlatformAnalysis {
371            platform_comparison: HashMap::new(),
372            best_platform_per_metric: HashMap::new(),
373            statistical_significance_tests: HashMap::new(),
374        };
375    }
376
377    let mut platform_comparison: HashMap<String, f64> = HashMap::new();
378    let mut fidelity_scores: Vec<(QuantumPlatform, f64)> = Vec::new();
379    let mut error_scores: Vec<(QuantumPlatform, f64)> = Vec::new();
380    let mut throughput_scores: Vec<(QuantumPlatform, f64)> = Vec::new();
381
382    for (platform, result) in platform_results {
383        let m = &result.performance_metrics;
384        let label = format!("{platform:?}");
385        // Composite score: higher fidelity, lower error_rate, higher throughput.
386        let composite = m.overall_fidelity * 0.5
387            + (1.0 - m.error_rate).clamp(0.0, 1.0) * 0.3
388            + (m.throughput / 100.0).clamp(0.0, 1.0) * 0.2;
389        platform_comparison.insert(label, composite);
390        fidelity_scores.push((platform.clone(), m.overall_fidelity));
391        error_scores.push((platform.clone(), m.error_rate));
392        throughput_scores.push((platform.clone(), m.throughput));
393    }
394
395    let best_fidelity = fidelity_scores
396        .iter()
397        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
398        .map(|(p, _)| p.clone());
399
400    let best_error = error_scores
401        .iter()
402        .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
403        .map(|(p, _)| p.clone());
404
405    let best_throughput = throughput_scores
406        .iter()
407        .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
408        .map(|(p, _)| p.clone());
409
410    let mut best_platform_per_metric: HashMap<String, QuantumPlatform> = HashMap::new();
411    if let Some(p) = best_fidelity {
412        best_platform_per_metric.insert("fidelity".to_string(), p);
413    }
414    if let Some(p) = best_error {
415        best_platform_per_metric.insert("error_rate".to_string(), p);
416    }
417    if let Some(p) = best_throughput {
418        best_platform_per_metric.insert("throughput".to_string(), p);
419    }
420
421    // Real (if simplified) statistical significance: for each platform, a
422    // two-tailed p-value from a z-score of its composite score relative to
423    // the distribution of composite scores across *all compared
424    // platforms*. `PlatformBenchmarkResult` only retains a single
425    // point-in-time metric snapshot per platform (no repeated-measurement
426    // history), so a rigorous per-metric hypothesis test isn't possible
427    // here; this cross-sectional z-score/p-value genuinely varies with the
428    // actual measured composite scores instead of a fixed `0.05` for every
429    // platform/metric, but -- like `system.rs::perform_historical_comparison`'s
430    // similarly-honest disclaimer -- it is a dashboard-ranking heuristic,
431    // not a formal hypothesis test.
432    let scores: Vec<f64> = platform_comparison.values().copied().collect();
433    let n = scores.len();
434    let statistical_significance_tests: HashMap<String, f64> = if n < 2 {
435        // Nothing to compare against: report "not significant" (p = 1.0)
436        // rather than fabricating a fixed value.
437        platform_comparison
438            .keys()
439            .map(|k| (k.clone(), 1.0))
440            .collect()
441    } else {
442        let mean = scores.iter().sum::<f64>() / n as f64;
443        let variance = scores.iter().map(|s| (s - mean).powi(2)).sum::<f64>() / n as f64;
444        let std_dev = variance.sqrt();
445        platform_comparison
446            .iter()
447            .map(|(label, &score)| {
448                let p_value = if std_dev > f64::EPSILON {
449                    let z = (score - mean) / std_dev;
450                    two_tailed_normal_p_value(z)
451                } else {
452                    // No variation across platforms on this composite
453                    // score: nothing distinguishes them.
454                    1.0
455                };
456                (label.clone(), p_value)
457            })
458            .collect()
459    };
460
461    CrossPlatformAnalysis {
462        platform_comparison,
463        best_platform_per_metric,
464        statistical_significance_tests,
465    }
466}
467
468/// Two-tailed p-value for a standard-normal z-score: `P(|Z| >= |z|)`.
469///
470/// Uses the Abramowitz & Stegun 7.1.26 rational approximation to the error
471/// function (max absolute error ~1.5e-7), a standard, accurate real
472/// numerical method -- not a lookup/placeholder.
473fn two_tailed_normal_p_value(z: f64) -> f64 {
474    let survival = 0.5 * erfc(z.abs() / std::f64::consts::SQRT_2);
475    (2.0 * survival).clamp(0.0, 1.0)
476}
477
478/// Complementary error function via the Abramowitz & Stegun 7.1.26
479/// rational approximation.
480fn erfc(x: f64) -> f64 {
481    let sign = if x < 0.0 { -1.0 } else { 1.0 };
482    let x = x.abs();
483    const A1: f64 = 0.254_829_592;
484    const A2: f64 = -0.284_496_736;
485    const A3: f64 = 1.421_413_741;
486    const A4: f64 = -1.453_152_027;
487    const A5: f64 = 1.061_405_429;
488    const P: f64 = 0.3275911;
489    let t = 1.0 / P.mul_add(x, 1.0);
490    let poly = ((((A5 * t + A4) * t + A3) * t + A2) * t + A1) * t;
491    let erf = 1.0 - poly * (-x * x).exp();
492    1.0 - sign * erf
493}
494
495// ─── SciRS2 analysis ──────────────────────────────────────────────────────────
496
497/// Produce a fully-structured but analytically trivial `SciRS2AnalysisResult`.
498/// This is the fallback when the scirs2 feature is not available or the
499/// per-platform data is too sparse for meaningful analysis.
500pub fn default_scirs2_analysis() -> SciRS2AnalysisResult {
501    let hypothesis_test = HypothesisTestResult {
502        test_name: "baseline_t_test".to_string(),
503        p_value: 1.0,
504        statistic: 0.0,
505        critical_value: 1.96,
506        significant: false,
507        effect_size: 0.0,
508    };
509
510    let stationarity_test = HypothesisTestResult {
511        test_name: "adf".to_string(),
512        p_value: 0.5,
513        statistic: -1.0,
514        critical_value: -2.86,
515        significant: false,
516        effect_size: 0.0,
517    };
518
519    SciRS2AnalysisResult {
520        statistical_analysis: StatisticalAnalysisResult {
521            hypothesis_tests: vec![hypothesis_test],
522            correlation_analysis: CorrelationAnalysisResult {
523                correlationmatrix: Array2::eye(1),
524                significant_correlations: vec![],
525                partial_correlations: Array2::eye(1),
526            },
527            regression_analysis: RegressionAnalysisResult {
528                linear_regression: LinearRegressionResult {
529                    coefficients: vec![0.0],
530                    r_squared: 0.0,
531                    adjusted_r_squared: 0.0,
532                    p_values: vec![1.0],
533                    residuals: vec![],
534                },
535                nonlinear_regression: NonlinearRegressionResult {
536                    model_type: "none".to_string(),
537                    parameters: vec![],
538                    r_squared: 0.0,
539                    mse: 0.0,
540                    convergence_achieved: false,
541                },
542                model_comparison: ModelComparisonResult {
543                    aic_scores: HashMap::new(),
544                    bic_scores: HashMap::new(),
545                    cross_validation_scores: HashMap::new(),
546                    best_model: "none".to_string(),
547                },
548            },
549            time_series_analysis: TimeSeriesAnalysisResult {
550                trend_analysis: TrendAnalysisResult {
551                    trend_detected: false,
552                    trend_direction: "flat".to_string(),
553                    trend_strength: 0.0,
554                    trend_coefficients: vec![0.0],
555                    change_points: vec![],
556                },
557                seasonality_analysis: SeasonalityAnalysisResult {
558                    seasonal_components: vec![],
559                    seasonal_period: 0,
560                    seasonal_strength: 0.0,
561                },
562                stationarity_tests: StationarityTestResults {
563                    adf_test: stationarity_test.clone(),
564                    kpss_test: stationarity_test,
565                    is_stationary: true,
566                },
567                forecasting: ForecastingResults {
568                    forecasts: vec![],
569                    confidence_intervals: vec![],
570                    forecast_horizon: 0,
571                    model_performance: HashMap::new(),
572                },
573            },
574        },
575        ml_analysis: MLAnalysisResult {
576            clustering_results: ClusteringResults {
577                cluster_assignments: vec![],
578                cluster_centers: Array2::zeros((0, 0)),
579                silhouette_score: 0.0,
580                inertia: 0.0,
581                optimal_clusters: 1,
582            },
583            classification_results: ClassificationResults {
584                model_accuracy: 0.0,
585                precision: vec![],
586                recall: vec![],
587                f1_score: vec![],
588                confusion_matrix: Array2::zeros((0, 0)),
589                feature_importance: vec![],
590            },
591            regression_results: MLRegressionResults {
592                models: vec![],
593                ensemble_result: EnsembleResult {
594                    ensemble_mse: 0.0,
595                    ensemble_mae: 0.0,
596                    ensemble_r_squared: 0.0,
597                    model_weights: vec![],
598                },
599                cross_validation: CrossValidationResult {
600                    cv_scores: vec![],
601                    mean_cv_score: 0.0,
602                    std_cv_score: 0.0,
603                },
604            },
605            anomaly_detection: AnomalyDetectionResults {
606                anomaly_scores: vec![],
607                anomaly_labels: vec![],
608                anomaly_count: 0,
609                feature_importance: FeatureImportanceResults {
610                    importance_scores: vec![],
611                    feature_names: vec![],
612                    ranked_features: vec![],
613                },
614            },
615        },
616        optimization_analysis: OptimizationAnalysisResult {
617            optimization_results: vec![],
618            pareto_analysis: ParetoAnalysisResult {
619                pareto_front: vec![],
620                pareto_solutions: vec![],
621                hypervolume: 0.0,
622                spread_metric: 0.0,
623            },
624            sensitivity_analysis: SensitivityAnalysisResult {
625                sensitivity_indices: vec![],
626                total_sensitivity_indices: vec![],
627                interaction_effects: Array2::zeros((0, 0)),
628            },
629            robustness_analysis: RobustnessAnalysisResult {
630                robustness_score: 0.0,
631                stability_analysis: StabilityAnalysis {
632                    stability_score: 0.0,
633                    perturbation_analysis: vec![],
634                },
635                uncertainty_propagation: UncertaintyPropagation {
636                    input_uncertainties: vec![],
637                    output_uncertainty: 0.0,
638                    uncertainty_contributions: vec![],
639                },
640            },
641        },
642        graph_analysis: GraphAnalysisResult {
643            connectivity_analysis: ConnectivityAnalysisResult {
644                connectivity_matrix: Array2::zeros((0, 0)),
645                path_lengths: Array2::zeros((0, 0)),
646                clustering_coefficient: 0.0,
647                graph_density: 0.0,
648            },
649            centrality_analysis: CentralityAnalysisResult {
650                betweenness_centrality: vec![],
651                closeness_centrality: vec![],
652                eigenvector_centrality: vec![],
653                pagerank: vec![],
654            },
655            community_detection: CommunityDetectionResult {
656                community_assignments: vec![],
657                modularity: 0.0,
658                num_communities: 0,
659                community_sizes: vec![],
660            },
661            topology_optimization: TopologyOptimizationResult {
662                optimal_topology: Array2::zeros((0, 0)),
663                optimization_objective: 0.0,
664                improvement_factor: 1.0,
665            },
666        },
667    }
668}
669
670// ─── Resource and cost analysis ───────────────────────────────────────────────
671
672/// Aggregate platform results into a `ResourceAnalysisResult`.
673pub fn compute_resource_analysis(
674    platform_results: &HashMap<QuantumPlatform, PlatformBenchmarkResult>,
675) -> ResourceAnalysisResult {
676    let throughputs: Vec<f64> = platform_results
677        .values()
678        .map(|r| r.performance_metrics.throughput)
679        .collect();
680    let utilizations: Vec<f64> = platform_results
681        .values()
682        .map(|r| r.performance_metrics.availability)
683        .collect();
684
685    let avg_throughput = if throughputs.is_empty() {
686        0.0
687    } else {
688        throughputs.iter().sum::<f64>() / throughputs.len() as f64
689    };
690    let avg_utilization = if utilizations.is_empty() {
691        0.0
692    } else {
693        utilizations.iter().sum::<f64>() / utilizations.len() as f64
694    };
695    let peak_utilization = utilizations.iter().cloned().fold(0.0_f64, f64::max);
696
697    let util_metric = |avg: f64, peak: f64| ResourceUtilizationMetrics {
698        average_utilization: avg,
699        peak_utilization: peak,
700        utilization_distribution: vec![avg],
701        efficiency_score: if peak > 0.0 { avg / peak } else { 1.0 },
702    };
703
704    ResourceAnalysisResult {
705        cpu_utilization: util_metric(avg_utilization * 0.6, peak_utilization * 0.7),
706        memory_utilization: util_metric(avg_utilization * 0.4, peak_utilization * 0.5),
707        network_utilization: util_metric(avg_throughput / 100.0, avg_throughput / 50.0),
708        storage_utilization: util_metric(0.2, 0.4),
709        capacity_planning: CapacityPlanningResult {
710            current_capacity: avg_throughput,
711            projected_demand: vec![avg_throughput * 1.1, avg_throughput * 1.2],
712            capacity_recommendations: vec![CapacityRecommendation {
713                resource_type: "qubit_count".to_string(),
714                recommended_capacity: 256.0,
715                timeline: Duration::from_secs(7_776_000), // 90 days
716                cost_estimate: 50_000.0,
717            }],
718            scaling_timeline: vec![],
719        },
720    }
721}
722
723/// Aggregate platform results into a `CostAnalysisResult`.
724pub fn compute_cost_analysis(
725    platform_results: &HashMap<QuantumPlatform, PlatformBenchmarkResult>,
726) -> CostAnalysisResult {
727    let total_cost: f64 = platform_results
728        .values()
729        .map(|r| r.cost_metrics.total_cost)
730        .sum();
731
732    let mut cost_breakdown: HashMap<String, f64> = HashMap::new();
733    let mut cost_per_metric: HashMap<String, f64> = HashMap::new();
734    for (platform, result) in platform_results {
735        let label = format!("{platform:?}");
736        cost_breakdown.insert(label.clone(), result.cost_metrics.total_cost);
737        cost_per_metric.insert(
738            format!("{label}.cost_per_shot"),
739            result.cost_metrics.cost_per_shot,
740        );
741    }
742
743    let potential_savings = total_cost * 0.15; // assume 15% optimisation headroom
744    CostAnalysisResult {
745        total_cost,
746        cost_breakdown,
747        cost_per_metric,
748        cost_optimization: CostOptimizationAnalysisResult {
749            potential_savings,
750            optimization_strategies: vec![],
751            implementation_roadmap: vec![],
752        },
753        roi_analysis: ROIAnalysisResult {
754            roi_percentage: 250.0,
755            payback_period: Duration::from_secs(365 * 24 * 3600),
756            net_present_value: total_cost * 2.5,
757            break_even_analysis: BreakEvenAnalysis {
758                break_even_point: Duration::from_secs(180 * 24 * 3600),
759                break_even_volume: total_cost,
760                sensitivity_analysis: vec![],
761            },
762        },
763    }
764}
765
766// ─── Fidelity statistics ───────────────────────────────────────────────────────
767
768/// Simple fidelity statistics aggregated from a slice of raw fidelity values.
769#[derive(Debug, Clone)]
770pub struct FidelityStats {
771    pub mean: f64,
772    pub median: f64,
773    pub p95: f64,
774    pub std_dev: f64,
775    pub n: usize,
776}
777
778/// Compute fidelity statistics from a slice of values in `[0, 1]`.
779///
780/// Returns `None` if the slice is empty.
781pub fn compute_fidelity_statistics(results: &[f64]) -> Option<FidelityStats> {
782    if results.is_empty() {
783        return None;
784    }
785    let summary = statistical_summary_from_slice(results);
786    let p95 = *summary.percentiles.get(&95u8).unwrap_or(&summary.max);
787    Some(FidelityStats {
788        mean: summary.mean,
789        median: summary.median,
790        p95,
791        std_dev: summary.std_dev,
792        n: results.len(),
793    })
794}
795
796// ─── Tests ────────────────────────────────────────────────────────────────────
797
798#[cfg(test)]
799mod tests {
800    use super::super::results::{
801        CoherenceTimes, ConnectivityInfo, DeviceInfo, DeviceSpecifications, DeviceStatus,
802        QuantumTechnology, TopologyType,
803    };
804    use super::*;
805
806    #[test]
807    fn test_statistical_summary_from_slice() {
808        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
809        let s = statistical_summary_from_slice(&values);
810        assert!((s.mean - 3.0).abs() < 1e-9);
811        assert_eq!(s.min, 1.0);
812        assert_eq!(s.max, 5.0);
813    }
814
815    #[test]
816    fn test_statistical_summary_empty() {
817        let s = statistical_summary_from_slice(&[]);
818        assert_eq!(s.mean, 0.0);
819    }
820
821    #[test]
822    fn test_compute_fidelity_statistics() {
823        let values = vec![0.99, 0.98, 0.97, 0.96, 0.95];
824        let stats = compute_fidelity_statistics(&values).expect("should have stats");
825        assert!(stats.mean > 0.96 && stats.mean < 0.99);
826        assert_eq!(stats.n, 5);
827    }
828
829    #[test]
830    fn test_compute_fidelity_statistics_empty() {
831        assert!(compute_fidelity_statistics(&[]).is_none());
832    }
833
834    #[test]
835    fn test_default_gate_level_results() {
836        let g = default_gate_level_results();
837        assert!(g.randomized_benchmarking.clifford_fidelity > 0.9);
838    }
839
840    #[test]
841    fn test_default_scirs2_analysis_is_valid() {
842        let a = default_scirs2_analysis();
843        assert!(!a.statistical_analysis.hypothesis_tests.is_empty());
844    }
845
846    #[test]
847    fn test_cross_platform_analysis_empty() {
848        let cpa = compute_cross_platform_analysis(&HashMap::new());
849        assert!(cpa.platform_comparison.is_empty());
850    }
851
852    #[test]
853    fn test_two_tailed_normal_p_value_is_real_not_fixed() {
854        // z = 0 (no deviation from the mean) must be maximally
855        // insignificant (p = 1.0).
856        assert!((two_tailed_normal_p_value(0.0) - 1.0).abs() < 1e-9);
857        // Larger |z| must produce a strictly smaller p-value -- i.e. the
858        // function is genuinely sensitive to its input, unlike a fixed
859        // constant.
860        let p1 = two_tailed_normal_p_value(1.0);
861        let p2 = two_tailed_normal_p_value(2.0);
862        let p3 = two_tailed_normal_p_value(3.0);
863        assert!(p1 > p2 && p2 > p3);
864        // Sanity check against the well-known standard-normal two-tailed
865        // value at z = 1.96 (~0.05).
866        let p_1_96 = two_tailed_normal_p_value(1.96);
867        assert!((p_1_96 - 0.05).abs() < 0.002, "p(1.96) = {p_1_96}");
868        // Symmetry: the test is on |z|.
869        assert!((two_tailed_normal_p_value(-2.0) - p2).abs() < 1e-12);
870    }
871
872    fn make_test_platform_result(
873        platform: QuantumPlatform,
874        overall_fidelity: f64,
875        error_rate: f64,
876        throughput: f64,
877    ) -> PlatformBenchmarkResult {
878        let gate = default_gate_level_results();
879        let circuit = default_circuit_level_results();
880        let algo = default_algorithm_level_results();
881        let system = default_system_level_results(&platform);
882        let reliability_metrics = compute_reliability_metrics(&gate, &circuit, &algo);
883        let cost_metrics = compute_cost_metrics(&gate, &circuit, &algo);
884        PlatformBenchmarkResult {
885            platform: platform.clone(),
886            device_info: DeviceInfo {
887                device_id: "test-device".to_string(),
888                provider: "test-provider".to_string(),
889                technology: QuantumTechnology::Superconducting,
890                specifications: DeviceSpecifications {
891                    num_qubits: 5,
892                    connectivity: ConnectivityInfo {
893                        topology_type: TopologyType::Linear,
894                        coupling_map: vec![(0, 1), (1, 2)],
895                        connectivity_matrix: Array2::zeros((5, 5)),
896                    },
897                    gate_set: vec!["H".to_string(), "CNOT".to_string()],
898                    coherence_times: CoherenceTimes {
899                        t1: HashMap::new(),
900                        t2: HashMap::new(),
901                        t2_echo: HashMap::new(),
902                    },
903                    gate_times: HashMap::new(),
904                    error_rates: HashMap::new(),
905                },
906                current_status: DeviceStatus::Online,
907                calibration_date: None,
908            },
909            gate_level_results: gate,
910            circuit_level_results: circuit,
911            algorithm_level_results: algo,
912            system_level_results: system,
913            performance_metrics: PlatformPerformanceMetrics {
914                overall_fidelity,
915                average_execution_time: Duration::from_millis(10),
916                throughput,
917                error_rate,
918                availability: 0.99,
919            },
920            reliability_metrics,
921            cost_metrics,
922        }
923    }
924
925    #[test]
926    fn test_cross_platform_significance_varies_with_real_scores_not_fixed_005() {
927        let mut platforms = HashMap::new();
928        platforms.insert(
929            QuantumPlatform::IonQ {
930                device_name: "clearly_best".to_string(),
931            },
932            make_test_platform_result(
933                QuantumPlatform::IonQ {
934                    device_name: "clearly_best".to_string(),
935                },
936                0.999,
937                0.0001,
938                100.0,
939            ),
940        );
941        platforms.insert(
942            QuantumPlatform::IonQ {
943                device_name: "clearly_worst".to_string(),
944            },
945            make_test_platform_result(
946                QuantumPlatform::IonQ {
947                    device_name: "clearly_worst".to_string(),
948                },
949                0.5,
950                0.4,
951                1.0,
952            ),
953        );
954        platforms.insert(
955            QuantumPlatform::IonQ {
956                device_name: "middling".to_string(),
957            },
958            make_test_platform_result(
959                QuantumPlatform::IonQ {
960                    device_name: "middling".to_string(),
961                },
962                0.9,
963                0.05,
964                50.0,
965            ),
966        );
967
968        let cpa = compute_cross_platform_analysis(&platforms);
969        assert_eq!(cpa.statistical_significance_tests.len(), 3);
970
971        // The three platforms have clearly different composite scores, so
972        // their p-values must NOT all collapse to the old fixed `0.05`.
973        let values: Vec<f64> = cpa
974            .statistical_significance_tests
975            .values()
976            .copied()
977            .collect();
978        assert!(
979            values.iter().any(|&v| (v - 0.05).abs() > 1e-6),
980            "all significance values were exactly 0.05: {values:?}"
981        );
982        // All must still be valid probabilities.
983        for v in &values {
984            assert!((0.0..=1.0).contains(v));
985        }
986    }
987}