Skip to main content

quantrs2_sim/
diagnostics.rs

1//! Comprehensive error handling and diagnostics for quantum simulation
2//!
3//! This module provides advanced error handling, performance diagnostics,
4//! and system health monitoring for the quantum simulation framework.
5
6use quantrs2_core::error::{QuantRS2Error, QuantRS2Result};
7use scirs2_core::Complex64;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10use std::sync::{Arc, Mutex};
11use std::time::{Duration, Instant};
12
13/// Comprehensive diagnostics system for quantum simulation
14#[derive(Debug, Clone)]
15pub struct SimulationDiagnostics {
16    /// Error tracking and categorization
17    error_tracker: Arc<Mutex<ErrorTracker>>,
18    /// Performance monitoring
19    performance_monitor: Arc<Mutex<PerformanceMonitor>>,
20    /// Memory usage tracking
21    memory_tracker: Arc<Mutex<MemoryTracker>>,
22    /// Circuit analysis results
23    circuit_analyzer: Arc<Mutex<CircuitAnalyzer>>,
24}
25
26/// Error tracking and categorization system
27#[derive(Debug, Default)]
28struct ErrorTracker {
29    /// Total error count by category
30    error_counts: HashMap<ErrorCategory, usize>,
31    /// Recent errors with timestamps
32    recent_errors: Vec<(Instant, ErrorInfo)>,
33    /// Error patterns and frequencies
34    error_patterns: HashMap<String, usize>,
35    /// Critical error threshold
36    critical_threshold: usize,
37}
38
39/// Performance monitoring system
40#[derive(Debug, Default)]
41struct PerformanceMonitor {
42    /// Operation timing statistics
43    operation_times: HashMap<String, OperationStats>,
44    /// Gate application performance
45    gate_performance: HashMap<String, GateStats>,
46    /// Memory allocation patterns
47    allocation_patterns: Vec<(Instant, usize, String)>,
48    /// Simulation throughput metrics
49    throughput_metrics: ThroughputMetrics,
50}
51
52/// Memory usage tracking
53#[derive(Debug, Default)]
54struct MemoryTracker {
55    /// Peak memory usage per operation
56    peak_memory: HashMap<String, usize>,
57    /// Memory efficiency metrics
58    efficiency_metrics: MemoryEfficiencyMetrics,
59    /// Buffer pool statistics
60    buffer_pool_stats: BufferPoolStats,
61    /// Memory leak detection
62    leak_detection: LeakDetectionStats,
63    /// Memory allocation patterns
64    allocation_patterns: Vec<(Instant, usize, String)>,
65}
66
67/// Circuit analysis and optimization recommendations
68#[derive(Debug, Default)]
69struct CircuitAnalyzer {
70    /// Circuit complexity metrics
71    complexity_metrics: ComplexityMetrics,
72    /// Gate count statistics
73    gate_statistics: HashMap<String, usize>,
74    /// Optimization opportunities
75    optimization_opportunities: Vec<OptimizationRecommendation>,
76    /// Circuit health score
77    health_score: f64,
78}
79
80/// Error categorization
81#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Serialize, Deserialize)]
82pub enum ErrorCategory {
83    /// Memory allocation errors
84    Memory,
85    /// Invalid circuit structure
86    Circuit,
87    /// Qubit index out of bounds
88    QubitIndex,
89    /// Mathematical computation errors
90    Computation,
91    /// Hardware/GPU related errors
92    Hardware,
93    /// Configuration errors
94    Configuration,
95    /// Threading/concurrency errors
96    Concurrency,
97    /// Unknown error category
98    Unknown,
99}
100
101/// Detailed error information
102#[derive(Debug, Clone, Serialize, Deserialize)]
103pub struct ErrorInfo {
104    pub category: ErrorCategory,
105    pub message: String,
106    pub context: HashMap<String, String>,
107    pub severity: ErrorSeverity,
108    pub suggested_fix: Option<String>,
109}
110
111/// Error severity levels
112#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
113pub enum ErrorSeverity {
114    Low,
115    Medium,
116    High,
117    Critical,
118}
119
120/// Operation timing statistics
121#[derive(Debug, Default, Clone)]
122struct OperationStats {
123    total_time: Duration,
124    call_count: usize,
125    min_time: Option<Duration>,
126    max_time: Option<Duration>,
127    recent_times: Vec<Duration>,
128}
129
130/// Gate-specific performance statistics
131#[derive(Debug, Default, Clone)]
132struct GateStats {
133    total_applications: usize,
134    total_time: Duration,
135    average_time: Duration,
136    qubits_affected: Vec<usize>,
137    efficiency_score: f64,
138}
139
140/// Throughput metrics
141#[derive(Debug, Default, Clone)]
142struct ThroughputMetrics {
143    gates_per_second: f64,
144    qubits_simulated_per_second: f64,
145    circuits_completed: usize,
146    average_circuit_time: Duration,
147}
148
149/// Memory efficiency metrics
150#[derive(Debug, Default, Clone)]
151struct MemoryEfficiencyMetrics {
152    buffer_reuse_rate: f64,
153    allocation_efficiency: f64,
154    peak_to_average_ratio: f64,
155    fragmentation_score: f64,
156}
157
158/// Buffer pool statistics
159#[derive(Debug, Default, Clone)]
160struct BufferPoolStats {
161    total_allocations: usize,
162    total_reuses: usize,
163    cache_hit_rate: f64,
164    average_buffer_lifetime: Duration,
165}
166
167/// Memory leak detection statistics
168#[derive(Debug, Default, Clone)]
169struct LeakDetectionStats {
170    suspicious_allocations: usize,
171    memory_growth_rate: f64,
172    long_lived_allocations: usize,
173}
174
175/// Circuit complexity metrics
176#[derive(Debug, Default, Clone)]
177struct ComplexityMetrics {
178    total_gates: usize,
179    depth: usize,
180    width: usize,
181    entanglement_measure: f64,
182    parallelization_potential: f64,
183}
184
185/// Optimization recommendations
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct OptimizationRecommendation {
188    pub category: OptimizationCategory,
189    pub description: String,
190    pub expected_improvement: f64,
191    pub implementation_difficulty: Difficulty,
192    pub priority: Priority,
193}
194
195/// Optimization categories
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub enum OptimizationCategory {
198    GateFusion,
199    CircuitReordering,
200    MemoryOptimization,
201    ParallelizationOpportunity,
202    AlgorithmicImprovement,
203}
204
205/// Implementation difficulty levels
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub enum Difficulty {
208    Easy,
209    Medium,
210    Hard,
211}
212
213/// Priority levels
214#[derive(Debug, Clone, Serialize, Deserialize)]
215pub enum Priority {
216    Low,
217    Medium,
218    High,
219    Critical,
220}
221
222/// Comprehensive diagnostic report
223#[derive(Debug, Serialize, Deserialize)]
224pub struct DiagnosticReport {
225    pub timestamp: String,
226    pub error_summary: ErrorSummary,
227    pub performance_summary: PerformanceSummary,
228    pub memory_summary: MemorySummary,
229    pub circuit_analysis: CircuitAnalysisSummary,
230    pub recommendations: Vec<OptimizationRecommendation>,
231    pub overall_health_score: f64,
232}
233
234#[derive(Debug, Serialize, Deserialize)]
235pub struct ErrorSummary {
236    pub total_errors: usize,
237    pub errors_by_category: HashMap<ErrorCategory, usize>,
238    pub critical_errors: usize,
239    pub error_rate: f64,
240}
241
242#[derive(Debug, Serialize, Deserialize)]
243pub struct PerformanceSummary {
244    pub average_gate_time: f64,
245    pub gates_per_second: f64,
246    pub memory_efficiency: f64,
247    pub parallelization_efficiency: f64,
248}
249
250#[derive(Debug, Serialize, Deserialize)]
251pub struct MemorySummary {
252    pub peak_memory_usage: usize,
253    pub buffer_pool_efficiency: f64,
254    pub memory_leak_risk: f64,
255    pub allocation_efficiency: f64,
256}
257
258#[derive(Debug, Serialize, Deserialize)]
259pub struct CircuitAnalysisSummary {
260    pub complexity_score: f64,
261    pub optimization_potential: f64,
262    pub gate_distribution: HashMap<String, usize>,
263    pub depth_analysis: DepthAnalysis,
264}
265
266#[derive(Debug, Serialize, Deserialize)]
267pub struct DepthAnalysis {
268    pub total_depth: usize,
269    pub critical_path_length: usize,
270    pub parallelization_opportunities: usize,
271}
272
273impl SimulationDiagnostics {
274    /// Create a new diagnostics system
275    #[must_use]
276    pub fn new() -> Self {
277        Self {
278            error_tracker: Arc::new(Mutex::new(ErrorTracker::default())),
279            performance_monitor: Arc::new(Mutex::new(PerformanceMonitor::default())),
280            memory_tracker: Arc::new(Mutex::new(MemoryTracker::default())),
281            circuit_analyzer: Arc::new(Mutex::new(CircuitAnalyzer::default())),
282        }
283    }
284
285    /// Record an error with detailed context
286    pub fn record_error(&self, error: &QuantRS2Error, context: HashMap<String, String>) {
287        let error_info = self.categorize_error(error, context);
288
289        if let Ok(mut tracker) = self.error_tracker.lock() {
290            tracker.record_error(error_info);
291        }
292    }
293
294    /// Record operation timing
295    pub fn record_operation_time(&self, operation: &str, duration: Duration) {
296        if let Ok(mut monitor) = self.performance_monitor.lock() {
297            monitor.record_operation(operation.to_string(), duration);
298        }
299    }
300
301    /// Record gate application performance
302    pub fn record_gate_performance(&self, gate_name: &str, qubits: &[usize], duration: Duration) {
303        if let Ok(mut monitor) = self.performance_monitor.lock() {
304            monitor.record_gate_performance(gate_name.to_string(), qubits.to_vec(), duration);
305        }
306    }
307
308    /// Record memory allocation
309    pub fn record_memory_allocation(&self, size: usize, operation: &str) {
310        if let Ok(mut tracker) = self.memory_tracker.lock() {
311            tracker.record_allocation(size, operation.to_string());
312        }
313    }
314
315    /// Analyze circuit complexity and optimization opportunities
316    pub fn analyze_circuit<const N: usize>(&self, circuit: &quantrs2_circuit::builder::Circuit<N>) {
317        if let Ok(mut analyzer) = self.circuit_analyzer.lock() {
318            analyzer.analyze_circuit(circuit);
319        }
320    }
321
322    /// Generate comprehensive diagnostic report
323    #[must_use]
324    pub fn generate_report(&self) -> DiagnosticReport {
325        let timestamp = chrono::Utc::now().to_rfc3339();
326
327        let error_summary = self
328            .error_tracker
329            .lock()
330            .map(|tracker| tracker.generate_summary())
331            .unwrap_or_default();
332
333        let performance_summary = self
334            .performance_monitor
335            .lock()
336            .map(|monitor| monitor.generate_summary())
337            .unwrap_or_default();
338
339        let memory_summary = self
340            .memory_tracker
341            .lock()
342            .map(|tracker| tracker.generate_summary())
343            .unwrap_or_default();
344
345        let circuit_analysis = self
346            .circuit_analyzer
347            .lock()
348            .map(|analyzer| analyzer.generate_summary())
349            .unwrap_or_default();
350
351        let recommendations = self.generate_recommendations();
352        let overall_health_score =
353            self.calculate_health_score(&error_summary, &performance_summary, &memory_summary);
354
355        DiagnosticReport {
356            timestamp,
357            error_summary,
358            performance_summary,
359            memory_summary,
360            circuit_analysis,
361            recommendations,
362            overall_health_score,
363        }
364    }
365
366    /// Categorize error for better tracking
367    fn categorize_error(
368        &self,
369        error: &QuantRS2Error,
370        context: HashMap<String, String>,
371    ) -> ErrorInfo {
372        let (category, severity, suggested_fix) = match error {
373            QuantRS2Error::InvalidQubitId(_) => (
374                ErrorCategory::QubitIndex,
375                ErrorSeverity::High,
376                Some("Check qubit indices are within circuit bounds".to_string()),
377            ),
378            QuantRS2Error::CircuitValidationFailed(_) => (
379                ErrorCategory::Circuit,
380                ErrorSeverity::Medium,
381                Some("Validate circuit structure before simulation".to_string()),
382            ),
383            QuantRS2Error::LinalgError(_) => (
384                ErrorCategory::Computation,
385                ErrorSeverity::High,
386                Some("Check matrix dimensions and numerical stability".to_string()),
387            ),
388            QuantRS2Error::UnsupportedOperation(_) => (
389                ErrorCategory::Configuration,
390                ErrorSeverity::Medium,
391                Some("Use supported gate types for this simulator".to_string()),
392            ),
393            QuantRS2Error::InvalidInput(_) => (
394                ErrorCategory::Configuration,
395                ErrorSeverity::Medium,
396                Some("Validate input parameters before operation".to_string()),
397            ),
398            _ => (ErrorCategory::Unknown, ErrorSeverity::Low, None),
399        };
400
401        ErrorInfo {
402            category,
403            message: error.to_string(),
404            context,
405            severity,
406            suggested_fix,
407        }
408    }
409
410    /// Generate optimization recommendations
411    fn generate_recommendations(&self) -> Vec<OptimizationRecommendation> {
412        let mut recommendations = Vec::new();
413
414        // Analyze performance patterns
415        if let Ok(monitor) = self.performance_monitor.lock() {
416            if !monitor.gate_performance.is_empty() {
417                let avg_gate_time: Duration = monitor
418                    .gate_performance
419                    .values()
420                    .map(|stats| stats.average_time)
421                    .sum::<Duration>()
422                    / monitor.gate_performance.len() as u32;
423
424                if avg_gate_time > Duration::from_millis(1) {
425                    recommendations.push(OptimizationRecommendation {
426                        category: OptimizationCategory::GateFusion,
427                        description: "Consider gate fusion to reduce operation overhead"
428                            .to_string(),
429                        expected_improvement: 0.3,
430                        implementation_difficulty: Difficulty::Medium,
431                        priority: Priority::High,
432                    });
433                }
434            }
435        }
436
437        // Analyze memory patterns
438        if let Ok(tracker) = self.memory_tracker.lock() {
439            if tracker.efficiency_metrics.buffer_reuse_rate < 0.7 {
440                recommendations.push(OptimizationRecommendation {
441                    category: OptimizationCategory::MemoryOptimization,
442                    description: "Improve buffer pool utilization for better memory efficiency"
443                        .to_string(),
444                    expected_improvement: 0.25,
445                    implementation_difficulty: Difficulty::Easy,
446                    priority: Priority::Medium,
447                });
448            }
449        }
450
451        // Analyze circuit structure
452        if let Ok(analyzer) = self.circuit_analyzer.lock() {
453            if analyzer.complexity_metrics.parallelization_potential > 0.5 {
454                recommendations.push(OptimizationRecommendation {
455                    category: OptimizationCategory::ParallelizationOpportunity,
456                    description:
457                        "Circuit has high parallelization potential - consider parallel execution"
458                            .to_string(),
459                    expected_improvement: 0.4,
460                    implementation_difficulty: Difficulty::Hard,
461                    priority: Priority::High,
462                });
463            }
464        }
465
466        recommendations
467    }
468
469    /// Calculate overall system health score
470    fn calculate_health_score(
471        &self,
472        error_summary: &ErrorSummary,
473        performance_summary: &PerformanceSummary,
474        memory_summary: &MemorySummary,
475    ) -> f64 {
476        let error_score = if error_summary.total_errors == 0 {
477            1.0
478        } else {
479            error_summary.error_rate.min(0.5).mul_add(-2.0, 1.0)
480        };
481
482        let performance_score = (performance_summary.gates_per_second / 1000.0).min(1.0);
483        let memory_score = memory_summary.buffer_pool_efficiency;
484
485        memory_score.mul_add(0.3, error_score * 0.4 + performance_score * 0.3) * 100.0
486    }
487}
488
489impl ErrorTracker {
490    fn record_error(&mut self, error_info: ErrorInfo) {
491        *self.error_counts.entry(error_info.category).or_insert(0) += 1;
492        self.recent_errors
493            .push((Instant::now(), error_info.clone()));
494
495        // Track error patterns
496        let pattern = format!(
497            "{:?}:{}",
498            error_info.category,
499            error_info
500                .message
501                .split_whitespace()
502                .take(3)
503                .collect::<Vec<_>>()
504                .join(" ")
505        );
506        *self.error_patterns.entry(pattern).or_insert(0) += 1;
507
508        // Keep only recent errors (last 100)
509        if self.recent_errors.len() > 100 {
510            self.recent_errors.remove(0);
511        }
512    }
513
514    fn generate_summary(&self) -> ErrorSummary {
515        let total_errors = self.recent_errors.len();
516        let critical_errors = self
517            .recent_errors
518            .iter()
519            .filter(|(_, error)| matches!(error.severity, ErrorSeverity::Critical))
520            .count();
521
522        let error_rate = if total_errors > 0 {
523            critical_errors as f64 / total_errors as f64
524        } else {
525            0.0
526        };
527
528        ErrorSummary {
529            total_errors,
530            errors_by_category: self.error_counts.clone(),
531            critical_errors,
532            error_rate,
533        }
534    }
535}
536
537impl PerformanceMonitor {
538    fn record_operation(&mut self, operation: String, duration: Duration) {
539        let stats = self.operation_times.entry(operation).or_default();
540        stats.total_time += duration;
541        stats.call_count += 1;
542
543        stats.min_time = Some(stats.min_time.map_or(duration, |min| min.min(duration)));
544        stats.max_time = Some(stats.max_time.map_or(duration, |max| max.max(duration)));
545
546        stats.recent_times.push(duration);
547        if stats.recent_times.len() > 50 {
548            stats.recent_times.remove(0);
549        }
550    }
551
552    fn record_gate_performance(
553        &mut self,
554        gate_name: String,
555        qubits: Vec<usize>,
556        duration: Duration,
557    ) {
558        let stats = self.gate_performance.entry(gate_name).or_default();
559        stats.total_applications += 1;
560        stats.total_time += duration;
561        stats.average_time = stats.total_time / stats.total_applications as u32;
562        stats.qubits_affected.extend(qubits);
563
564        // Calculate efficiency score based on time and qubit count
565        stats.efficiency_score =
566            1000.0 / (duration.as_nanos() as f64 / stats.qubits_affected.len() as f64);
567    }
568
569    fn generate_summary(&self) -> PerformanceSummary {
570        let average_gate_time = if self.gate_performance.is_empty() {
571            0.0
572        } else {
573            self.gate_performance
574                .values()
575                .map(|stats| stats.average_time.as_nanos() as f64)
576                .sum::<f64>()
577                / self.gate_performance.len() as f64
578        };
579
580        let gates_per_second = if average_gate_time > 0.0 {
581            1_000_000_000.0 / average_gate_time
582        } else {
583            0.0
584        };
585
586        PerformanceSummary {
587            average_gate_time,
588            gates_per_second,
589            memory_efficiency: self.compute_memory_efficiency(),
590            parallelization_efficiency: self.compute_parallelization_efficiency(),
591        }
592    }
593
594    /// Realized memory efficiency in `[0, 1]` from the monitor's own allocation
595    /// records: the fraction of allocations whose byte size had already been seen
596    /// (and could thus have been served from a reused buffer). Returns `0.0` when
597    /// no allocations have been recorded.
598    fn compute_memory_efficiency(&self) -> f64 {
599        if self.allocation_patterns.is_empty() {
600            return 0.0;
601        }
602        let mut seen_sizes: std::collections::HashSet<usize> = std::collections::HashSet::new();
603        let mut reusable = 0usize;
604        for (_, size, _) in &self.allocation_patterns {
605            if seen_sizes.contains(size) {
606                reusable += 1;
607            } else {
608                seen_sizes.insert(*size);
609            }
610        }
611        reusable as f64 / self.allocation_patterns.len() as f64
612    }
613
614    /// Parallelization efficiency in `[0, 1]`.
615    ///
616    /// True parallel efficiency (parallel speedup / thread count) requires
617    /// matched serial-vs-parallel execution timings, which this monitor does not
618    /// collect. To avoid fabricating a measurement we report `0.0` ("not
619    /// measured") unless throughput metrics carrying a real measured value have
620    /// been populated, in which case that value is normalized and returned.
621    fn compute_parallelization_efficiency(&self) -> f64 {
622        // `circuits_completed` is only non-zero once real throughput sampling has
623        // populated the metrics; without it there is no parallel signal to report.
624        if self.throughput_metrics.circuits_completed == 0 {
625            return 0.0;
626        }
627        // Normalize observed gate throughput against per-qubit throughput as a
628        // bounded, data-derived efficiency proxy.
629        let gates = self.throughput_metrics.gates_per_second;
630        let qubits = self.throughput_metrics.qubits_simulated_per_second;
631        if gates <= 0.0 || qubits <= 0.0 {
632            return 0.0;
633        }
634        (qubits / gates).clamp(0.0, 1.0)
635    }
636}
637
638impl MemoryTracker {
639    fn record_allocation(&mut self, size: usize, operation: String) {
640        self.allocation_patterns
641            .push((Instant::now(), size, operation.clone()));
642
643        // Update peak memory for operation
644        let current_peak = self.peak_memory.entry(operation).or_insert(0);
645        *current_peak = (*current_peak).max(size);
646
647        // Keep only recent allocations
648        if self.allocation_patterns.len() > 1000 {
649            self.allocation_patterns.remove(0);
650        }
651    }
652
653    fn generate_summary(&self) -> MemorySummary {
654        let peak_memory_usage = self.peak_memory.values().max().copied().unwrap_or(0);
655
656        MemorySummary {
657            peak_memory_usage,
658            buffer_pool_efficiency: self.compute_buffer_pool_efficiency(),
659            memory_leak_risk: self.compute_memory_leak_risk(),
660            allocation_efficiency: self.compute_allocation_efficiency(),
661        }
662    }
663
664    /// Realized buffer-pool efficiency in `[0, 1]`.
665    ///
666    /// When explicit buffer-pool statistics have been recorded, this is the true
667    /// reuse rate `reuses / (allocations + reuses)`. Otherwise it is derived from
668    /// the observed allocation pattern as the fraction of allocations whose byte
669    /// size was already seen before (i.e. a buffer of that size could have been
670    /// reused from a pool). With no allocations recorded the result is `0.0`.
671    fn compute_buffer_pool_efficiency(&self) -> f64 {
672        let recorded =
673            self.buffer_pool_stats.total_allocations + self.buffer_pool_stats.total_reuses;
674        if recorded > 0 {
675            return self.buffer_pool_stats.total_reuses as f64 / recorded as f64;
676        }
677
678        if self.allocation_patterns.is_empty() {
679            return 0.0;
680        }
681
682        let mut seen_sizes: std::collections::HashSet<usize> = std::collections::HashSet::new();
683        let mut reusable = 0usize;
684        for (_, size, _) in &self.allocation_patterns {
685            if seen_sizes.contains(size) {
686                reusable += 1;
687            } else {
688                seen_sizes.insert(*size);
689            }
690        }
691        reusable as f64 / self.allocation_patterns.len() as f64
692    }
693
694    /// Allocation efficiency in `[0, 1]`: the share of total allocated bytes that
695    /// is still "live" at peak, i.e. `peak_memory / total_allocated_bytes`.
696    ///
697    /// A value near 1 means allocations are tightly clustered around the peak
698    /// working set (little churn); a low value means many transient allocations
699    /// were made and freed relative to the peak footprint. Returns `0.0` when no
700    /// allocations have been recorded.
701    fn compute_allocation_efficiency(&self) -> f64 {
702        if self.allocation_patterns.is_empty() {
703            return 0.0;
704        }
705        let total_allocated: usize = self
706            .allocation_patterns
707            .iter()
708            .map(|(_, size, _)| *size)
709            .sum();
710        if total_allocated == 0 {
711            return 0.0;
712        }
713        let peak = self.peak_memory.values().max().copied().unwrap_or(0);
714        (peak as f64 / total_allocated as f64).clamp(0.0, 1.0)
715    }
716
717    /// Memory-leak risk in `[0, 1]` derived from the cumulative-allocation growth
718    /// trend across the recorded window.
719    ///
720    /// The allocation sizes are split into a first and second half (ordered by
721    /// record time); the risk is the normalized positive growth of mean
722    /// allocation size from the first half to the second. Sustained growth in
723    /// allocation size is the in-process signal of a leak. Fewer than two samples
724    /// (no trend observable) yields `0.0`.
725    fn compute_memory_leak_risk(&self) -> f64 {
726        if self.leak_detection.memory_growth_rate > 0.0 {
727            return self.leak_detection.memory_growth_rate.clamp(0.0, 1.0);
728        }
729
730        let count = self.allocation_patterns.len();
731        if count < 2 {
732            return 0.0;
733        }
734        let mid = count / 2;
735        let first_sum: usize = self.allocation_patterns[..mid]
736            .iter()
737            .map(|(_, size, _)| *size)
738            .sum();
739        let second_sum: usize = self.allocation_patterns[mid..]
740            .iter()
741            .map(|(_, size, _)| *size)
742            .sum();
743        let first_mean = first_sum as f64 / mid.max(1) as f64;
744        let second_mean = second_sum as f64 / (count - mid).max(1) as f64;
745        if first_mean <= 0.0 {
746            return 0.0;
747        }
748        let growth = (second_mean - first_mean) / first_mean;
749        growth.clamp(0.0, 1.0)
750    }
751}
752
753impl CircuitAnalyzer {
754    fn analyze_circuit<const N: usize>(&mut self, circuit: &quantrs2_circuit::builder::Circuit<N>) {
755        self.complexity_metrics.width = N;
756        self.complexity_metrics.total_gates = circuit.gates().len();
757
758        // Analyze gate distribution
759        for gate in circuit.gates() {
760            *self
761                .gate_statistics
762                .entry(gate.name().to_string())
763                .or_insert(0) += 1;
764        }
765
766        // Real circuit depth (critical-path length) from gate ordering and
767        // per-qubit dependencies. Previously this was left at 0, which made the
768        // depth-based health score and depth analysis report meaningless values.
769        self.complexity_metrics.depth = Self::compute_circuit_depth(circuit);
770
771        // Calculate complexity score
772        self.complexity_metrics.entanglement_measure = self.calculate_entanglement_measure();
773        self.complexity_metrics.parallelization_potential =
774            self.calculate_parallelization_potential(circuit);
775
776        // Calculate overall health score
777        self.health_score = self.calculate_circuit_health();
778    }
779
780    /// Compute the circuit depth (critical-path length) from the gate sequence.
781    ///
782    /// Each gate's depth is one more than the maximum depth currently assigned to
783    /// any qubit it acts on; the result is the deepest qubit track. This is the
784    /// standard layered-depth (ASAP) computation, a real function of gate order
785    /// and qubit usage.
786    fn compute_circuit_depth<const N: usize>(
787        circuit: &quantrs2_circuit::builder::Circuit<N>,
788    ) -> usize {
789        let mut qubit_depths: HashMap<u32, usize> = HashMap::new();
790        let mut max_depth = 0usize;
791        for gate in circuit.gates() {
792            let qubits = gate.qubits();
793            let input_depth = qubits
794                .iter()
795                .map(|q| qubit_depths.get(&q.id()).copied().unwrap_or(0))
796                .max()
797                .unwrap_or(0);
798            let new_depth = input_depth + 1;
799            for q in &qubits {
800                qubit_depths.insert(q.id(), new_depth);
801            }
802            max_depth = max_depth.max(new_depth);
803        }
804        max_depth
805    }
806
807    fn calculate_entanglement_measure(&self) -> f64 {
808        // Simplified entanglement measure based on two-qubit gates
809        let two_qubit_gates = self
810            .gate_statistics
811            .iter()
812            .filter(|(name, _)| matches!(name.as_str(), "CNOT" | "CZ" | "SWAP" | "CY" | "CH"))
813            .map(|(_, count)| *count)
814            .sum::<usize>();
815
816        two_qubit_gates as f64 / self.complexity_metrics.total_gates.max(1) as f64
817    }
818
819    /// Parallelization potential in `[0, 1]` derived from the real circuit DAG.
820    ///
821    /// Using the ASAP layered depth, the maximum achievable parallel width is
822    /// `total_gates / depth` (gates per layer). The potential is the fraction of
823    /// work that overlaps beyond the serial critical path:
824    /// `1 - depth / total_gates`. A purely sequential circuit (depth == gates)
825    /// scores 0; a fully parallel single-layer circuit approaches 1. Empty
826    /// circuits score 0.
827    fn calculate_parallelization_potential<const N: usize>(
828        &self,
829        circuit: &quantrs2_circuit::builder::Circuit<N>,
830    ) -> f64 {
831        let total_gates = self.complexity_metrics.total_gates;
832        if total_gates == 0 {
833            return 0.0;
834        }
835        let depth = if self.complexity_metrics.depth > 0 {
836            self.complexity_metrics.depth
837        } else {
838            Self::compute_circuit_depth(circuit)
839        };
840        (1.0 - depth as f64 / total_gates as f64).clamp(0.0, 1.0)
841    }
842
843    fn calculate_circuit_health(&self) -> f64 {
844        // Health score based on various factors
845        let depth_score = if self.complexity_metrics.depth > 0 {
846            1.0 / (1.0 + (self.complexity_metrics.depth as f64 / 100.0))
847        } else {
848            1.0
849        };
850
851        let complexity_score = 1.0 - self.complexity_metrics.entanglement_measure.min(1.0);
852        let parallelization_score = self.complexity_metrics.parallelization_potential;
853
854        (depth_score + complexity_score + parallelization_score) / 3.0 * 100.0
855    }
856
857    fn generate_summary(&self) -> CircuitAnalysisSummary {
858        CircuitAnalysisSummary {
859            complexity_score: self.complexity_metrics.entanglement_measure * 100.0,
860            optimization_potential: self.complexity_metrics.parallelization_potential * 100.0,
861            gate_distribution: self.gate_statistics.clone(),
862            depth_analysis: DepthAnalysis {
863                total_depth: self.complexity_metrics.depth,
864                // Layered (ASAP) depth equals the critical-path length.
865                critical_path_length: self.complexity_metrics.depth,
866                parallelization_opportunities: (self.complexity_metrics.parallelization_potential
867                    * 10.0) as usize,
868            },
869        }
870    }
871}
872
873impl Default for ErrorSummary {
874    fn default() -> Self {
875        Self {
876            total_errors: 0,
877            errors_by_category: HashMap::new(),
878            critical_errors: 0,
879            error_rate: 0.0,
880        }
881    }
882}
883
884impl Default for PerformanceSummary {
885    fn default() -> Self {
886        Self {
887            average_gate_time: 0.0,
888            gates_per_second: 0.0,
889            memory_efficiency: 0.0,
890            parallelization_efficiency: 0.0,
891        }
892    }
893}
894
895impl Default for MemorySummary {
896    fn default() -> Self {
897        Self {
898            peak_memory_usage: 0,
899            buffer_pool_efficiency: 0.0,
900            memory_leak_risk: 0.0,
901            allocation_efficiency: 0.0,
902        }
903    }
904}
905
906impl Default for CircuitAnalysisSummary {
907    fn default() -> Self {
908        Self {
909            complexity_score: 0.0,
910            optimization_potential: 0.0,
911            gate_distribution: HashMap::new(),
912            depth_analysis: DepthAnalysis {
913                total_depth: 0,
914                critical_path_length: 0,
915                parallelization_opportunities: 0,
916            },
917        }
918    }
919}
920
921impl Default for SimulationDiagnostics {
922    fn default() -> Self {
923        Self::new()
924    }
925}
926
927#[cfg(test)]
928mod tests {
929    use super::*;
930
931    #[test]
932    fn test_diagnostics_creation() {
933        let diagnostics = SimulationDiagnostics::new();
934        let report = diagnostics.generate_report();
935
936        assert_eq!(report.error_summary.total_errors, 0);
937        assert!(report.overall_health_score >= 0.0);
938    }
939
940    #[test]
941    fn test_error_recording() {
942        let diagnostics = SimulationDiagnostics::new();
943        let error = QuantRS2Error::InvalidQubitId(5);
944        let mut context = HashMap::new();
945        context.insert("operation".to_string(), "gate_application".to_string());
946
947        diagnostics.record_error(&error, context);
948
949        let report = diagnostics.generate_report();
950        assert_eq!(report.error_summary.total_errors, 1);
951        assert!(report
952            .error_summary
953            .errors_by_category
954            .contains_key(&ErrorCategory::QubitIndex));
955    }
956
957    #[test]
958    fn test_performance_recording() {
959        let diagnostics = SimulationDiagnostics::new();
960
961        diagnostics.record_operation_time("gate_application", Duration::from_millis(10));
962        diagnostics.record_gate_performance("H", &[0], Duration::from_micros(500));
963
964        let report = diagnostics.generate_report();
965        assert!(report.performance_summary.average_gate_time > 0.0);
966    }
967}