Skip to main content

quantrs2_sim/
performance_prediction.rs

1//! Performance Prediction Models for Circuit Execution Time Estimation
2//!
3//! This module provides sophisticated models for predicting quantum circuit
4//! execution times across different simulation backends using `SciRS2` analysis
5//! tools and machine learning techniques.
6
7use crate::{
8    auto_optimizer::{AnalysisDepth, BackendType, CircuitCharacteristics},
9    error::{Result, SimulatorError},
10    scirs2_integration::{Matrix, SciRS2Backend, Vector},
11};
12use quantrs2_circuit::builder::Circuit;
13use quantrs2_core::{
14    error::{QuantRS2Error, QuantRS2Result},
15    gate::GateOp,
16    qubit::QubitId,
17};
18use scirs2_core::Complex64;
19use serde::{Deserialize, Serialize};
20use std::collections::{HashMap, VecDeque};
21use std::time::{Duration, Instant};
22
23/// Configuration for performance prediction models
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct PerformancePredictionConfig {
26    /// Enable machine learning-based predictions
27    pub enable_ml_prediction: bool,
28    /// Maximum historical data points to maintain
29    pub max_history_size: usize,
30    /// Confidence threshold for predictions (0.0 to 1.0)
31    pub confidence_threshold: f64,
32    /// Enable hardware profiling for adaptive predictions
33    pub enable_hardware_profiling: bool,
34    /// `SciRS2` analysis depth for complexity estimation
35    pub analysis_depth: AnalysisDepth,
36    /// Prediction strategy to use
37    pub prediction_strategy: PredictionStrategy,
38    /// Learning rate for adaptive models
39    pub learning_rate: f64,
40    /// Enable cross-backend performance transfer learning
41    pub enable_transfer_learning: bool,
42    /// Minimum samples required before using ML predictions
43    pub min_samples_for_ml: usize,
44}
45
46impl Default for PerformancePredictionConfig {
47    fn default() -> Self {
48        Self {
49            enable_ml_prediction: true,
50            max_history_size: 10_000,
51            confidence_threshold: 0.8,
52            enable_hardware_profiling: true,
53            analysis_depth: AnalysisDepth::Deep,
54            prediction_strategy: PredictionStrategy::Hybrid,
55            learning_rate: 0.01,
56            enable_transfer_learning: true,
57            min_samples_for_ml: 100,
58        }
59    }
60}
61
62/// Prediction strategy for execution time estimation
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum PredictionStrategy {
65    /// Static analysis only
66    StaticAnalysis,
67    /// Machine learning only
68    MachineLearning,
69    /// Hybrid approach (static + ML)
70    Hybrid,
71    /// Ensemble of multiple models
72    Ensemble,
73}
74
75/// Performance prediction model types
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77pub enum ModelType {
78    /// Linear regression model
79    LinearRegression,
80    /// Polynomial regression model
81    PolynomialRegression,
82    /// Neural network model
83    NeuralNetwork,
84    /// Support vector regression
85    SupportVectorRegression,
86    /// Random forest model
87    RandomForest,
88    /// Gradient boosting model
89    GradientBoosting,
90}
91
92/// Circuit complexity metrics for prediction
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ComplexityMetrics {
95    /// Total number of gates
96    pub gate_count: usize,
97    /// Circuit depth (critical path length)
98    pub circuit_depth: usize,
99    /// Number of qubits
100    pub qubit_count: usize,
101    /// Number of two-qubit gates
102    pub two_qubit_gate_count: usize,
103    /// Estimated memory requirement (bytes)
104    pub memory_requirement: usize,
105    /// Parallelism potential (0.0 to 1.0)
106    pub parallelism_factor: f64,
107    /// Entanglement complexity measure
108    pub entanglement_complexity: f64,
109    /// Gate type distribution
110    pub gate_type_distribution: HashMap<String, usize>,
111    /// Critical path analysis
112    pub critical_path_complexity: f64,
113    /// Resource estimation
114    pub resource_estimation: ResourceMetrics,
115}
116
117impl Default for ComplexityMetrics {
118    fn default() -> Self {
119        Self {
120            gate_count: 0,
121            circuit_depth: 0,
122            qubit_count: 0,
123            two_qubit_gate_count: 0,
124            memory_requirement: 0,
125            parallelism_factor: 0.0,
126            entanglement_complexity: 0.0,
127            gate_type_distribution: HashMap::new(),
128            critical_path_complexity: 0.0,
129            resource_estimation: ResourceMetrics::default(),
130        }
131    }
132}
133
134/// Resource requirements metrics
135#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ResourceMetrics {
137    /// Estimated CPU time (seconds)
138    pub cpu_time_estimate: f64,
139    /// Estimated memory usage (bytes)
140    pub memory_usage_estimate: usize,
141    /// Estimated I/O operations
142    pub io_operations_estimate: usize,
143    /// Network bandwidth requirement (bytes/sec)
144    pub network_bandwidth_estimate: usize,
145    /// GPU memory requirement (bytes)
146    pub gpu_memory_estimate: usize,
147    /// Parallel thread requirement
148    pub thread_requirement: usize,
149}
150
151/// Historical execution data point
152#[derive(Debug, Clone, Serialize)]
153pub struct ExecutionDataPoint {
154    /// Circuit complexity metrics
155    pub complexity: ComplexityMetrics,
156    /// Backend used for execution
157    pub backend_type: BackendType,
158    /// Actual execution time
159    pub execution_time: Duration,
160    /// Hardware specifications during execution
161    pub hardware_specs: PerformanceHardwareSpecs,
162    /// Timestamp of execution
163    #[serde(skip_serializing, skip_deserializing)]
164    pub timestamp: std::time::SystemTime,
165    /// Success flag
166    pub success: bool,
167    /// Error information if failed
168    pub error_info: Option<String>,
169}
170
171impl Default for ExecutionDataPoint {
172    fn default() -> Self {
173        Self {
174            complexity: ComplexityMetrics::default(),
175            backend_type: BackendType::StateVector,
176            execution_time: Duration::from_secs(0),
177            hardware_specs: PerformanceHardwareSpecs::default(),
178            timestamp: std::time::SystemTime::now(),
179            success: false,
180            error_info: None,
181        }
182    }
183}
184
185/// Hardware specifications for context
186#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct PerformanceHardwareSpecs {
188    /// CPU cores available
189    pub cpu_cores: usize,
190    /// Total system memory (bytes)
191    pub total_memory: usize,
192    /// Available memory at execution time (bytes)
193    pub available_memory: usize,
194    /// GPU memory (bytes, if available)
195    pub gpu_memory: Option<usize>,
196    /// CPU frequency (MHz)
197    pub cpu_frequency: f64,
198    /// Network bandwidth (Mbps, for distributed)
199    pub network_bandwidth: Option<f64>,
200    /// System load average
201    pub load_average: f64,
202}
203
204impl Default for PerformanceHardwareSpecs {
205    fn default() -> Self {
206        Self {
207            cpu_cores: 1,
208            total_memory: 1024 * 1024 * 1024,    // 1GB
209            available_memory: 512 * 1024 * 1024, // 512MB
210            gpu_memory: None,
211            cpu_frequency: 2000.0, // 2GHz
212            network_bandwidth: None,
213            load_average: 0.0,
214        }
215    }
216}
217
218/// Prediction result with confidence metrics
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct PredictionResult {
221    /// Predicted execution time
222    pub predicted_time: Duration,
223    /// Confidence in prediction (0.0 to 1.0)
224    pub confidence: f64,
225    /// Prediction interval (lower bound, upper bound)
226    pub prediction_interval: (Duration, Duration),
227    /// Model used for prediction
228    pub model_type: ModelType,
229    /// Feature importance scores
230    pub feature_importance: HashMap<String, f64>,
231    /// Prediction metadata
232    pub metadata: PredictionMetadata,
233}
234
235/// Metadata about the prediction process
236#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct PredictionMetadata {
238    /// Time taken to generate prediction
239    pub prediction_time: Duration,
240    /// Number of historical samples used
241    pub samples_used: usize,
242    /// Model training status
243    pub model_trained: bool,
244    /// Cross-validation score (if available)
245    pub cv_score: Option<f64>,
246    /// Prediction method used
247    pub prediction_method: String,
248}
249
250/// Performance prediction engine
251pub struct PerformancePredictionEngine {
252    /// Configuration
253    config: PerformancePredictionConfig,
254    /// Historical execution data
255    execution_history: VecDeque<ExecutionDataPoint>,
256    /// Trained models for different backends
257    trained_models: HashMap<BackendType, TrainedModel>,
258    /// `SciRS2` backend for analysis
259    scirs2_backend: SciRS2Backend,
260    /// Current hardware specifications
261    current_hardware: PerformanceHardwareSpecs,
262    /// Performance statistics
263    prediction_stats: PredictionStatistics,
264    /// Running accumulators for prediction-latency statistics (nanoseconds):
265    /// (count, sum, sum-of-squares). Used to compute an exact mean/standard
266    /// deviation without storing the full latency history.
267    timing_accumulator: (u64, f64, f64),
268}
269
270/// Trained machine learning model
271#[derive(Debug, Clone, Serialize)]
272pub struct TrainedModel {
273    /// Model type
274    pub model_type: ModelType,
275    /// Model parameters (simplified representation)
276    pub parameters: Vec<f64>,
277    /// Feature weights
278    pub feature_weights: HashMap<String, f64>,
279    /// Training statistics
280    pub training_stats: TrainingStatistics,
281    /// Last training time
282    #[serde(skip_serializing, skip_deserializing)]
283    pub last_trained: std::time::SystemTime,
284}
285
286impl Default for TrainedModel {
287    fn default() -> Self {
288        Self {
289            model_type: ModelType::LinearRegression,
290            parameters: Vec::new(),
291            feature_weights: HashMap::new(),
292            training_stats: TrainingStatistics::default(),
293            last_trained: std::time::SystemTime::now(),
294        }
295    }
296}
297
298/// Training statistics for models
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct TrainingStatistics {
301    /// Training samples used
302    pub training_samples: usize,
303    /// Training accuracy (R²)
304    pub training_accuracy: f64,
305    /// Validation accuracy
306    pub validation_accuracy: f64,
307    /// Mean absolute error
308    pub mean_absolute_error: f64,
309    /// Root mean square error
310    pub root_mean_square_error: f64,
311    /// Training time
312    pub training_time: Duration,
313}
314
315impl Default for TrainingStatistics {
316    fn default() -> Self {
317        Self {
318            training_samples: 0,
319            training_accuracy: 0.0,
320            validation_accuracy: 0.0,
321            mean_absolute_error: 0.0,
322            root_mean_square_error: 0.0,
323            training_time: Duration::from_secs(0),
324        }
325    }
326}
327
328/// Overall prediction engine statistics
329#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct PredictionStatistics {
331    /// Total predictions made
332    pub total_predictions: usize,
333    /// Successful predictions
334    pub successful_predictions: usize,
335    /// Average prediction accuracy
336    pub average_accuracy: f64,
337    /// Prediction time statistics
338    pub prediction_time_stats: PerformanceTimingStatistics,
339    /// Model update frequency
340    pub model_updates: usize,
341    /// Cache hit rate
342    pub cache_hit_rate: f64,
343}
344
345/// Timing statistics
346#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct PerformanceTimingStatistics {
348    /// Average time
349    pub average: Duration,
350    /// Minimum time
351    pub minimum: Duration,
352    /// Maximum time
353    pub maximum: Duration,
354    /// Standard deviation
355    pub std_deviation: Duration,
356}
357
358impl PerformancePredictionEngine {
359    /// Create new performance prediction engine
360    pub fn new(config: PerformancePredictionConfig) -> Result<Self> {
361        let current_hardware = Self::detect_hardware_specs()?;
362
363        Ok(Self {
364            config,
365            execution_history: VecDeque::with_capacity(10_000),
366            trained_models: HashMap::new(),
367            scirs2_backend: SciRS2Backend::new(),
368            current_hardware,
369            prediction_stats: PredictionStatistics::default(),
370            timing_accumulator: (0, 0.0, 0.0),
371        })
372    }
373
374    /// Predict execution time for a circuit on a specific backend
375    pub fn predict_execution_time<const N: usize>(
376        &mut self,
377        circuit: &Circuit<N>,
378        backend_type: BackendType,
379    ) -> Result<PredictionResult> {
380        let start_time = Instant::now();
381
382        // Analyze circuit complexity using SciRS2
383        let complexity = self.analyze_circuit_complexity(circuit)?;
384
385        // Get prediction based on strategy
386        let prediction = match self.config.prediction_strategy {
387            PredictionStrategy::StaticAnalysis => {
388                self.predict_with_static_analysis(&complexity, backend_type)?
389            }
390            PredictionStrategy::MachineLearning => {
391                self.predict_with_ml(&complexity, backend_type)?
392            }
393            PredictionStrategy::Hybrid => self.predict_with_hybrid(&complexity, backend_type)?,
394            PredictionStrategy::Ensemble => {
395                self.predict_with_ensemble(&complexity, backend_type)?
396            }
397        };
398
399        // Update statistics
400        self.prediction_stats.total_predictions += 1;
401        let prediction_time = start_time.elapsed();
402        self.update_timing_stats(prediction_time);
403
404        Ok(prediction)
405    }
406
407    /// Analyze circuit complexity using `SciRS2` tools
408    fn analyze_circuit_complexity<const N: usize>(
409        &self,
410        circuit: &Circuit<N>,
411    ) -> Result<ComplexityMetrics> {
412        let gate_count = circuit.num_gates();
413        let qubit_count = N;
414
415        // Basic complexity analysis
416        let circuit_depth = self.calculate_circuit_depth(circuit)?;
417        let two_qubit_gate_count = self.count_two_qubit_gates(circuit)?;
418        let memory_requirement = self.estimate_memory_requirement(qubit_count);
419
420        // Advanced analysis using SciRS2
421        let parallelism_factor = self.analyze_parallelism_potential(circuit)?;
422        let entanglement_complexity = self.estimate_entanglement_complexity(circuit)?;
423        let gate_type_distribution = self.analyze_gate_distribution(circuit)?;
424        let critical_path_complexity = self.analyze_critical_path(circuit)?;
425
426        // Resource estimation
427        let resource_estimation = self.estimate_resources(&ComplexityMetrics {
428            gate_count,
429            circuit_depth,
430            qubit_count,
431            two_qubit_gate_count,
432            memory_requirement,
433            parallelism_factor,
434            entanglement_complexity,
435            gate_type_distribution: gate_type_distribution.clone(),
436            critical_path_complexity,
437            resource_estimation: ResourceMetrics::default(), // Will be filled
438        })?;
439
440        Ok(ComplexityMetrics {
441            gate_count,
442            circuit_depth,
443            qubit_count,
444            two_qubit_gate_count,
445            memory_requirement,
446            parallelism_factor,
447            entanglement_complexity,
448            gate_type_distribution,
449            critical_path_complexity,
450            resource_estimation,
451        })
452    }
453
454    /// Calculate circuit depth (critical path length)
455    fn calculate_circuit_depth<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
456        // Simple depth calculation - can be enhanced with SciRS2 graph analysis
457        let mut qubit_last_gate: Vec<usize> = vec![0; N];
458        let mut max_depth = 0;
459
460        let gates = circuit.gates_as_boxes();
461        for (gate_idx, gate) in gates.iter().enumerate() {
462            let gate_qubits = self.get_gate_qubits(gate.as_ref())?;
463            let mut max_dependency = 0;
464
465            for &qubit in &gate_qubits {
466                if qubit < N {
467                    max_dependency = max_dependency.max(qubit_last_gate[qubit]);
468                }
469            }
470
471            let current_depth = max_dependency + 1;
472            max_depth = max_depth.max(current_depth);
473
474            for &qubit in &gate_qubits {
475                if qubit < N {
476                    qubit_last_gate[qubit] = current_depth;
477                }
478            }
479        }
480
481        Ok(max_depth)
482    }
483
484    /// Count two-qubit gates in circuit
485    fn count_two_qubit_gates<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
486        let mut count = 0;
487        let gates = circuit.gates_as_boxes();
488        for gate in &gates {
489            let qubits = self.get_gate_qubits(gate.as_ref())?;
490            if qubits.len() >= 2 {
491                count += 1;
492            }
493        }
494        Ok(count)
495    }
496
497    /// Get qubits affected by a gate
498    fn get_gate_qubits(&self, gate: &dyn GateOp) -> Result<Vec<usize>> {
499        // Extract qubit indices from gate operation using the GateOp trait
500        let qubits = gate.qubits();
501        Ok(qubits.iter().map(|q| q.id() as usize).collect())
502    }
503
504    /// Estimate memory requirement for simulation
505    const fn estimate_memory_requirement(&self, qubit_count: usize) -> usize {
506        // 2^N complex numbers, each 16 bytes (8 bytes real + 8 bytes imag)
507        let state_vector_size = (1usize << qubit_count) * 16;
508        // Add overhead for intermediate calculations
509        state_vector_size * 3
510    }
511
512    /// Analyze parallelism potential using `SciRS2`
513    fn analyze_parallelism_potential<const N: usize>(&self, circuit: &Circuit<N>) -> Result<f64> {
514        // Use SciRS2 parallel analysis
515        let independent_operations = self.count_independent_operations(circuit)?;
516        let total_operations = circuit.num_gates();
517
518        if total_operations == 0 {
519            return Ok(0.0);
520        }
521
522        Ok(independent_operations as f64 / total_operations as f64)
523    }
524
525    /// Count independent operations that can be parallelized
526    fn count_independent_operations<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
527        // Analyze gate dependencies for parallelization opportunities
528        // This is a simplified implementation
529        let mut independent_count = 0;
530        let mut qubit_dependencies: Vec<Option<usize>> = vec![None; N];
531
532        let gates = circuit.gates_as_boxes();
533        for (gate_idx, gate) in gates.iter().enumerate() {
534            let gate_qubits = self.get_gate_qubits(gate.as_ref())?;
535            let mut has_dependency = false;
536
537            for &qubit in &gate_qubits {
538                if qubit < N && qubit_dependencies[qubit].is_some() {
539                    has_dependency = true;
540                    break;
541                }
542            }
543
544            if !has_dependency {
545                independent_count += 1;
546            }
547
548            // Update dependencies
549            for &qubit in &gate_qubits {
550                if qubit < N {
551                    qubit_dependencies[qubit] = Some(gate_idx);
552                }
553            }
554        }
555
556        Ok(independent_count)
557    }
558
559    /// Estimate entanglement complexity
560    fn estimate_entanglement_complexity<const N: usize>(
561        &self,
562        circuit: &Circuit<N>,
563    ) -> Result<f64> {
564        // Simplified entanglement analysis
565        let two_qubit_gates = self.count_two_qubit_gates(circuit)?;
566        let total_possible_entangling = N * (N - 1) / 2; // All possible qubit pairs
567
568        if total_possible_entangling == 0 {
569            return Ok(0.0);
570        }
571
572        Ok((two_qubit_gates as f64 / total_possible_entangling as f64).min(1.0))
573    }
574
575    /// Analyze gate type distribution
576    fn analyze_gate_distribution<const N: usize>(
577        &self,
578        circuit: &Circuit<N>,
579    ) -> Result<HashMap<String, usize>> {
580        let mut distribution = HashMap::new();
581
582        let gates = circuit.gates_as_boxes();
583        for gate in &gates {
584            let gate_type = self.get_gate_type_name(gate.as_ref());
585            *distribution.entry(gate_type).or_insert(0) += 1;
586        }
587
588        Ok(distribution)
589    }
590
591    /// Get gate type name for classification
592    fn get_gate_type_name(&self, gate: &dyn GateOp) -> String {
593        // Use the gate's name from the GateOp trait
594        gate.name().to_string()
595    }
596
597    /// Analyze critical path complexity
598    fn analyze_critical_path<const N: usize>(&self, circuit: &Circuit<N>) -> Result<f64> {
599        // Analyze the complexity of the critical path
600        let depth = self.calculate_circuit_depth(circuit)?;
601        let gate_count = circuit.num_gates();
602
603        if gate_count == 0 {
604            return Ok(0.0);
605        }
606
607        // Complexity is depth relative to total gates
608        Ok(depth as f64 / gate_count as f64)
609    }
610
611    /// Estimate resource requirements
612    fn estimate_resources(&self, complexity: &ComplexityMetrics) -> Result<ResourceMetrics> {
613        // CPU time estimation based on complexity
614        let base_cpu_time = complexity.gate_count as f64 * 1e-6; // 1 microsecond per gate base
615        let depth_factor = complexity.circuit_depth as f64 * 0.1;
616        let entanglement_factor = complexity.entanglement_complexity * 2.0;
617        let cpu_time_estimate = base_cpu_time * (1.0 + depth_factor + entanglement_factor);
618
619        // Memory estimation
620        let memory_usage_estimate = complexity.memory_requirement;
621
622        // I/O estimation
623        let io_operations_estimate = complexity.gate_count * 2; // Read + write per gate
624
625        // Network bandwidth for distributed execution
626        let network_bandwidth_estimate = if complexity.qubit_count > 20 {
627            complexity.memory_requirement / 10 // 10% of memory for communication
628        } else {
629            0
630        };
631
632        // GPU memory estimation
633        let gpu_memory_estimate = complexity.memory_requirement * 2; // GPU needs more memory
634
635        // Thread requirement
636        let thread_requirement = (complexity.parallelism_factor * 16.0).ceil() as usize;
637
638        Ok(ResourceMetrics {
639            cpu_time_estimate,
640            memory_usage_estimate,
641            io_operations_estimate,
642            network_bandwidth_estimate,
643            gpu_memory_estimate,
644            thread_requirement,
645        })
646    }
647
648    /// Predict using static analysis only
649    fn predict_with_static_analysis(
650        &self,
651        complexity: &ComplexityMetrics,
652        backend_type: BackendType,
653    ) -> Result<PredictionResult> {
654        // Static analysis-based prediction
655        let base_time = complexity.resource_estimation.cpu_time_estimate;
656
657        // Backend-specific factors
658        let backend_factor = match backend_type {
659            BackendType::StateVector => 1.0,
660            BackendType::SciRS2Gpu => 0.3,   // GPU acceleration
661            BackendType::LargeScale => 0.7,  // Optimized for large circuits
662            BackendType::Distributed => 0.5, // Distributed speedup
663            BackendType::Auto => 0.8,        // Conservative estimate
664        };
665
666        let predicted_seconds = base_time * backend_factor;
667        let predicted_time = Duration::from_secs_f64(predicted_seconds);
668
669        // Static confidence based on circuit characteristics
670        let confidence = if complexity.qubit_count <= 20 {
671            0.9
672        } else {
673            0.7
674        };
675
676        // Prediction interval (±20%)
677        let lower = Duration::from_secs_f64(predicted_seconds * 0.8);
678        let upper = Duration::from_secs_f64(predicted_seconds * 1.2);
679
680        Ok(PredictionResult {
681            predicted_time,
682            confidence,
683            prediction_interval: (lower, upper),
684            model_type: ModelType::LinearRegression,
685            feature_importance: HashMap::new(),
686            metadata: PredictionMetadata {
687                prediction_time: Duration::from_millis(1),
688                samples_used: 0,
689                model_trained: false,
690                cv_score: None,
691                prediction_method: "Static Analysis".to_string(),
692            },
693        })
694    }
695
696    /// Predict using machine learning
697    fn predict_with_ml(
698        &mut self,
699        complexity: &ComplexityMetrics,
700        backend_type: BackendType,
701    ) -> Result<PredictionResult> {
702        // Check if we have enough historical data
703        if self.execution_history.len() < self.config.min_samples_for_ml {
704            return self.predict_with_static_analysis(complexity, backend_type);
705        }
706
707        // Train model if needed
708        if !self.trained_models.contains_key(&backend_type) {
709            self.train_model_for_backend(backend_type)?;
710        }
711
712        // Get trained model
713        let model = self
714            .trained_models
715            .get(&backend_type)
716            .ok_or_else(|| SimulatorError::ComputationError("Model not found".to_string()))?;
717
718        // Make prediction using trained model
719        let predicted_seconds = self.apply_model(model, complexity)?;
720        let predicted_time = Duration::from_secs_f64(predicted_seconds);
721
722        // ML confidence based on training statistics
723        let confidence = model.training_stats.validation_accuracy;
724
725        // Prediction interval based on model error
726        let error_margin = model.training_stats.mean_absolute_error;
727        let lower = Duration::from_secs_f64((predicted_seconds - error_margin).max(0.0));
728        let upper = Duration::from_secs_f64(predicted_seconds + error_margin);
729
730        Ok(PredictionResult {
731            predicted_time,
732            confidence,
733            prediction_interval: (lower, upper),
734            model_type: model.model_type,
735            feature_importance: model.feature_weights.clone(),
736            metadata: PredictionMetadata {
737                prediction_time: Duration::from_millis(5),
738                samples_used: model.training_stats.training_samples,
739                model_trained: true,
740                cv_score: Some(model.training_stats.validation_accuracy),
741                prediction_method: "Machine Learning".to_string(),
742            },
743        })
744    }
745
746    /// Predict using hybrid approach (static + ML)
747    fn predict_with_hybrid(
748        &mut self,
749        complexity: &ComplexityMetrics,
750        backend_type: BackendType,
751    ) -> Result<PredictionResult> {
752        // Get static prediction
753        let static_pred = self.predict_with_static_analysis(complexity, backend_type)?;
754
755        // Try ML prediction if enough data
756        if self.execution_history.len() >= self.config.min_samples_for_ml {
757            let ml_pred = self.predict_with_ml(complexity, backend_type)?;
758
759            // Weighted combination
760            let static_weight = 0.3;
761            let ml_weight = 0.7;
762
763            let combined_seconds = static_pred.predicted_time.as_secs_f64().mul_add(
764                static_weight,
765                ml_pred.predicted_time.as_secs_f64() * ml_weight,
766            );
767
768            let predicted_time = Duration::from_secs_f64(combined_seconds);
769            let confidence = static_pred
770                .confidence
771                .mul_add(static_weight, ml_pred.confidence * ml_weight);
772
773            // Combined prediction interval
774            let lower_combined =
775                Duration::from_secs_f64(static_pred.prediction_interval.0.as_secs_f64().mul_add(
776                    static_weight,
777                    ml_pred.prediction_interval.0.as_secs_f64() * ml_weight,
778                ));
779            let upper_combined =
780                Duration::from_secs_f64(static_pred.prediction_interval.1.as_secs_f64().mul_add(
781                    static_weight,
782                    ml_pred.prediction_interval.1.as_secs_f64() * ml_weight,
783                ));
784
785            Ok(PredictionResult {
786                predicted_time,
787                confidence,
788                prediction_interval: (lower_combined, upper_combined),
789                model_type: ModelType::LinearRegression, // Hybrid
790                feature_importance: ml_pred.feature_importance,
791                metadata: PredictionMetadata {
792                    prediction_time: Duration::from_millis(6),
793                    samples_used: ml_pred.metadata.samples_used,
794                    model_trained: ml_pred.metadata.model_trained,
795                    cv_score: ml_pred.metadata.cv_score,
796                    prediction_method: "Hybrid (Static + ML)".to_string(),
797                },
798            })
799        } else {
800            // Fall back to static analysis
801            Ok(static_pred)
802        }
803    }
804
805    /// Predict using an ensemble of independent base predictors.
806    ///
807    /// Runs the static-analysis predictor and, when enough history exists, the ML
808    /// regressor as two independent base learners, then combines them with
809    /// confidence-weighted averaging (each base prediction weighted by its own
810    /// reported confidence). The ensemble confidence is the weighted mean of the
811    /// base confidences and the prediction interval is the union (min lower /
812    /// max upper) of the base intervals, which is a real reflection of combined
813    /// uncertainty rather than a copy of a single model.
814    fn predict_with_ensemble(
815        &mut self,
816        complexity: &ComplexityMetrics,
817        backend_type: BackendType,
818    ) -> Result<PredictionResult> {
819        let mut members: Vec<PredictionResult> = Vec::new();
820
821        // Base learner 1: static analysis (always available).
822        members.push(self.predict_with_static_analysis(complexity, backend_type)?);
823
824        // Base learner 2: trained ML model, only when enough history exists.
825        if self.execution_history.len() >= self.config.min_samples_for_ml {
826            if let Ok(ml_pred) = self.predict_with_ml(complexity, backend_type) {
827                members.push(ml_pred);
828            }
829        }
830
831        // Confidence-weighted combination (fall back to equal weights if all
832        // confidences are zero).
833        let total_confidence: f64 = members.iter().map(|m| m.confidence).sum();
834        let use_equal = total_confidence <= f64::EPSILON;
835        let member_count = members.len() as f64;
836
837        let mut predicted_seconds = 0.0;
838        let mut confidence = 0.0;
839        let mut lower = f64::MAX;
840        let mut upper: f64 = 0.0;
841        let mut samples_used = 0usize;
842        let mut model_trained = false;
843        let mut feature_importance = HashMap::new();
844
845        for member in &members {
846            let weight = if use_equal {
847                1.0 / member_count
848            } else {
849                member.confidence / total_confidence
850            };
851            predicted_seconds += member.predicted_time.as_secs_f64() * weight;
852            confidence += member.confidence * weight;
853            lower = lower.min(member.prediction_interval.0.as_secs_f64());
854            upper = upper.max(member.prediction_interval.1.as_secs_f64());
855            samples_used = samples_used.max(member.metadata.samples_used);
856            model_trained |= member.metadata.model_trained;
857            if !member.feature_importance.is_empty() {
858                feature_importance = member.feature_importance.clone();
859            }
860        }
861
862        if lower == f64::MAX {
863            lower = predicted_seconds;
864        }
865
866        Ok(PredictionResult {
867            predicted_time: Duration::from_secs_f64(predicted_seconds.max(0.0)),
868            confidence: confidence.clamp(0.0, 1.0),
869            prediction_interval: (
870                Duration::from_secs_f64(lower.max(0.0)),
871                Duration::from_secs_f64(upper.max(0.0)),
872            ),
873            model_type: ModelType::RandomForest,
874            feature_importance,
875            metadata: PredictionMetadata {
876                prediction_time: Duration::from_millis(8),
877                samples_used,
878                model_trained,
879                cv_score: None,
880                prediction_method: format!("Ensemble ({} base models)", members.len()),
881            },
882        })
883    }
884
885    /// Feature names used by the linear model, in column order. The first column
886    /// of the design matrix is an implicit intercept (handled separately).
887    const FEATURE_NAMES: [&'static str; 5] = [
888        "gate_count",
889        "circuit_depth",
890        "qubit_count",
891        "entanglement_complexity",
892        "parallelism_factor",
893    ];
894
895    /// Extract the model feature row for a set of complexity metrics.
896    ///
897    /// `gate_count`, `circuit_depth` and `qubit_count` are log1p-transformed so a
898    /// linear model captures their multiplicative effect on runtime; the two
899    /// ratio features are used directly.
900    fn feature_row(complexity: &ComplexityMetrics) -> [f64; 5] {
901        [
902            (complexity.gate_count as f64).ln_1p(),
903            (complexity.circuit_depth as f64).ln_1p(),
904            (complexity.qubit_count as f64).ln_1p(),
905            complexity.entanglement_complexity,
906            complexity.parallelism_factor,
907        ]
908    }
909
910    /// Train a real ordinary-least-squares linear regression for one backend.
911    ///
912    /// The model fits `ln(1 + execution_seconds)` against the feature row (plus an
913    /// intercept) over all successful historical runs for the backend, solving the
914    /// normal equations `(XᵀX) β = Xᵀy` with Gaussian elimination. Training and
915    /// validation accuracy are the real coefficients of determination (R²) on a
916    /// time-ordered train/validation split, and MAE/RMSE are measured on the
917    /// validation fold (or the training fold when there is too little data to
918    /// split). No statistics are fabricated.
919    fn train_model_for_backend(&mut self, backend_type: BackendType) -> Result<()> {
920        let train_start = Instant::now();
921
922        let training_data: Vec<&ExecutionDataPoint> = self
923            .execution_history
924            .iter()
925            .filter(|data| data.backend_type == backend_type && data.success)
926            .collect();
927
928        if training_data.is_empty() {
929            return Err(SimulatorError::ComputationError(
930                "No training data available".to_string(),
931            ));
932        }
933
934        // Build design rows (features + target). Target is ln(1 + seconds).
935        let samples: Vec<([f64; 5], f64)> = training_data
936            .iter()
937            .map(|dp| {
938                (
939                    Self::feature_row(&dp.complexity),
940                    dp.execution_time.as_secs_f64().ln_1p(),
941                )
942            })
943            .collect();
944
945        // Time-ordered split: last 20% (at least 1) used for validation when we
946        // have enough samples; otherwise validate on the training data itself.
947        let total = samples.len();
948        let split = if total >= 5 {
949            total - (total / 5).max(1)
950        } else {
951            total
952        };
953        let (train_slice, valid_slice) = samples.split_at(split);
954        let train_slice = if train_slice.is_empty() {
955            &samples[..]
956        } else {
957            train_slice
958        };
959        let valid_slice = if valid_slice.is_empty() {
960            train_slice
961        } else {
962            valid_slice
963        };
964
965        // Fit coefficients [intercept, w0..w4] via the normal equations.
966        let coefficients = Self::fit_least_squares(train_slice)?;
967
968        let training_accuracy = Self::r_squared(train_slice, &coefficients);
969        let validation_accuracy = Self::r_squared(valid_slice, &coefficients);
970        let (mean_absolute_error, root_mean_square_error) =
971            Self::error_metrics(valid_slice, &coefficients);
972
973        // Feature importance: |standardized coefficient| normalized to sum to 1.
974        let feature_weights = Self::feature_importance(train_slice, &coefficients);
975
976        let model = TrainedModel {
977            model_type: ModelType::LinearRegression,
978            parameters: coefficients,
979            feature_weights,
980            training_stats: TrainingStatistics {
981                training_samples: train_slice.len(),
982                training_accuracy,
983                validation_accuracy,
984                mean_absolute_error,
985                root_mean_square_error,
986                training_time: train_start.elapsed(),
987            },
988            last_trained: std::time::SystemTime::now(),
989        };
990
991        self.trained_models.insert(backend_type, model);
992        self.prediction_stats.model_updates += 1;
993
994        Ok(())
995    }
996
997    /// Solve ordinary least squares for `[intercept, w0..w4]` over the samples.
998    ///
999    /// Forms the 6x6 normal-equation system `(XᵀX) β = Xᵀy` (the leading column of
1000    /// `X` is all ones for the intercept) and solves it with partial-pivoted
1001    /// Gaussian elimination. A tiny ridge term is added to the diagonal to keep
1002    /// the system solvable when features are collinear or samples are scarce.
1003    fn fit_least_squares(samples: &[([f64; 5], f64)]) -> Result<Vec<f64>> {
1004        const DIM: usize = 6; // intercept + 5 features
1005        if samples.is_empty() {
1006            return Err(SimulatorError::ComputationError(
1007                "cannot fit model with no samples".to_string(),
1008            ));
1009        }
1010
1011        let mut ata = [[0.0f64; DIM]; DIM];
1012        let mut aty = [0.0f64; DIM];
1013
1014        for (features, target) in samples {
1015            let mut row = [0.0f64; DIM];
1016            row[0] = 1.0;
1017            row[1..DIM].copy_from_slice(features);
1018
1019            for i in 0..DIM {
1020                aty[i] += row[i] * target;
1021                for j in 0..DIM {
1022                    ata[i][j] += row[i] * row[j];
1023                }
1024            }
1025        }
1026
1027        // Ridge regularization for numerical stability (does not bias a
1028        // well-conditioned fit meaningfully).
1029        let ridge = 1e-6;
1030        for i in 0..DIM {
1031            ata[i][i] += ridge;
1032        }
1033
1034        Self::solve_linear_system(ata, aty)
1035    }
1036
1037    /// Solve a fixed 6x6 linear system with partial-pivoted Gaussian elimination.
1038    fn solve_linear_system(mut a: [[f64; 6]; 6], mut b: [f64; 6]) -> Result<Vec<f64>> {
1039        const DIM: usize = 6;
1040        for col in 0..DIM {
1041            // Partial pivot: find the largest magnitude entry in this column.
1042            let mut pivot = col;
1043            let mut best = a[col][col].abs();
1044            for row in (col + 1)..DIM {
1045                let candidate = a[row][col].abs();
1046                if candidate > best {
1047                    best = candidate;
1048                    pivot = row;
1049                }
1050            }
1051            if best < 1e-12 {
1052                return Err(SimulatorError::ComputationError(
1053                    "singular system while fitting performance model".to_string(),
1054                ));
1055            }
1056            if pivot != col {
1057                a.swap(col, pivot);
1058                b.swap(col, pivot);
1059            }
1060            // Eliminate below the pivot.
1061            for row in (col + 1)..DIM {
1062                let factor = a[row][col] / a[col][col];
1063                for k in col..DIM {
1064                    a[row][k] -= factor * a[col][k];
1065                }
1066                b[row] -= factor * b[col];
1067            }
1068        }
1069
1070        // Back-substitution.
1071        let mut x = vec![0.0f64; DIM];
1072        for row in (0..DIM).rev() {
1073            let mut sum = b[row];
1074            for k in (row + 1)..DIM {
1075                sum -= a[row][k] * x[k];
1076            }
1077            x[row] = sum / a[row][row];
1078        }
1079        Ok(x)
1080    }
1081
1082    /// Predict `ln(1 + seconds)` from a feature row and fitted coefficients.
1083    fn predict_log_time(features: &[f64; 5], coefficients: &[f64]) -> f64 {
1084        let intercept = coefficients.first().copied().unwrap_or(0.0);
1085        let mut acc = intercept;
1086        for (idx, value) in features.iter().enumerate() {
1087            acc += coefficients.get(idx + 1).copied().unwrap_or(0.0) * value;
1088        }
1089        acc
1090    }
1091
1092    /// Coefficient of determination (R²) of the fit over the given samples.
1093    fn r_squared(samples: &[([f64; 5], f64)], coefficients: &[f64]) -> f64 {
1094        if samples.is_empty() {
1095            return 0.0;
1096        }
1097        let mean = samples.iter().map(|(_, y)| *y).sum::<f64>() / samples.len() as f64;
1098        let mut ss_res = 0.0;
1099        let mut ss_tot = 0.0;
1100        for (features, target) in samples {
1101            let predicted = Self::predict_log_time(features, coefficients);
1102            ss_res += (target - predicted).powi(2);
1103            ss_tot += (target - mean).powi(2);
1104        }
1105        if ss_tot <= f64::EPSILON {
1106            // All targets identical: a perfect fit reproduces them, otherwise 0.
1107            return if ss_res <= f64::EPSILON { 1.0 } else { 0.0 };
1108        }
1109        (1.0 - ss_res / ss_tot).clamp(0.0, 1.0)
1110    }
1111
1112    /// Mean absolute error and root-mean-square error in the original time domain
1113    /// (seconds), measured over the given samples.
1114    fn error_metrics(samples: &[([f64; 5], f64)], coefficients: &[f64]) -> (f64, f64) {
1115        if samples.is_empty() {
1116            return (0.0, 0.0);
1117        }
1118        let mut abs_sum = 0.0;
1119        let mut sq_sum = 0.0;
1120        for (features, target) in samples {
1121            let predicted_log = Self::predict_log_time(features, coefficients);
1122            // Convert both back from ln(1 + seconds) to seconds.
1123            let predicted = predicted_log.exp_m1().max(0.0);
1124            let actual = target.exp_m1().max(0.0);
1125            let diff = predicted - actual;
1126            abs_sum += diff.abs();
1127            sq_sum += diff * diff;
1128        }
1129        let n = samples.len() as f64;
1130        (abs_sum / n, (sq_sum / n).sqrt())
1131    }
1132
1133    /// Normalized feature importance from standardized coefficients.
1134    ///
1135    /// Each coefficient is scaled by the standard deviation of its feature so the
1136    /// magnitudes are comparable, then the absolute values are normalized to sum
1137    /// to 1. Returns an empty map only when no samples are available.
1138    fn feature_importance(
1139        samples: &[([f64; 5], f64)],
1140        coefficients: &[f64],
1141    ) -> HashMap<String, f64> {
1142        let mut weights = HashMap::new();
1143        if samples.is_empty() {
1144            return weights;
1145        }
1146        let n = samples.len() as f64;
1147        let mut scaled = [0.0f64; 5];
1148        for col in 0..5 {
1149            let mean = samples.iter().map(|(f, _)| f[col]).sum::<f64>() / n;
1150            let variance = samples
1151                .iter()
1152                .map(|(f, _)| (f[col] - mean).powi(2))
1153                .sum::<f64>()
1154                / n;
1155            let std_dev = variance.sqrt();
1156            let coeff = coefficients.get(col + 1).copied().unwrap_or(0.0);
1157            scaled[col] = (coeff * std_dev).abs();
1158        }
1159        let total: f64 = scaled.iter().sum();
1160        for (col, name) in Self::FEATURE_NAMES.iter().enumerate() {
1161            let importance = if total > f64::EPSILON {
1162                scaled[col] / total
1163            } else {
1164                0.0
1165            };
1166            weights.insert((*name).to_string(), importance);
1167        }
1168        weights
1169    }
1170
1171    /// Apply a trained linear model to predict execution time (seconds).
1172    ///
1173    /// Evaluates the fitted regression on the feature row and converts the
1174    /// predicted `ln(1 + seconds)` back to seconds, clamped to be non-negative.
1175    fn apply_model(&self, model: &TrainedModel, complexity: &ComplexityMetrics) -> Result<f64> {
1176        let features = Self::feature_row(complexity);
1177        let predicted_log = Self::predict_log_time(&features, &model.parameters);
1178        Ok(predicted_log.exp_m1().max(0.0))
1179    }
1180
1181    /// Record actual execution time for model improvement
1182    pub fn record_execution(&mut self, data_point: ExecutionDataPoint) -> Result<()> {
1183        // Add to history
1184        self.execution_history.push_back(data_point.clone());
1185
1186        // Maintain size limit
1187        if self.execution_history.len() > self.config.max_history_size {
1188            self.execution_history.pop_front();
1189        }
1190
1191        // Update prediction accuracy if we have a prediction for this data
1192        self.update_prediction_accuracy(&data_point);
1193
1194        // Retrain models periodically
1195        if self.execution_history.len() % 100 == 0 {
1196            self.retrain_models()?;
1197        }
1198
1199        Ok(())
1200    }
1201
1202    /// Update prediction-accuracy statistics from a newly observed execution.
1203    ///
1204    /// When a trained model exists for the data point's backend, the model is
1205    /// re-evaluated on the point's complexity and the prediction is compared to
1206    /// the actual measured time. The per-sample accuracy is
1207    /// `1 - |predicted - actual| / max(actual, eps)` (clamped to `[0, 1]`), and
1208    /// the engine's `average_accuracy` is updated as a running mean over all such
1209    /// comparisons. This is a measured back-test, not a placeholder.
1210    fn update_prediction_accuracy(&mut self, data_point: &ExecutionDataPoint) {
1211        if !data_point.success {
1212            return;
1213        }
1214
1215        self.prediction_stats.successful_predictions += 1;
1216
1217        let Some(model) = self.trained_models.get(&data_point.backend_type) else {
1218            return;
1219        };
1220
1221        let Ok(predicted_seconds) = self.apply_model(model, &data_point.complexity) else {
1222            return;
1223        };
1224
1225        let actual_seconds = data_point.execution_time.as_secs_f64();
1226        let denom = actual_seconds.max(1e-9);
1227        let sample_accuracy =
1228            (1.0 - (predicted_seconds - actual_seconds).abs() / denom).clamp(0.0, 1.0);
1229
1230        // Running mean of accuracy over successful, model-backed predictions.
1231        let n = self.prediction_stats.successful_predictions as f64;
1232        let prev = self.prediction_stats.average_accuracy;
1233        self.prediction_stats.average_accuracy = prev + (sample_accuracy - prev) / n;
1234    }
1235
1236    /// Retrain all models with latest data
1237    fn retrain_models(&mut self) -> Result<()> {
1238        let backends = vec![
1239            BackendType::StateVector,
1240            BackendType::SciRS2Gpu,
1241            BackendType::LargeScale,
1242            BackendType::Distributed,
1243        ];
1244
1245        for backend in backends {
1246            if self
1247                .execution_history
1248                .iter()
1249                .any(|d| d.backend_type == backend)
1250            {
1251                self.train_model_for_backend(backend)?;
1252            }
1253        }
1254
1255        Ok(())
1256    }
1257
1258    /// Detect current hardware specifications from real system sources.
1259    ///
1260    /// CPU core count comes from `num_cpus`; total/available memory are read from
1261    /// the OS via `scirs2_core::resource` (which parses `/proc/meminfo` on Linux,
1262    /// with a documented fallback). The system load average is read from
1263    /// `/proc/loadavg` when present.
1264    ///
1265    /// Values that cannot be probed in-process without extra dependencies or a
1266    /// running GPU context are reported honestly rather than fabricated:
1267    /// `gpu_memory` is `None` (no GPU memory is queried here), `cpu_frequency` is
1268    /// `0.0` ("unknown"), and `network_bandwidth` is `None`.
1269    fn detect_hardware_specs() -> Result<PerformanceHardwareSpecs> {
1270        let cpu_cores = num_cpus::get();
1271
1272        let total_memory = scirs2_core::resource::get_total_memory()
1273            .map_err(|e| SimulatorError::ComputationError(format!("memory probe failed: {e}")))?;
1274        let available_memory = scirs2_core::resource::get_available_memory()
1275            .map_err(|e| SimulatorError::ComputationError(format!("memory probe failed: {e}")))?;
1276
1277        let load_average = Self::read_load_average();
1278
1279        Ok(PerformanceHardwareSpecs {
1280            cpu_cores,
1281            total_memory,
1282            available_memory,
1283            // No GPU memory is queried on this path; do not fabricate a size.
1284            gpu_memory: None,
1285            // Per-core frequency is not portably probeable in-process; "unknown".
1286            cpu_frequency: 0.0,
1287            // Network bandwidth is not measured here.
1288            network_bandwidth: None,
1289            load_average,
1290        })
1291    }
1292
1293    /// Read the 1-minute load average from `/proc/loadavg` (Linux). Returns 0.0
1294    /// when unavailable, signalling "unknown" rather than fabricating a value.
1295    fn read_load_average() -> f64 {
1296        std::fs::read_to_string("/proc/loadavg")
1297            .ok()
1298            .and_then(|content| {
1299                content
1300                    .split_whitespace()
1301                    .next()
1302                    .and_then(|first| first.parse::<f64>().ok())
1303            })
1304            .unwrap_or(0.0)
1305    }
1306
1307    /// Update prediction-latency statistics with a new measured `elapsed` time.
1308    ///
1309    /// Maintains exact running count/sum/sum-of-squares (in nanoseconds) and
1310    /// recomputes the reported average, minimum, maximum and population standard
1311    /// deviation. These are real measurements of how long predictions take, not
1312    /// placeholders.
1313    fn update_timing_stats(&mut self, elapsed: Duration) {
1314        let nanos = elapsed.as_nanos() as f64;
1315        let (count, sum, sum_sq) = self.timing_accumulator;
1316        let new_count = count + 1;
1317        let new_sum = sum + nanos;
1318        let new_sum_sq = sum_sq + nanos * nanos;
1319        self.timing_accumulator = (new_count, new_sum, new_sum_sq);
1320
1321        let mean = new_sum / new_count as f64;
1322        let variance = (new_sum_sq / new_count as f64) - mean * mean;
1323        let std_dev = variance.max(0.0).sqrt();
1324
1325        let stats = &mut self.prediction_stats.prediction_time_stats;
1326        stats.average = Duration::from_nanos(mean as u64);
1327        stats.std_deviation = Duration::from_nanos(std_dev as u64);
1328        if new_count == 1 {
1329            stats.minimum = elapsed;
1330            stats.maximum = elapsed;
1331        } else {
1332            stats.minimum = stats.minimum.min(elapsed);
1333            stats.maximum = stats.maximum.max(elapsed);
1334        }
1335    }
1336
1337    /// Get prediction engine statistics
1338    #[must_use]
1339    pub const fn get_statistics(&self) -> &PredictionStatistics {
1340        &self.prediction_stats
1341    }
1342
1343    /// Export prediction models for persistence
1344    pub fn export_models(&self) -> Result<Vec<u8>> {
1345        // Serialize models for storage
1346        let serialized = serde_json::to_vec(&self.trained_models)
1347            .map_err(|e| SimulatorError::ComputationError(format!("Serialization error: {e}")))?;
1348        Ok(serialized)
1349    }
1350
1351    /// Import prediction models from storage
1352    pub fn import_models(&mut self, _data: &[u8]) -> Result<()> {
1353        // Note: Import functionality disabled due to SystemTime serialization limitations
1354        // In a full implementation, would use a custom serialization format or different time representation
1355        Err(SimulatorError::ComputationError(
1356            "Import not supported in current implementation".to_string(),
1357        ))
1358    }
1359}
1360
1361impl Default for ResourceMetrics {
1362    fn default() -> Self {
1363        Self {
1364            cpu_time_estimate: 0.0,
1365            memory_usage_estimate: 0,
1366            io_operations_estimate: 0,
1367            network_bandwidth_estimate: 0,
1368            gpu_memory_estimate: 0,
1369            thread_requirement: 1,
1370        }
1371    }
1372}
1373
1374impl Default for PredictionStatistics {
1375    fn default() -> Self {
1376        Self {
1377            total_predictions: 0,
1378            successful_predictions: 0,
1379            average_accuracy: 0.0,
1380            prediction_time_stats: PerformanceTimingStatistics {
1381                average: Duration::from_millis(0),
1382                minimum: Duration::from_millis(0),
1383                maximum: Duration::from_millis(0),
1384                std_deviation: Duration::from_millis(0),
1385            },
1386            model_updates: 0,
1387            cache_hit_rate: 0.0,
1388        }
1389    }
1390}
1391
1392/// Convenience function to create a performance prediction engine with default config
1393pub fn create_performance_predictor() -> Result<PerformancePredictionEngine> {
1394    PerformancePredictionEngine::new(PerformancePredictionConfig::default())
1395}
1396
1397/// Convenience function to predict execution time for a circuit
1398pub fn predict_circuit_execution_time<const N: usize>(
1399    predictor: &mut PerformancePredictionEngine,
1400    circuit: &Circuit<N>,
1401    backend_type: BackendType,
1402) -> Result<PredictionResult> {
1403    predictor.predict_execution_time(circuit, backend_type)
1404}