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    // Simple significance placeholder — all at 0.05.
422    let statistical_significance_tests: HashMap<String, f64> = platform_comparison
423        .keys()
424        .map(|k| (k.clone(), 0.05))
425        .collect();
426
427    CrossPlatformAnalysis {
428        platform_comparison,
429        best_platform_per_metric,
430        statistical_significance_tests,
431    }
432}
433
434// ─── SciRS2 analysis ──────────────────────────────────────────────────────────
435
436/// Produce a fully-structured but analytically trivial `SciRS2AnalysisResult`.
437/// This is the fallback when the scirs2 feature is not available or the
438/// per-platform data is too sparse for meaningful analysis.
439pub fn default_scirs2_analysis() -> SciRS2AnalysisResult {
440    let hypothesis_test = HypothesisTestResult {
441        test_name: "baseline_t_test".to_string(),
442        p_value: 1.0,
443        statistic: 0.0,
444        critical_value: 1.96,
445        significant: false,
446        effect_size: 0.0,
447    };
448
449    let stationarity_test = HypothesisTestResult {
450        test_name: "adf".to_string(),
451        p_value: 0.5,
452        statistic: -1.0,
453        critical_value: -2.86,
454        significant: false,
455        effect_size: 0.0,
456    };
457
458    SciRS2AnalysisResult {
459        statistical_analysis: StatisticalAnalysisResult {
460            hypothesis_tests: vec![hypothesis_test],
461            correlation_analysis: CorrelationAnalysisResult {
462                correlationmatrix: Array2::eye(1),
463                significant_correlations: vec![],
464                partial_correlations: Array2::eye(1),
465            },
466            regression_analysis: RegressionAnalysisResult {
467                linear_regression: LinearRegressionResult {
468                    coefficients: vec![0.0],
469                    r_squared: 0.0,
470                    adjusted_r_squared: 0.0,
471                    p_values: vec![1.0],
472                    residuals: vec![],
473                },
474                nonlinear_regression: NonlinearRegressionResult {
475                    model_type: "none".to_string(),
476                    parameters: vec![],
477                    r_squared: 0.0,
478                    mse: 0.0,
479                    convergence_achieved: false,
480                },
481                model_comparison: ModelComparisonResult {
482                    aic_scores: HashMap::new(),
483                    bic_scores: HashMap::new(),
484                    cross_validation_scores: HashMap::new(),
485                    best_model: "none".to_string(),
486                },
487            },
488            time_series_analysis: TimeSeriesAnalysisResult {
489                trend_analysis: TrendAnalysisResult {
490                    trend_detected: false,
491                    trend_direction: "flat".to_string(),
492                    trend_strength: 0.0,
493                    trend_coefficients: vec![0.0],
494                    change_points: vec![],
495                },
496                seasonality_analysis: SeasonalityAnalysisResult {
497                    seasonal_components: vec![],
498                    seasonal_period: 0,
499                    seasonal_strength: 0.0,
500                },
501                stationarity_tests: StationarityTestResults {
502                    adf_test: stationarity_test.clone(),
503                    kpss_test: stationarity_test,
504                    is_stationary: true,
505                },
506                forecasting: ForecastingResults {
507                    forecasts: vec![],
508                    confidence_intervals: vec![],
509                    forecast_horizon: 0,
510                    model_performance: HashMap::new(),
511                },
512            },
513        },
514        ml_analysis: MLAnalysisResult {
515            clustering_results: ClusteringResults {
516                cluster_assignments: vec![],
517                cluster_centers: Array2::zeros((0, 0)),
518                silhouette_score: 0.0,
519                inertia: 0.0,
520                optimal_clusters: 1,
521            },
522            classification_results: ClassificationResults {
523                model_accuracy: 0.0,
524                precision: vec![],
525                recall: vec![],
526                f1_score: vec![],
527                confusion_matrix: Array2::zeros((0, 0)),
528                feature_importance: vec![],
529            },
530            regression_results: MLRegressionResults {
531                models: vec![],
532                ensemble_result: EnsembleResult {
533                    ensemble_mse: 0.0,
534                    ensemble_mae: 0.0,
535                    ensemble_r_squared: 0.0,
536                    model_weights: vec![],
537                },
538                cross_validation: CrossValidationResult {
539                    cv_scores: vec![],
540                    mean_cv_score: 0.0,
541                    std_cv_score: 0.0,
542                },
543            },
544            anomaly_detection: AnomalyDetectionResults {
545                anomaly_scores: vec![],
546                anomaly_labels: vec![],
547                anomaly_count: 0,
548                feature_importance: FeatureImportanceResults {
549                    importance_scores: vec![],
550                    feature_names: vec![],
551                    ranked_features: vec![],
552                },
553            },
554        },
555        optimization_analysis: OptimizationAnalysisResult {
556            optimization_results: vec![],
557            pareto_analysis: ParetoAnalysisResult {
558                pareto_front: vec![],
559                pareto_solutions: vec![],
560                hypervolume: 0.0,
561                spread_metric: 0.0,
562            },
563            sensitivity_analysis: SensitivityAnalysisResult {
564                sensitivity_indices: vec![],
565                total_sensitivity_indices: vec![],
566                interaction_effects: Array2::zeros((0, 0)),
567            },
568            robustness_analysis: RobustnessAnalysisResult {
569                robustness_score: 0.0,
570                stability_analysis: StabilityAnalysis {
571                    stability_score: 0.0,
572                    perturbation_analysis: vec![],
573                },
574                uncertainty_propagation: UncertaintyPropagation {
575                    input_uncertainties: vec![],
576                    output_uncertainty: 0.0,
577                    uncertainty_contributions: vec![],
578                },
579            },
580        },
581        graph_analysis: GraphAnalysisResult {
582            connectivity_analysis: ConnectivityAnalysisResult {
583                connectivity_matrix: Array2::zeros((0, 0)),
584                path_lengths: Array2::zeros((0, 0)),
585                clustering_coefficient: 0.0,
586                graph_density: 0.0,
587            },
588            centrality_analysis: CentralityAnalysisResult {
589                betweenness_centrality: vec![],
590                closeness_centrality: vec![],
591                eigenvector_centrality: vec![],
592                pagerank: vec![],
593            },
594            community_detection: CommunityDetectionResult {
595                community_assignments: vec![],
596                modularity: 0.0,
597                num_communities: 0,
598                community_sizes: vec![],
599            },
600            topology_optimization: TopologyOptimizationResult {
601                optimal_topology: Array2::zeros((0, 0)),
602                optimization_objective: 0.0,
603                improvement_factor: 1.0,
604            },
605        },
606    }
607}
608
609// ─── Resource and cost analysis ───────────────────────────────────────────────
610
611/// Aggregate platform results into a `ResourceAnalysisResult`.
612pub fn compute_resource_analysis(
613    platform_results: &HashMap<QuantumPlatform, PlatformBenchmarkResult>,
614) -> ResourceAnalysisResult {
615    let throughputs: Vec<f64> = platform_results
616        .values()
617        .map(|r| r.performance_metrics.throughput)
618        .collect();
619    let utilizations: Vec<f64> = platform_results
620        .values()
621        .map(|r| r.performance_metrics.availability)
622        .collect();
623
624    let avg_throughput = if throughputs.is_empty() {
625        0.0
626    } else {
627        throughputs.iter().sum::<f64>() / throughputs.len() as f64
628    };
629    let avg_utilization = if utilizations.is_empty() {
630        0.0
631    } else {
632        utilizations.iter().sum::<f64>() / utilizations.len() as f64
633    };
634    let peak_utilization = utilizations.iter().cloned().fold(0.0_f64, f64::max);
635
636    let util_metric = |avg: f64, peak: f64| ResourceUtilizationMetrics {
637        average_utilization: avg,
638        peak_utilization: peak,
639        utilization_distribution: vec![avg],
640        efficiency_score: if peak > 0.0 { avg / peak } else { 1.0 },
641    };
642
643    ResourceAnalysisResult {
644        cpu_utilization: util_metric(avg_utilization * 0.6, peak_utilization * 0.7),
645        memory_utilization: util_metric(avg_utilization * 0.4, peak_utilization * 0.5),
646        network_utilization: util_metric(avg_throughput / 100.0, avg_throughput / 50.0),
647        storage_utilization: util_metric(0.2, 0.4),
648        capacity_planning: CapacityPlanningResult {
649            current_capacity: avg_throughput,
650            projected_demand: vec![avg_throughput * 1.1, avg_throughput * 1.2],
651            capacity_recommendations: vec![CapacityRecommendation {
652                resource_type: "qubit_count".to_string(),
653                recommended_capacity: 256.0,
654                timeline: Duration::from_secs(7_776_000), // 90 days
655                cost_estimate: 50_000.0,
656            }],
657            scaling_timeline: vec![],
658        },
659    }
660}
661
662/// Aggregate platform results into a `CostAnalysisResult`.
663pub fn compute_cost_analysis(
664    platform_results: &HashMap<QuantumPlatform, PlatformBenchmarkResult>,
665) -> CostAnalysisResult {
666    let total_cost: f64 = platform_results
667        .values()
668        .map(|r| r.cost_metrics.total_cost)
669        .sum();
670
671    let mut cost_breakdown: HashMap<String, f64> = HashMap::new();
672    let mut cost_per_metric: HashMap<String, f64> = HashMap::new();
673    for (platform, result) in platform_results {
674        let label = format!("{platform:?}");
675        cost_breakdown.insert(label.clone(), result.cost_metrics.total_cost);
676        cost_per_metric.insert(
677            format!("{label}.cost_per_shot"),
678            result.cost_metrics.cost_per_shot,
679        );
680    }
681
682    let potential_savings = total_cost * 0.15; // assume 15% optimisation headroom
683    CostAnalysisResult {
684        total_cost,
685        cost_breakdown,
686        cost_per_metric,
687        cost_optimization: CostOptimizationAnalysisResult {
688            potential_savings,
689            optimization_strategies: vec![],
690            implementation_roadmap: vec![],
691        },
692        roi_analysis: ROIAnalysisResult {
693            roi_percentage: 250.0,
694            payback_period: Duration::from_secs(365 * 24 * 3600),
695            net_present_value: total_cost * 2.5,
696            break_even_analysis: BreakEvenAnalysis {
697                break_even_point: Duration::from_secs(180 * 24 * 3600),
698                break_even_volume: total_cost,
699                sensitivity_analysis: vec![],
700            },
701        },
702    }
703}
704
705// ─── Fidelity statistics ───────────────────────────────────────────────────────
706
707/// Simple fidelity statistics aggregated from a slice of raw fidelity values.
708#[derive(Debug, Clone)]
709pub struct FidelityStats {
710    pub mean: f64,
711    pub median: f64,
712    pub p95: f64,
713    pub std_dev: f64,
714    pub n: usize,
715}
716
717/// Compute fidelity statistics from a slice of values in `[0, 1]`.
718///
719/// Returns `None` if the slice is empty.
720pub fn compute_fidelity_statistics(results: &[f64]) -> Option<FidelityStats> {
721    if results.is_empty() {
722        return None;
723    }
724    let summary = statistical_summary_from_slice(results);
725    let p95 = *summary.percentiles.get(&95u8).unwrap_or(&summary.max);
726    Some(FidelityStats {
727        mean: summary.mean,
728        median: summary.median,
729        p95,
730        std_dev: summary.std_dev,
731        n: results.len(),
732    })
733}
734
735// ─── Tests ────────────────────────────────────────────────────────────────────
736
737#[cfg(test)]
738mod tests {
739    use super::*;
740
741    #[test]
742    fn test_statistical_summary_from_slice() {
743        let values = vec![1.0, 2.0, 3.0, 4.0, 5.0];
744        let s = statistical_summary_from_slice(&values);
745        assert!((s.mean - 3.0).abs() < 1e-9);
746        assert_eq!(s.min, 1.0);
747        assert_eq!(s.max, 5.0);
748    }
749
750    #[test]
751    fn test_statistical_summary_empty() {
752        let s = statistical_summary_from_slice(&[]);
753        assert_eq!(s.mean, 0.0);
754    }
755
756    #[test]
757    fn test_compute_fidelity_statistics() {
758        let values = vec![0.99, 0.98, 0.97, 0.96, 0.95];
759        let stats = compute_fidelity_statistics(&values).expect("should have stats");
760        assert!(stats.mean > 0.96 && stats.mean < 0.99);
761        assert_eq!(stats.n, 5);
762    }
763
764    #[test]
765    fn test_compute_fidelity_statistics_empty() {
766        assert!(compute_fidelity_statistics(&[]).is_none());
767    }
768
769    #[test]
770    fn test_default_gate_level_results() {
771        let g = default_gate_level_results();
772        assert!(g.randomized_benchmarking.clifford_fidelity > 0.9);
773    }
774
775    #[test]
776    fn test_default_scirs2_analysis_is_valid() {
777        let a = default_scirs2_analysis();
778        assert!(!a.statistical_analysis.hypothesis_tests.is_empty());
779    }
780
781    #[test]
782    fn test_cross_platform_analysis_empty() {
783        let cpa = compute_cross_platform_analysis(&HashMap::new());
784        assert!(cpa.platform_comparison.is_empty());
785    }
786}