Skip to main content

quantrs2_device/performance_dashboard/
mod.rs

1//! Comprehensive Device Performance Analytics Dashboard
2//!
3//! This module provides a comprehensive real-time performance analytics dashboard
4//! that unifies monitoring, visualization, and intelligent insights across all quantum
5//! device components using SciRS2's advanced analytics and machine learning capabilities.
6
7use std::collections::{BTreeMap, HashMap, VecDeque};
8use std::sync::{Arc, Mutex, RwLock};
9use std::time::{Duration, Instant, SystemTime, UNIX_EPOCH};
10
11use quantrs2_circuit::prelude::*;
12use quantrs2_core::{
13    error::{QuantRS2Error, QuantRS2Result},
14    gate::GateOp,
15    qubit::QubitId,
16};
17
18use serde::{Deserialize, Serialize};
19
20// SciRS2 dependencies for advanced analytics
21#[cfg(feature = "scirs2")]
22use scirs2_graph::{
23    betweenness_centrality, closeness_centrality, dijkstra_path, minimum_spanning_tree,
24    strongly_connected_components, Graph,
25};
26#[cfg(feature = "scirs2")]
27use scirs2_linalg::{det, eig, inv, matrix_norm, prelude::*, svd, LinalgError, LinalgResult};
28#[cfg(feature = "scirs2")]
29use scirs2_optimize::{minimize, OptimizeResult};
30use scirs2_stats::ttest::Alternative;
31#[cfg(feature = "scirs2")]
32use scirs2_stats::{corrcoef, distributions, mean, pearsonr, spearmanr, std, var};
33
34// Fallback implementations when SciRS2 is not available
35#[cfg(not(feature = "scirs2"))]
36mod fallback_scirs2;
37#[cfg(not(feature = "scirs2"))]
38use fallback_scirs2::*;
39
40use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
41use scirs2_core::random::prelude::*;
42
43use crate::{
44    adaptive_compilation::AdaptiveCompilationConfig,
45    backend_traits::{query_backend_capabilities, BackendCapabilities},
46    calibration::{CalibrationManager, DeviceCalibration},
47    integrated_device_manager::IntegratedQuantumDeviceManager,
48    noise_model::CalibrationNoiseModel,
49    topology::HardwareTopology,
50    CircuitResult, DeviceError, DeviceResult,
51};
52
53// Module declarations
54pub mod alerting;
55pub mod config;
56pub mod data_collection;
57pub mod ml_analytics;
58pub mod optimization;
59pub mod reporting;
60pub mod visualization;
61
62// Re-exports for public API
63pub use alerting::*;
64pub use config::*;
65pub use data_collection::*;
66pub use ml_analytics::*;
67pub use optimization::*;
68pub use reporting::*;
69pub use visualization::*;
70
71#[cfg(not(feature = "scirs2"))]
72pub use fallback_scirs2::*;
73
74// ── PerformanceDashboard ──────────────────────────────────────────────────
75
76/// Metrics captured for a single circuit execution.
77#[derive(Debug, Clone, Serialize, Deserialize)]
78pub struct ExecutionMetrics {
79    /// Wall-clock execution time in milliseconds
80    pub execution_time_ms: f64,
81    /// Measured circuit fidelity (0.0 – 1.0)
82    pub fidelity: f64,
83    /// Whether the execution succeeded
84    pub success: bool,
85    /// Number of gates in the circuit
86    pub gate_count: usize,
87    /// Circuit depth
88    pub circuit_depth: usize,
89    /// Two-qubit gate count
90    pub two_qubit_gate_count: usize,
91    /// Error rate observed during execution
92    pub error_rate: f64,
93    /// Timestamp of this execution (UNIX milliseconds)
94    pub timestamp_ms: u128,
95}
96
97impl ExecutionMetrics {
98    /// Create a new `ExecutionMetrics` snapshot.
99    pub fn new(
100        execution_time_ms: f64,
101        fidelity: f64,
102        success: bool,
103        gate_count: usize,
104        circuit_depth: usize,
105        two_qubit_gate_count: usize,
106        error_rate: f64,
107    ) -> Self {
108        let timestamp_ms = SystemTime::now()
109            .duration_since(UNIX_EPOCH)
110            .unwrap_or_default()
111            .as_millis();
112        Self {
113            execution_time_ms,
114            fidelity,
115            success,
116            gate_count,
117            circuit_depth,
118            two_qubit_gate_count,
119            error_rate,
120            timestamp_ms,
121        }
122    }
123}
124
125/// Aggregated statistics returned by `PerformanceDashboard::get_summary`.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127pub struct DashboardSummary {
128    /// Total number of recorded executions
129    pub total_executions: usize,
130    /// Number of successful executions
131    pub successful_executions: usize,
132    /// Success rate (0.0 – 1.0)
133    pub success_rate: f64,
134    /// Mean fidelity across all executions
135    pub mean_fidelity: f64,
136    /// Minimum fidelity observed
137    pub min_fidelity: f64,
138    /// Maximum fidelity observed
139    pub max_fidelity: f64,
140    /// Mean execution time in milliseconds
141    pub mean_execution_time_ms: f64,
142    /// 95th-percentile execution time in milliseconds
143    pub p95_execution_time_ms: f64,
144    /// Mean error rate
145    pub mean_error_rate: f64,
146    /// Mean gate count
147    pub mean_gate_count: f64,
148    /// Mean circuit depth
149    pub mean_circuit_depth: f64,
150    /// Number of distinct circuit IDs tracked
151    pub distinct_circuits: usize,
152}
153
154/// Per-circuit accumulated statistics (running totals for Welford's algorithm).
155#[derive(Debug, Clone)]
156struct CircuitStats {
157    count: usize,
158    total_execution_time_ms: f64,
159    total_fidelity: f64,
160    total_error_rate: f64,
161    total_gate_count: usize,
162    total_circuit_depth: usize,
163    successes: usize,
164    /// Sorted list of execution times kept for percentile computation
165    sorted_times: Vec<f64>,
166}
167
168impl CircuitStats {
169    fn new() -> Self {
170        Self {
171            count: 0,
172            total_execution_time_ms: 0.0,
173            total_fidelity: 0.0,
174            total_error_rate: 0.0,
175            total_gate_count: 0,
176            total_circuit_depth: 0,
177            successes: 0,
178            sorted_times: Vec::new(),
179        }
180    }
181
182    fn record(&mut self, m: &ExecutionMetrics) {
183        self.count += 1;
184        self.total_execution_time_ms += m.execution_time_ms;
185        self.total_fidelity += m.fidelity;
186        self.total_error_rate += m.error_rate;
187        self.total_gate_count += m.gate_count;
188        self.total_circuit_depth += m.circuit_depth;
189        if m.success {
190            self.successes += 1;
191        }
192        // Insert in sorted order for percentile queries
193        let pos = self
194            .sorted_times
195            .partition_point(|&t| t <= m.execution_time_ms);
196        self.sorted_times.insert(pos, m.execution_time_ms);
197    }
198
199    fn percentile_time(&self, p: f64) -> f64 {
200        if self.sorted_times.is_empty() {
201            return 0.0;
202        }
203        let idx = ((p / 100.0) * (self.sorted_times.len() as f64 - 1.0)).round() as usize;
204        self.sorted_times
205            .get(idx.min(self.sorted_times.len() - 1))
206            .copied()
207            .unwrap_or(0.0)
208    }
209}
210
211/// Real-time performance analytics dashboard.
212///
213/// Records execution metrics per circuit ID, computes aggregate statistics,
214/// and can export a Markdown performance report.
215///
216/// # Example
217/// ```rust,ignore
218/// let mut dashboard = PerformanceDashboard::new(DashboardConfig::default());
219/// dashboard.record_execution("bell_state", ExecutionMetrics::new(...));
220/// let summary = dashboard.get_summary();
221/// println!("{}", dashboard.export_report());
222/// ```
223pub struct PerformanceDashboard {
224    /// Per-circuit statistics
225    stats: HashMap<String, CircuitStats>,
226    /// Global history (most recent first, bounded by config buffer_size)
227    history: VecDeque<(String, ExecutionMetrics)>,
228    /// Dashboard configuration
229    config: DashboardConfig,
230    /// Creation timestamp
231    created_at: SystemTime,
232}
233
234impl PerformanceDashboard {
235    /// Create a new dashboard with the given configuration.
236    pub fn new(config: DashboardConfig) -> Self {
237        Self {
238            stats: HashMap::new(),
239            history: VecDeque::new(),
240            config,
241            created_at: SystemTime::now(),
242        }
243    }
244
245    /// Record one circuit execution.
246    ///
247    /// The metrics are associated with `circuit_id` and added to the global
248    /// rolling history buffer.
249    pub fn record_execution(&mut self, circuit_id: &str, metrics: ExecutionMetrics) {
250        // Update per-circuit stats
251        self.stats
252            .entry(circuit_id.to_string())
253            .or_insert_with(CircuitStats::new)
254            .record(&metrics);
255
256        // Add to rolling history
257        self.history.push_front((circuit_id.to_string(), metrics));
258        let buffer = self.config.data_config.buffer_size;
259        while self.history.len() > buffer {
260            self.history.pop_back();
261        }
262    }
263
264    /// Compute aggregated statistics across all recorded executions.
265    pub fn get_summary(&self) -> DashboardSummary {
266        if self.stats.is_empty() {
267            return DashboardSummary {
268                total_executions: 0,
269                successful_executions: 0,
270                success_rate: 0.0,
271                mean_fidelity: 0.0,
272                min_fidelity: 0.0,
273                max_fidelity: 0.0,
274                mean_execution_time_ms: 0.0,
275                p95_execution_time_ms: 0.0,
276                mean_error_rate: 0.0,
277                mean_gate_count: 0.0,
278                mean_circuit_depth: 0.0,
279                distinct_circuits: 0,
280            };
281        }
282
283        let total_executions: usize = self.stats.values().map(|s| s.count).sum();
284        let successful_executions: usize = self.stats.values().map(|s| s.successes).sum();
285
286        let sum_fidelity: f64 = self.stats.values().map(|s| s.total_fidelity).sum();
287        let sum_time: f64 = self.stats.values().map(|s| s.total_execution_time_ms).sum();
288        let sum_error: f64 = self.stats.values().map(|s| s.total_error_rate).sum();
289        let sum_gates: usize = self.stats.values().map(|s| s.total_gate_count).sum();
290        let sum_depth: usize = self.stats.values().map(|s| s.total_circuit_depth).sum();
291
292        // Min/max fidelity from per-execution history
293        let (min_fidelity, max_fidelity) = self
294            .history
295            .iter()
296            .fold((f64::MAX, f64::MIN), |(mn, mx), (_, m)| {
297                (mn.min(m.fidelity), mx.max(m.fidelity))
298            });
299        let min_fidelity = if min_fidelity == f64::MAX {
300            0.0
301        } else {
302            min_fidelity
303        };
304        let max_fidelity = if max_fidelity == f64::MIN {
305            0.0
306        } else {
307            max_fidelity
308        };
309
310        // Global p95 execution time from history
311        let mut all_times: Vec<f64> = self
312            .history
313            .iter()
314            .map(|(_, m)| m.execution_time_ms)
315            .collect();
316        all_times.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
317        let p95_idx = ((0.95 * (all_times.len() as f64 - 1.0)).round() as usize)
318            .min(all_times.len().saturating_sub(1));
319        let p95_execution_time_ms = all_times.get(p95_idx).copied().unwrap_or(0.0);
320
321        let n = total_executions as f64;
322        DashboardSummary {
323            total_executions,
324            successful_executions,
325            success_rate: if total_executions > 0 {
326                successful_executions as f64 / n
327            } else {
328                0.0
329            },
330            mean_fidelity: sum_fidelity / n,
331            min_fidelity,
332            max_fidelity,
333            mean_execution_time_ms: sum_time / n,
334            p95_execution_time_ms,
335            mean_error_rate: sum_error / n,
336            mean_gate_count: sum_gates as f64 / n,
337            mean_circuit_depth: sum_depth as f64 / n,
338            distinct_circuits: self.stats.len(),
339        }
340    }
341
342    /// Export a Markdown-formatted performance report.
343    pub fn export_report(&self) -> String {
344        let summary = self.get_summary();
345        let uptime = self.created_at.elapsed().unwrap_or_default().as_secs();
346
347        let mut report = String::new();
348        report.push_str("# QuantRS2 Device Performance Dashboard\n\n");
349        report.push_str(&format!("**Dashboard uptime:** {}s\n\n", uptime));
350
351        report.push_str("## Summary\n\n");
352        report.push_str("| Metric | Value |\n");
353        report.push_str("|--------|-------|\n");
354        report.push_str(&format!(
355            "| Total Executions | {} |\n",
356            summary.total_executions
357        ));
358        report.push_str(&format!(
359            "| Successful Executions | {} |\n",
360            summary.successful_executions
361        ));
362        report.push_str(&format!(
363            "| Success Rate | {:.2}% |\n",
364            summary.success_rate * 100.0
365        ));
366        report.push_str(&format!(
367            "| Mean Fidelity | {:.4} |\n",
368            summary.mean_fidelity
369        ));
370        report.push_str(&format!("| Min Fidelity | {:.4} |\n", summary.min_fidelity));
371        report.push_str(&format!("| Max Fidelity | {:.4} |\n", summary.max_fidelity));
372        report.push_str(&format!(
373            "| Mean Execution Time | {:.2} ms |\n",
374            summary.mean_execution_time_ms
375        ));
376        report.push_str(&format!(
377            "| P95 Execution Time | {:.2} ms |\n",
378            summary.p95_execution_time_ms
379        ));
380        report.push_str(&format!(
381            "| Mean Error Rate | {:.6} |\n",
382            summary.mean_error_rate
383        ));
384        report.push_str(&format!(
385            "| Mean Gate Count | {:.1} |\n",
386            summary.mean_gate_count
387        ));
388        report.push_str(&format!(
389            "| Mean Circuit Depth | {:.1} |\n",
390            summary.mean_circuit_depth
391        ));
392        report.push_str(&format!(
393            "| Distinct Circuits Tracked | {} |\n",
394            summary.distinct_circuits
395        ));
396
397        // Per-circuit breakdown
398        if !self.stats.is_empty() {
399            report.push_str("\n## Per-Circuit Breakdown\n\n");
400            report.push_str(
401                "| Circuit ID | Executions | Success Rate | Mean Fidelity | Mean Time (ms) |\n",
402            );
403            report.push_str(
404                "|-----------|-----------|-------------|--------------|---------------|\n",
405            );
406
407            let mut circuit_ids: Vec<&String> = self.stats.keys().collect();
408            circuit_ids.sort();
409            for id in circuit_ids {
410                let s = match self.stats.get(id) {
411                    Some(s) => s,
412                    None => continue,
413                };
414                let mean_f = if s.count > 0 {
415                    s.total_fidelity / s.count as f64
416                } else {
417                    0.0
418                };
419                let mean_t = if s.count > 0 {
420                    s.total_execution_time_ms / s.count as f64
421                } else {
422                    0.0
423                };
424                let sr = if s.count > 0 {
425                    s.successes as f64 / s.count as f64
426                } else {
427                    0.0
428                };
429                report.push_str(&format!(
430                    "| {} | {} | {:.1}% | {:.4} | {:.2} |\n",
431                    id,
432                    s.count,
433                    sr * 100.0,
434                    mean_f,
435                    mean_t
436                ));
437            }
438        }
439
440        report
441    }
442
443    /// Return the number of executions recorded for a specific circuit.
444    pub fn execution_count(&self, circuit_id: &str) -> usize {
445        self.stats.get(circuit_id).map_or(0, |s| s.count)
446    }
447
448    /// Clear all recorded data.
449    pub fn reset(&mut self) {
450        self.stats.clear();
451        self.history.clear();
452    }
453}
454
455impl Default for DashboardConfig {
456    fn default() -> Self {
457        use crate::performance_dashboard::{
458            alerting::{AlertingConfig, AnomalyDetectionAlgorithm, AnomalyDetectionConfig},
459            data_collection::{
460                AggregationConfig, AggregationFunction, DataCollectionConfig, MetricsConfig,
461                PerformanceMetric, QualityMetric, ResourceMetric, SamplingConfig, SamplingStrategy,
462                TimeWindow,
463            },
464            ml_analytics::{
465                EvaluationConfig, EvaluationMetric, FeatureConfig, MLAnalyticsConfig,
466                ModelSelectionCriteria, TrainingConfig,
467            },
468            optimization::{DashboardOptimizationConfig, OptimizationObjective},
469            reporting::{
470                DistributionConfig, ReportFormat, ReportFrequency, ReportSchedule, ReportingConfig,
471            },
472            visualization::{
473                ColorScheme, GridLayout, InteractiveConfig, LayoutConfig, ThemeConfig,
474                VisualizationConfig,
475            },
476        };
477        use std::collections::HashMap;
478        use std::time::Duration;
479
480        DashboardConfig {
481            enable_realtime_monitoring: false,
482            data_config: DataCollectionConfig {
483                collection_interval: 60,
484                buffer_size: 1000,
485                retention_days: 30,
486                metrics_config: MetricsConfig {
487                    performance_metrics: vec![
488                        PerformanceMetric::Fidelity,
489                        PerformanceMetric::Latency,
490                        PerformanceMetric::ErrorRate,
491                    ],
492                    resource_metrics: vec![ResourceMetric::CpuUtilization],
493                    quality_metrics: vec![QualityMetric::GateFidelity],
494                    custom_metrics: vec![],
495                },
496                aggregation_config: AggregationConfig {
497                    aggregation_functions: vec![
498                        AggregationFunction::Mean,
499                        AggregationFunction::Percentile(95.0),
500                    ],
501                    time_windows: vec![TimeWindow::Minutes(5), TimeWindow::Hours(1)],
502                    grouping_dimensions: vec!["circuit_id".to_string()],
503                },
504                sampling_config: SamplingConfig {
505                    sampling_strategy: SamplingStrategy::Fixed,
506                    sample_rate: 1.0,
507                    adaptive_sampling: false,
508                    quality_based_sampling: false,
509                },
510            },
511            visualization_config: VisualizationConfig {
512                refresh_rate: 60,
513                chart_types: vec![],
514                layout_config: LayoutConfig {
515                    grid_layout: GridLayout {
516                        rows: 4,
517                        columns: 3,
518                        gap_size: 8,
519                    },
520                    responsive_design: true,
521                    panel_configuration: vec![],
522                },
523                theme_config: ThemeConfig {
524                    color_scheme: ColorScheme::Default,
525                    dark_mode: false,
526                    custom_styling: HashMap::new(),
527                },
528                interactive_config: InteractiveConfig {
529                    enable_drill_down: true,
530                    enable_filtering: true,
531                    enable_zooming: true,
532                    enable_real_time_updates: false,
533                },
534            },
535            alerting_config: AlertingConfig {
536                enable_alerting: false,
537                alert_thresholds: HashMap::new(),
538                notification_channels: vec![],
539                escalation_rules: vec![],
540                anomaly_detection: AnomalyDetectionConfig {
541                    detection_algorithms: vec![AnomalyDetectionAlgorithm::StatisticalOutlier],
542                    sensitivity: 0.95,
543                    baseline_window: Duration::from_secs(3600),
544                    detection_window: Duration::from_secs(300),
545                },
546            },
547            ml_config: MLAnalyticsConfig {
548                enable_ml_analytics: false,
549                prediction_models: vec![],
550                feature_config: FeatureConfig {
551                    feature_selection_methods: vec![],
552                    feature_engineering_rules: vec![],
553                    dimensionality_reduction: None,
554                },
555                training_config: TrainingConfig {
556                    training_data_size: 1000,
557                    validation_split: 0.2,
558                    cross_validation_folds: 5,
559                    hyperparameter_tuning: false,
560                    model_selection_criteria: ModelSelectionCriteria::CrossValidationScore,
561                },
562                evaluation_config: EvaluationConfig {
563                    evaluation_metrics: vec![EvaluationMetric::RMSE],
564                    test_data_size: 200,
565                    evaluation_frequency: Duration::from_secs(3600),
566                    performance_tracking: true,
567                },
568            },
569            optimization_config: DashboardOptimizationConfig {
570                enable_auto_recommendations: false,
571                optimization_objectives: vec![OptimizationObjective::BalancedPerformance],
572                confidence_threshold: 0.8,
573                priority_weighting: HashMap::new(),
574            },
575            reporting_config: ReportingConfig {
576                enable_automated_reports: false,
577                report_schedule: ReportSchedule {
578                    frequency: ReportFrequency::Daily,
579                    time_of_day: "00:00".to_string(),
580                    time_zone: "UTC".to_string(),
581                    custom_schedule: None,
582                },
583                report_formats: vec![ReportFormat::HTML],
584                distribution_config: DistributionConfig {
585                    email_recipients: vec![],
586                    file_storage_locations: vec![],
587                    api_endpoints: vec![],
588                },
589            },
590        }
591    }
592}
593
594#[cfg(test)]
595mod dashboard_tests {
596    use super::*;
597
598    fn sample_metrics(fidelity: f64, success: bool, time_ms: f64) -> ExecutionMetrics {
599        ExecutionMetrics::new(time_ms, fidelity, success, 5, 3, 2, 1.0 - fidelity)
600    }
601
602    #[test]
603    fn test_dashboard_creation() {
604        let dashboard = PerformanceDashboard::new(DashboardConfig::default());
605        let summary = dashboard.get_summary();
606        assert_eq!(summary.total_executions, 0);
607        assert_eq!(summary.distinct_circuits, 0);
608    }
609
610    #[test]
611    fn test_record_and_summary() {
612        let mut dashboard = PerformanceDashboard::new(DashboardConfig::default());
613        dashboard.record_execution("bell_state", sample_metrics(0.99, true, 10.0));
614        dashboard.record_execution("bell_state", sample_metrics(0.97, true, 12.0));
615        dashboard.record_execution("ghz", sample_metrics(0.95, false, 20.0));
616
617        let summary = dashboard.get_summary();
618        assert_eq!(summary.total_executions, 3);
619        assert_eq!(summary.successful_executions, 2);
620        assert_eq!(summary.distinct_circuits, 2);
621        assert!((summary.success_rate - 2.0 / 3.0).abs() < 1e-6);
622        assert!(summary.mean_fidelity > 0.0);
623    }
624
625    #[test]
626    fn test_execution_count() {
627        let mut dashboard = PerformanceDashboard::new(DashboardConfig::default());
628        dashboard.record_execution("circ_a", sample_metrics(0.9, true, 5.0));
629        dashboard.record_execution("circ_a", sample_metrics(0.88, true, 6.0));
630
631        assert_eq!(dashboard.execution_count("circ_a"), 2);
632        assert_eq!(dashboard.execution_count("missing"), 0);
633    }
634
635    #[test]
636    fn test_export_report_contains_sections() {
637        let mut dashboard = PerformanceDashboard::new(DashboardConfig::default());
638        dashboard.record_execution("test_circuit", sample_metrics(0.99, true, 8.0));
639
640        let report = dashboard.export_report();
641        assert!(report.contains("# QuantRS2 Device Performance Dashboard"));
642        assert!(report.contains("## Summary"));
643        assert!(report.contains("## Per-Circuit Breakdown"));
644        assert!(report.contains("test_circuit"));
645    }
646
647    #[test]
648    fn test_reset() {
649        let mut dashboard = PerformanceDashboard::new(DashboardConfig::default());
650        dashboard.record_execution("circ", sample_metrics(0.9, true, 5.0));
651        assert_eq!(dashboard.get_summary().total_executions, 1);
652        dashboard.reset();
653        assert_eq!(dashboard.get_summary().total_executions, 0);
654    }
655}