1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct PerformancePredictionConfig {
26 pub enable_ml_prediction: bool,
28 pub max_history_size: usize,
30 pub confidence_threshold: f64,
32 pub enable_hardware_profiling: bool,
34 pub analysis_depth: AnalysisDepth,
36 pub prediction_strategy: PredictionStrategy,
38 pub learning_rate: f64,
40 pub enable_transfer_learning: bool,
42 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#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
64pub enum PredictionStrategy {
65 StaticAnalysis,
67 MachineLearning,
69 Hybrid,
71 Ensemble,
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77pub enum ModelType {
78 LinearRegression,
80 PolynomialRegression,
82 NeuralNetwork,
84 SupportVectorRegression,
86 RandomForest,
88 GradientBoosting,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct ComplexityMetrics {
95 pub gate_count: usize,
97 pub circuit_depth: usize,
99 pub qubit_count: usize,
101 pub two_qubit_gate_count: usize,
103 pub memory_requirement: usize,
105 pub parallelism_factor: f64,
107 pub entanglement_complexity: f64,
109 pub gate_type_distribution: HashMap<String, usize>,
111 pub critical_path_complexity: f64,
113 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#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct ResourceMetrics {
137 pub cpu_time_estimate: f64,
139 pub memory_usage_estimate: usize,
141 pub io_operations_estimate: usize,
143 pub network_bandwidth_estimate: usize,
145 pub gpu_memory_estimate: usize,
147 pub thread_requirement: usize,
149}
150
151#[derive(Debug, Clone, Serialize)]
153pub struct ExecutionDataPoint {
154 pub complexity: ComplexityMetrics,
156 pub backend_type: BackendType,
158 pub execution_time: Duration,
160 pub hardware_specs: PerformanceHardwareSpecs,
162 #[serde(skip_serializing, skip_deserializing)]
164 pub timestamp: std::time::SystemTime,
165 pub success: bool,
167 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#[derive(Debug, Clone, Serialize, Deserialize)]
187pub struct PerformanceHardwareSpecs {
188 pub cpu_cores: usize,
190 pub total_memory: usize,
192 pub available_memory: usize,
194 pub gpu_memory: Option<usize>,
196 pub cpu_frequency: f64,
198 pub network_bandwidth: Option<f64>,
200 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, available_memory: 512 * 1024 * 1024, gpu_memory: None,
211 cpu_frequency: 2000.0, network_bandwidth: None,
213 load_average: 0.0,
214 }
215 }
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct PredictionResult {
221 pub predicted_time: Duration,
223 pub confidence: f64,
225 pub prediction_interval: (Duration, Duration),
227 pub model_type: ModelType,
229 pub feature_importance: HashMap<String, f64>,
231 pub metadata: PredictionMetadata,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
237pub struct PredictionMetadata {
238 pub prediction_time: Duration,
240 pub samples_used: usize,
242 pub model_trained: bool,
244 pub cv_score: Option<f64>,
246 pub prediction_method: String,
248}
249
250pub struct PerformancePredictionEngine {
252 config: PerformancePredictionConfig,
254 execution_history: VecDeque<ExecutionDataPoint>,
256 trained_models: HashMap<BackendType, TrainedModel>,
258 scirs2_backend: SciRS2Backend,
260 current_hardware: PerformanceHardwareSpecs,
262 prediction_stats: PredictionStatistics,
264 timing_accumulator: (u64, f64, f64),
268}
269
270#[derive(Debug, Clone, Serialize)]
272pub struct TrainedModel {
273 pub model_type: ModelType,
275 pub parameters: Vec<f64>,
277 pub feature_weights: HashMap<String, f64>,
279 pub training_stats: TrainingStatistics,
281 #[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#[derive(Debug, Clone, Serialize, Deserialize)]
300pub struct TrainingStatistics {
301 pub training_samples: usize,
303 pub training_accuracy: f64,
305 pub validation_accuracy: f64,
307 pub mean_absolute_error: f64,
309 pub root_mean_square_error: f64,
311 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#[derive(Debug, Clone, Serialize, Deserialize)]
330pub struct PredictionStatistics {
331 pub total_predictions: usize,
333 pub successful_predictions: usize,
335 pub average_accuracy: f64,
337 pub prediction_time_stats: PerformanceTimingStatistics,
339 pub model_updates: usize,
341 pub cache_hit_rate: f64,
343}
344
345#[derive(Debug, Clone, Serialize, Deserialize)]
347pub struct PerformanceTimingStatistics {
348 pub average: Duration,
350 pub minimum: Duration,
352 pub maximum: Duration,
354 pub std_deviation: Duration,
356}
357
358impl PerformancePredictionEngine {
359 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 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 let complexity = self.analyze_circuit_complexity(circuit)?;
384
385 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 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 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 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 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 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(), })?;
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 fn calculate_circuit_depth<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
456 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 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 fn get_gate_qubits(&self, gate: &dyn GateOp) -> Result<Vec<usize>> {
499 let qubits = gate.qubits();
501 Ok(qubits.iter().map(|q| q.id() as usize).collect())
502 }
503
504 const fn estimate_memory_requirement(&self, qubit_count: usize) -> usize {
506 let state_vector_size = (1usize << qubit_count) * 16;
508 state_vector_size * 3
510 }
511
512 fn analyze_parallelism_potential<const N: usize>(&self, circuit: &Circuit<N>) -> Result<f64> {
514 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 fn count_independent_operations<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
527 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 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 fn estimate_entanglement_complexity<const N: usize>(
561 &self,
562 circuit: &Circuit<N>,
563 ) -> Result<f64> {
564 let two_qubit_gates = self.count_two_qubit_gates(circuit)?;
566 let total_possible_entangling = N * (N - 1) / 2; 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 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 fn get_gate_type_name(&self, gate: &dyn GateOp) -> String {
593 gate.name().to_string()
595 }
596
597 fn analyze_critical_path<const N: usize>(&self, circuit: &Circuit<N>) -> Result<f64> {
599 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 Ok(depth as f64 / gate_count as f64)
609 }
610
611 fn estimate_resources(&self, complexity: &ComplexityMetrics) -> Result<ResourceMetrics> {
613 let base_cpu_time = complexity.gate_count as f64 * 1e-6; 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 let memory_usage_estimate = complexity.memory_requirement;
621
622 let io_operations_estimate = complexity.gate_count * 2; let network_bandwidth_estimate = if complexity.qubit_count > 20 {
627 complexity.memory_requirement / 10 } else {
629 0
630 };
631
632 let gpu_memory_estimate = complexity.memory_requirement * 2; 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 fn predict_with_static_analysis(
650 &self,
651 complexity: &ComplexityMetrics,
652 backend_type: BackendType,
653 ) -> Result<PredictionResult> {
654 let base_time = complexity.resource_estimation.cpu_time_estimate;
656
657 let backend_factor = match backend_type {
659 BackendType::StateVector => 1.0,
660 BackendType::SciRS2Gpu => 0.3, BackendType::LargeScale => 0.7, BackendType::Distributed => 0.5, BackendType::Auto => 0.8, };
665
666 let predicted_seconds = base_time * backend_factor;
667 let predicted_time = Duration::from_secs_f64(predicted_seconds);
668
669 let confidence = if complexity.qubit_count <= 20 {
671 0.9
672 } else {
673 0.7
674 };
675
676 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 fn predict_with_ml(
698 &mut self,
699 complexity: &ComplexityMetrics,
700 backend_type: BackendType,
701 ) -> Result<PredictionResult> {
702 if self.execution_history.len() < self.config.min_samples_for_ml {
704 return self.predict_with_static_analysis(complexity, backend_type);
705 }
706
707 if !self.trained_models.contains_key(&backend_type) {
709 self.train_model_for_backend(backend_type)?;
710 }
711
712 let model = self
714 .trained_models
715 .get(&backend_type)
716 .ok_or_else(|| SimulatorError::ComputationError("Model not found".to_string()))?;
717
718 let predicted_seconds = self.apply_model(model, complexity)?;
720 let predicted_time = Duration::from_secs_f64(predicted_seconds);
721
722 let confidence = model.training_stats.validation_accuracy;
724
725 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 fn predict_with_hybrid(
748 &mut self,
749 complexity: &ComplexityMetrics,
750 backend_type: BackendType,
751 ) -> Result<PredictionResult> {
752 let static_pred = self.predict_with_static_analysis(complexity, backend_type)?;
754
755 if self.execution_history.len() >= self.config.min_samples_for_ml {
757 let ml_pred = self.predict_with_ml(complexity, backend_type)?;
758
759 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 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, 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 Ok(static_pred)
802 }
803 }
804
805 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 members.push(self.predict_with_static_analysis(complexity, backend_type)?);
823
824 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 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 const FEATURE_NAMES: [&'static str; 5] = [
888 "gate_count",
889 "circuit_depth",
890 "qubit_count",
891 "entanglement_complexity",
892 "parallelism_factor",
893 ];
894
895 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 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 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 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 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 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 fn fit_least_squares(samples: &[([f64; 5], f64)]) -> Result<Vec<f64>> {
1004 const DIM: usize = 6; 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn record_execution(&mut self, data_point: ExecutionDataPoint) -> Result<()> {
1183 self.execution_history.push_back(data_point.clone());
1185
1186 if self.execution_history.len() > self.config.max_history_size {
1188 self.execution_history.pop_front();
1189 }
1190
1191 self.update_prediction_accuracy(&data_point);
1193
1194 if self.execution_history.len() % 100 == 0 {
1196 self.retrain_models()?;
1197 }
1198
1199 Ok(())
1200 }
1201
1202 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 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 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 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 gpu_memory: None,
1285 cpu_frequency: 0.0,
1287 network_bandwidth: None,
1289 load_average,
1290 })
1291 }
1292
1293 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 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 #[must_use]
1339 pub const fn get_statistics(&self) -> &PredictionStatistics {
1340 &self.prediction_stats
1341 }
1342
1343 pub fn export_models(&self) -> Result<Vec<u8>> {
1345 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 pub fn import_models(&mut self, _data: &[u8]) -> Result<()> {
1353 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
1392pub fn create_performance_predictor() -> Result<PerformancePredictionEngine> {
1394 PerformancePredictionEngine::new(PerformancePredictionConfig::default())
1395}
1396
1397pub 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}