use crate::{
auto_optimizer::{AnalysisDepth, BackendType, CircuitCharacteristics},
error::{Result, SimulatorError},
scirs2_integration::{Matrix, SciRS2Backend, Vector},
};
use quantrs2_circuit::builder::Circuit;
use quantrs2_core::{
error::{QuantRS2Error, QuantRS2Result},
gate::GateOp,
qubit::QubitId,
};
use scirs2_core::Complex64;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformancePredictionConfig {
pub enable_ml_prediction: bool,
pub max_history_size: usize,
pub confidence_threshold: f64,
pub enable_hardware_profiling: bool,
pub analysis_depth: AnalysisDepth,
pub prediction_strategy: PredictionStrategy,
pub learning_rate: f64,
pub enable_transfer_learning: bool,
pub min_samples_for_ml: usize,
}
impl Default for PerformancePredictionConfig {
fn default() -> Self {
Self {
enable_ml_prediction: true,
max_history_size: 10_000,
confidence_threshold: 0.8,
enable_hardware_profiling: true,
analysis_depth: AnalysisDepth::Deep,
prediction_strategy: PredictionStrategy::Hybrid,
learning_rate: 0.01,
enable_transfer_learning: true,
min_samples_for_ml: 100,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PredictionStrategy {
StaticAnalysis,
MachineLearning,
Hybrid,
Ensemble,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum ModelType {
LinearRegression,
PolynomialRegression,
NeuralNetwork,
SupportVectorRegression,
RandomForest,
GradientBoosting,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ComplexityMetrics {
pub gate_count: usize,
pub circuit_depth: usize,
pub qubit_count: usize,
pub two_qubit_gate_count: usize,
pub memory_requirement: usize,
pub parallelism_factor: f64,
pub entanglement_complexity: f64,
pub gate_type_distribution: HashMap<String, usize>,
pub critical_path_complexity: f64,
pub resource_estimation: ResourceMetrics,
}
impl Default for ComplexityMetrics {
fn default() -> Self {
Self {
gate_count: 0,
circuit_depth: 0,
qubit_count: 0,
two_qubit_gate_count: 0,
memory_requirement: 0,
parallelism_factor: 0.0,
entanglement_complexity: 0.0,
gate_type_distribution: HashMap::new(),
critical_path_complexity: 0.0,
resource_estimation: ResourceMetrics::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceMetrics {
pub cpu_time_estimate: f64,
pub memory_usage_estimate: usize,
pub io_operations_estimate: usize,
pub network_bandwidth_estimate: usize,
pub gpu_memory_estimate: usize,
pub thread_requirement: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct ExecutionDataPoint {
pub complexity: ComplexityMetrics,
pub backend_type: BackendType,
pub execution_time: Duration,
pub hardware_specs: PerformanceHardwareSpecs,
#[serde(skip_serializing, skip_deserializing)]
pub timestamp: std::time::SystemTime,
pub success: bool,
pub error_info: Option<String>,
}
impl Default for ExecutionDataPoint {
fn default() -> Self {
Self {
complexity: ComplexityMetrics::default(),
backend_type: BackendType::StateVector,
execution_time: Duration::from_secs(0),
hardware_specs: PerformanceHardwareSpecs::default(),
timestamp: std::time::SystemTime::now(),
success: false,
error_info: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceHardwareSpecs {
pub cpu_cores: usize,
pub total_memory: usize,
pub available_memory: usize,
pub gpu_memory: Option<usize>,
pub cpu_frequency: f64,
pub network_bandwidth: Option<f64>,
pub load_average: f64,
}
impl Default for PerformanceHardwareSpecs {
fn default() -> Self {
Self {
cpu_cores: 1,
total_memory: 1024 * 1024 * 1024, available_memory: 512 * 1024 * 1024, gpu_memory: None,
cpu_frequency: 2000.0, network_bandwidth: None,
load_average: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PredictionResult {
pub predicted_time: Duration,
pub confidence: f64,
pub prediction_interval: (Duration, Duration),
pub model_type: ModelType,
pub feature_importance: HashMap<String, f64>,
pub metadata: PredictionMetadata,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PredictionMetadata {
pub prediction_time: Duration,
pub samples_used: usize,
pub model_trained: bool,
pub cv_score: Option<f64>,
pub prediction_method: String,
}
pub struct PerformancePredictionEngine {
config: PerformancePredictionConfig,
execution_history: VecDeque<ExecutionDataPoint>,
trained_models: HashMap<BackendType, TrainedModel>,
scirs2_backend: SciRS2Backend,
current_hardware: PerformanceHardwareSpecs,
prediction_stats: PredictionStatistics,
timing_accumulator: (u64, f64, f64),
}
#[derive(Debug, Clone, Serialize)]
pub struct TrainedModel {
pub model_type: ModelType,
pub parameters: Vec<f64>,
pub feature_weights: HashMap<String, f64>,
pub training_stats: TrainingStatistics,
#[serde(skip_serializing, skip_deserializing)]
pub last_trained: std::time::SystemTime,
}
impl Default for TrainedModel {
fn default() -> Self {
Self {
model_type: ModelType::LinearRegression,
parameters: Vec::new(),
feature_weights: HashMap::new(),
training_stats: TrainingStatistics::default(),
last_trained: std::time::SystemTime::now(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TrainingStatistics {
pub training_samples: usize,
pub training_accuracy: f64,
pub validation_accuracy: f64,
pub mean_absolute_error: f64,
pub root_mean_square_error: f64,
pub training_time: Duration,
}
impl Default for TrainingStatistics {
fn default() -> Self {
Self {
training_samples: 0,
training_accuracy: 0.0,
validation_accuracy: 0.0,
mean_absolute_error: 0.0,
root_mean_square_error: 0.0,
training_time: Duration::from_secs(0),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PredictionStatistics {
pub total_predictions: usize,
pub successful_predictions: usize,
pub average_accuracy: f64,
pub prediction_time_stats: PerformanceTimingStatistics,
pub model_updates: usize,
pub cache_hit_rate: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceTimingStatistics {
pub average: Duration,
pub minimum: Duration,
pub maximum: Duration,
pub std_deviation: Duration,
}
impl PerformancePredictionEngine {
pub fn new(config: PerformancePredictionConfig) -> Result<Self> {
let current_hardware = Self::detect_hardware_specs()?;
Ok(Self {
config,
execution_history: VecDeque::with_capacity(10_000),
trained_models: HashMap::new(),
scirs2_backend: SciRS2Backend::new(),
current_hardware,
prediction_stats: PredictionStatistics::default(),
timing_accumulator: (0, 0.0, 0.0),
})
}
pub fn predict_execution_time<const N: usize>(
&mut self,
circuit: &Circuit<N>,
backend_type: BackendType,
) -> Result<PredictionResult> {
let start_time = Instant::now();
let complexity = self.analyze_circuit_complexity(circuit)?;
let prediction = match self.config.prediction_strategy {
PredictionStrategy::StaticAnalysis => {
self.predict_with_static_analysis(&complexity, backend_type)?
}
PredictionStrategy::MachineLearning => {
self.predict_with_ml(&complexity, backend_type)?
}
PredictionStrategy::Hybrid => self.predict_with_hybrid(&complexity, backend_type)?,
PredictionStrategy::Ensemble => {
self.predict_with_ensemble(&complexity, backend_type)?
}
};
self.prediction_stats.total_predictions += 1;
let prediction_time = start_time.elapsed();
self.update_timing_stats(prediction_time);
Ok(prediction)
}
fn analyze_circuit_complexity<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> Result<ComplexityMetrics> {
let gate_count = circuit.num_gates();
let qubit_count = N;
let circuit_depth = self.calculate_circuit_depth(circuit)?;
let two_qubit_gate_count = self.count_two_qubit_gates(circuit)?;
let memory_requirement = self.estimate_memory_requirement(qubit_count);
let parallelism_factor = self.analyze_parallelism_potential(circuit)?;
let entanglement_complexity = self.estimate_entanglement_complexity(circuit)?;
let gate_type_distribution = self.analyze_gate_distribution(circuit)?;
let critical_path_complexity = self.analyze_critical_path(circuit)?;
let resource_estimation = self.estimate_resources(&ComplexityMetrics {
gate_count,
circuit_depth,
qubit_count,
two_qubit_gate_count,
memory_requirement,
parallelism_factor,
entanglement_complexity,
gate_type_distribution: gate_type_distribution.clone(),
critical_path_complexity,
resource_estimation: ResourceMetrics::default(), })?;
Ok(ComplexityMetrics {
gate_count,
circuit_depth,
qubit_count,
two_qubit_gate_count,
memory_requirement,
parallelism_factor,
entanglement_complexity,
gate_type_distribution,
critical_path_complexity,
resource_estimation,
})
}
fn calculate_circuit_depth<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
let mut qubit_last_gate: Vec<usize> = vec![0; N];
let mut max_depth = 0;
let gates = circuit.gates_as_boxes();
for (gate_idx, gate) in gates.iter().enumerate() {
let gate_qubits = self.get_gate_qubits(gate.as_ref())?;
let mut max_dependency = 0;
for &qubit in &gate_qubits {
if qubit < N {
max_dependency = max_dependency.max(qubit_last_gate[qubit]);
}
}
let current_depth = max_dependency + 1;
max_depth = max_depth.max(current_depth);
for &qubit in &gate_qubits {
if qubit < N {
qubit_last_gate[qubit] = current_depth;
}
}
}
Ok(max_depth)
}
fn count_two_qubit_gates<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
let mut count = 0;
let gates = circuit.gates_as_boxes();
for gate in &gates {
let qubits = self.get_gate_qubits(gate.as_ref())?;
if qubits.len() >= 2 {
count += 1;
}
}
Ok(count)
}
fn get_gate_qubits(&self, gate: &dyn GateOp) -> Result<Vec<usize>> {
let qubits = gate.qubits();
Ok(qubits.iter().map(|q| q.id() as usize).collect())
}
const fn estimate_memory_requirement(&self, qubit_count: usize) -> usize {
let state_vector_size = (1usize << qubit_count) * 16;
state_vector_size * 3
}
fn analyze_parallelism_potential<const N: usize>(&self, circuit: &Circuit<N>) -> Result<f64> {
let independent_operations = self.count_independent_operations(circuit)?;
let total_operations = circuit.num_gates();
if total_operations == 0 {
return Ok(0.0);
}
Ok(independent_operations as f64 / total_operations as f64)
}
fn count_independent_operations<const N: usize>(&self, circuit: &Circuit<N>) -> Result<usize> {
let mut independent_count = 0;
let mut qubit_dependencies: Vec<Option<usize>> = vec![None; N];
let gates = circuit.gates_as_boxes();
for (gate_idx, gate) in gates.iter().enumerate() {
let gate_qubits = self.get_gate_qubits(gate.as_ref())?;
let mut has_dependency = false;
for &qubit in &gate_qubits {
if qubit < N && qubit_dependencies[qubit].is_some() {
has_dependency = true;
break;
}
}
if !has_dependency {
independent_count += 1;
}
for &qubit in &gate_qubits {
if qubit < N {
qubit_dependencies[qubit] = Some(gate_idx);
}
}
}
Ok(independent_count)
}
fn estimate_entanglement_complexity<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> Result<f64> {
let two_qubit_gates = self.count_two_qubit_gates(circuit)?;
let total_possible_entangling = N * (N - 1) / 2;
if total_possible_entangling == 0 {
return Ok(0.0);
}
Ok((two_qubit_gates as f64 / total_possible_entangling as f64).min(1.0))
}
fn analyze_gate_distribution<const N: usize>(
&self,
circuit: &Circuit<N>,
) -> Result<HashMap<String, usize>> {
let mut distribution = HashMap::new();
let gates = circuit.gates_as_boxes();
for gate in &gates {
let gate_type = self.get_gate_type_name(gate.as_ref());
*distribution.entry(gate_type).or_insert(0) += 1;
}
Ok(distribution)
}
fn get_gate_type_name(&self, gate: &dyn GateOp) -> String {
gate.name().to_string()
}
fn analyze_critical_path<const N: usize>(&self, circuit: &Circuit<N>) -> Result<f64> {
let depth = self.calculate_circuit_depth(circuit)?;
let gate_count = circuit.num_gates();
if gate_count == 0 {
return Ok(0.0);
}
Ok(depth as f64 / gate_count as f64)
}
fn estimate_resources(&self, complexity: &ComplexityMetrics) -> Result<ResourceMetrics> {
let base_cpu_time = complexity.gate_count as f64 * 1e-6; let depth_factor = complexity.circuit_depth as f64 * 0.1;
let entanglement_factor = complexity.entanglement_complexity * 2.0;
let cpu_time_estimate = base_cpu_time * (1.0 + depth_factor + entanglement_factor);
let memory_usage_estimate = complexity.memory_requirement;
let io_operations_estimate = complexity.gate_count * 2;
let network_bandwidth_estimate = if complexity.qubit_count > 20 {
complexity.memory_requirement / 10 } else {
0
};
let gpu_memory_estimate = complexity.memory_requirement * 2;
let thread_requirement = (complexity.parallelism_factor * 16.0).ceil() as usize;
Ok(ResourceMetrics {
cpu_time_estimate,
memory_usage_estimate,
io_operations_estimate,
network_bandwidth_estimate,
gpu_memory_estimate,
thread_requirement,
})
}
fn predict_with_static_analysis(
&self,
complexity: &ComplexityMetrics,
backend_type: BackendType,
) -> Result<PredictionResult> {
let base_time = complexity.resource_estimation.cpu_time_estimate;
let backend_factor = match backend_type {
BackendType::StateVector => 1.0,
BackendType::SciRS2Gpu => 0.3, BackendType::LargeScale => 0.7, BackendType::Distributed => 0.5, BackendType::Auto => 0.8, };
let predicted_seconds = base_time * backend_factor;
let predicted_time = Duration::from_secs_f64(predicted_seconds);
let confidence = if complexity.qubit_count <= 20 {
0.9
} else {
0.7
};
let lower = Duration::from_secs_f64(predicted_seconds * 0.8);
let upper = Duration::from_secs_f64(predicted_seconds * 1.2);
Ok(PredictionResult {
predicted_time,
confidence,
prediction_interval: (lower, upper),
model_type: ModelType::LinearRegression,
feature_importance: HashMap::new(),
metadata: PredictionMetadata {
prediction_time: Duration::from_millis(1),
samples_used: 0,
model_trained: false,
cv_score: None,
prediction_method: "Static Analysis".to_string(),
},
})
}
fn predict_with_ml(
&mut self,
complexity: &ComplexityMetrics,
backend_type: BackendType,
) -> Result<PredictionResult> {
if self.execution_history.len() < self.config.min_samples_for_ml {
return self.predict_with_static_analysis(complexity, backend_type);
}
if !self.trained_models.contains_key(&backend_type) {
self.train_model_for_backend(backend_type)?;
}
let model = self
.trained_models
.get(&backend_type)
.ok_or_else(|| SimulatorError::ComputationError("Model not found".to_string()))?;
let predicted_seconds = self.apply_model(model, complexity)?;
let predicted_time = Duration::from_secs_f64(predicted_seconds);
let confidence = model.training_stats.validation_accuracy;
let error_margin = model.training_stats.mean_absolute_error;
let lower = Duration::from_secs_f64((predicted_seconds - error_margin).max(0.0));
let upper = Duration::from_secs_f64(predicted_seconds + error_margin);
Ok(PredictionResult {
predicted_time,
confidence,
prediction_interval: (lower, upper),
model_type: model.model_type,
feature_importance: model.feature_weights.clone(),
metadata: PredictionMetadata {
prediction_time: Duration::from_millis(5),
samples_used: model.training_stats.training_samples,
model_trained: true,
cv_score: Some(model.training_stats.validation_accuracy),
prediction_method: "Machine Learning".to_string(),
},
})
}
fn predict_with_hybrid(
&mut self,
complexity: &ComplexityMetrics,
backend_type: BackendType,
) -> Result<PredictionResult> {
let static_pred = self.predict_with_static_analysis(complexity, backend_type)?;
if self.execution_history.len() >= self.config.min_samples_for_ml {
let ml_pred = self.predict_with_ml(complexity, backend_type)?;
let static_weight = 0.3;
let ml_weight = 0.7;
let combined_seconds = static_pred.predicted_time.as_secs_f64().mul_add(
static_weight,
ml_pred.predicted_time.as_secs_f64() * ml_weight,
);
let predicted_time = Duration::from_secs_f64(combined_seconds);
let confidence = static_pred
.confidence
.mul_add(static_weight, ml_pred.confidence * ml_weight);
let lower_combined =
Duration::from_secs_f64(static_pred.prediction_interval.0.as_secs_f64().mul_add(
static_weight,
ml_pred.prediction_interval.0.as_secs_f64() * ml_weight,
));
let upper_combined =
Duration::from_secs_f64(static_pred.prediction_interval.1.as_secs_f64().mul_add(
static_weight,
ml_pred.prediction_interval.1.as_secs_f64() * ml_weight,
));
Ok(PredictionResult {
predicted_time,
confidence,
prediction_interval: (lower_combined, upper_combined),
model_type: ModelType::LinearRegression, feature_importance: ml_pred.feature_importance,
metadata: PredictionMetadata {
prediction_time: Duration::from_millis(6),
samples_used: ml_pred.metadata.samples_used,
model_trained: ml_pred.metadata.model_trained,
cv_score: ml_pred.metadata.cv_score,
prediction_method: "Hybrid (Static + ML)".to_string(),
},
})
} else {
Ok(static_pred)
}
}
fn predict_with_ensemble(
&mut self,
complexity: &ComplexityMetrics,
backend_type: BackendType,
) -> Result<PredictionResult> {
let mut members: Vec<PredictionResult> = Vec::new();
members.push(self.predict_with_static_analysis(complexity, backend_type)?);
if self.execution_history.len() >= self.config.min_samples_for_ml {
if let Ok(ml_pred) = self.predict_with_ml(complexity, backend_type) {
members.push(ml_pred);
}
}
let total_confidence: f64 = members.iter().map(|m| m.confidence).sum();
let use_equal = total_confidence <= f64::EPSILON;
let member_count = members.len() as f64;
let mut predicted_seconds = 0.0;
let mut confidence = 0.0;
let mut lower = f64::MAX;
let mut upper: f64 = 0.0;
let mut samples_used = 0usize;
let mut model_trained = false;
let mut feature_importance = HashMap::new();
for member in &members {
let weight = if use_equal {
1.0 / member_count
} else {
member.confidence / total_confidence
};
predicted_seconds += member.predicted_time.as_secs_f64() * weight;
confidence += member.confidence * weight;
lower = lower.min(member.prediction_interval.0.as_secs_f64());
upper = upper.max(member.prediction_interval.1.as_secs_f64());
samples_used = samples_used.max(member.metadata.samples_used);
model_trained |= member.metadata.model_trained;
if !member.feature_importance.is_empty() {
feature_importance = member.feature_importance.clone();
}
}
if lower == f64::MAX {
lower = predicted_seconds;
}
Ok(PredictionResult {
predicted_time: Duration::from_secs_f64(predicted_seconds.max(0.0)),
confidence: confidence.clamp(0.0, 1.0),
prediction_interval: (
Duration::from_secs_f64(lower.max(0.0)),
Duration::from_secs_f64(upper.max(0.0)),
),
model_type: ModelType::RandomForest,
feature_importance,
metadata: PredictionMetadata {
prediction_time: Duration::from_millis(8),
samples_used,
model_trained,
cv_score: None,
prediction_method: format!("Ensemble ({} base models)", members.len()),
},
})
}
const FEATURE_NAMES: [&'static str; 5] = [
"gate_count",
"circuit_depth",
"qubit_count",
"entanglement_complexity",
"parallelism_factor",
];
fn feature_row(complexity: &ComplexityMetrics) -> [f64; 5] {
[
(complexity.gate_count as f64).ln_1p(),
(complexity.circuit_depth as f64).ln_1p(),
(complexity.qubit_count as f64).ln_1p(),
complexity.entanglement_complexity,
complexity.parallelism_factor,
]
}
fn train_model_for_backend(&mut self, backend_type: BackendType) -> Result<()> {
let train_start = Instant::now();
let training_data: Vec<&ExecutionDataPoint> = self
.execution_history
.iter()
.filter(|data| data.backend_type == backend_type && data.success)
.collect();
if training_data.is_empty() {
return Err(SimulatorError::ComputationError(
"No training data available".to_string(),
));
}
let samples: Vec<([f64; 5], f64)> = training_data
.iter()
.map(|dp| {
(
Self::feature_row(&dp.complexity),
dp.execution_time.as_secs_f64().ln_1p(),
)
})
.collect();
let total = samples.len();
let split = if total >= 5 {
total - (total / 5).max(1)
} else {
total
};
let (train_slice, valid_slice) = samples.split_at(split);
let train_slice = if train_slice.is_empty() {
&samples[..]
} else {
train_slice
};
let valid_slice = if valid_slice.is_empty() {
train_slice
} else {
valid_slice
};
let coefficients = Self::fit_least_squares(train_slice)?;
let training_accuracy = Self::r_squared(train_slice, &coefficients);
let validation_accuracy = Self::r_squared(valid_slice, &coefficients);
let (mean_absolute_error, root_mean_square_error) =
Self::error_metrics(valid_slice, &coefficients);
let feature_weights = Self::feature_importance(train_slice, &coefficients);
let model = TrainedModel {
model_type: ModelType::LinearRegression,
parameters: coefficients,
feature_weights,
training_stats: TrainingStatistics {
training_samples: train_slice.len(),
training_accuracy,
validation_accuracy,
mean_absolute_error,
root_mean_square_error,
training_time: train_start.elapsed(),
},
last_trained: std::time::SystemTime::now(),
};
self.trained_models.insert(backend_type, model);
self.prediction_stats.model_updates += 1;
Ok(())
}
fn fit_least_squares(samples: &[([f64; 5], f64)]) -> Result<Vec<f64>> {
const DIM: usize = 6; if samples.is_empty() {
return Err(SimulatorError::ComputationError(
"cannot fit model with no samples".to_string(),
));
}
let mut ata = [[0.0f64; DIM]; DIM];
let mut aty = [0.0f64; DIM];
for (features, target) in samples {
let mut row = [0.0f64; DIM];
row[0] = 1.0;
row[1..DIM].copy_from_slice(features);
for i in 0..DIM {
aty[i] += row[i] * target;
for j in 0..DIM {
ata[i][j] += row[i] * row[j];
}
}
}
let ridge = 1e-6;
for i in 0..DIM {
ata[i][i] += ridge;
}
Self::solve_linear_system(ata, aty)
}
fn solve_linear_system(mut a: [[f64; 6]; 6], mut b: [f64; 6]) -> Result<Vec<f64>> {
const DIM: usize = 6;
for col in 0..DIM {
let mut pivot = col;
let mut best = a[col][col].abs();
for row in (col + 1)..DIM {
let candidate = a[row][col].abs();
if candidate > best {
best = candidate;
pivot = row;
}
}
if best < 1e-12 {
return Err(SimulatorError::ComputationError(
"singular system while fitting performance model".to_string(),
));
}
if pivot != col {
a.swap(col, pivot);
b.swap(col, pivot);
}
for row in (col + 1)..DIM {
let factor = a[row][col] / a[col][col];
for k in col..DIM {
a[row][k] -= factor * a[col][k];
}
b[row] -= factor * b[col];
}
}
let mut x = vec![0.0f64; DIM];
for row in (0..DIM).rev() {
let mut sum = b[row];
for k in (row + 1)..DIM {
sum -= a[row][k] * x[k];
}
x[row] = sum / a[row][row];
}
Ok(x)
}
fn predict_log_time(features: &[f64; 5], coefficients: &[f64]) -> f64 {
let intercept = coefficients.first().copied().unwrap_or(0.0);
let mut acc = intercept;
for (idx, value) in features.iter().enumerate() {
acc += coefficients.get(idx + 1).copied().unwrap_or(0.0) * value;
}
acc
}
fn r_squared(samples: &[([f64; 5], f64)], coefficients: &[f64]) -> f64 {
if samples.is_empty() {
return 0.0;
}
let mean = samples.iter().map(|(_, y)| *y).sum::<f64>() / samples.len() as f64;
let mut ss_res = 0.0;
let mut ss_tot = 0.0;
for (features, target) in samples {
let predicted = Self::predict_log_time(features, coefficients);
ss_res += (target - predicted).powi(2);
ss_tot += (target - mean).powi(2);
}
if ss_tot <= f64::EPSILON {
return if ss_res <= f64::EPSILON { 1.0 } else { 0.0 };
}
(1.0 - ss_res / ss_tot).clamp(0.0, 1.0)
}
fn error_metrics(samples: &[([f64; 5], f64)], coefficients: &[f64]) -> (f64, f64) {
if samples.is_empty() {
return (0.0, 0.0);
}
let mut abs_sum = 0.0;
let mut sq_sum = 0.0;
for (features, target) in samples {
let predicted_log = Self::predict_log_time(features, coefficients);
let predicted = predicted_log.exp_m1().max(0.0);
let actual = target.exp_m1().max(0.0);
let diff = predicted - actual;
abs_sum += diff.abs();
sq_sum += diff * diff;
}
let n = samples.len() as f64;
(abs_sum / n, (sq_sum / n).sqrt())
}
fn feature_importance(
samples: &[([f64; 5], f64)],
coefficients: &[f64],
) -> HashMap<String, f64> {
let mut weights = HashMap::new();
if samples.is_empty() {
return weights;
}
let n = samples.len() as f64;
let mut scaled = [0.0f64; 5];
for col in 0..5 {
let mean = samples.iter().map(|(f, _)| f[col]).sum::<f64>() / n;
let variance = samples
.iter()
.map(|(f, _)| (f[col] - mean).powi(2))
.sum::<f64>()
/ n;
let std_dev = variance.sqrt();
let coeff = coefficients.get(col + 1).copied().unwrap_or(0.0);
scaled[col] = (coeff * std_dev).abs();
}
let total: f64 = scaled.iter().sum();
for (col, name) in Self::FEATURE_NAMES.iter().enumerate() {
let importance = if total > f64::EPSILON {
scaled[col] / total
} else {
0.0
};
weights.insert((*name).to_string(), importance);
}
weights
}
fn apply_model(&self, model: &TrainedModel, complexity: &ComplexityMetrics) -> Result<f64> {
let features = Self::feature_row(complexity);
let predicted_log = Self::predict_log_time(&features, &model.parameters);
Ok(predicted_log.exp_m1().max(0.0))
}
pub fn record_execution(&mut self, data_point: ExecutionDataPoint) -> Result<()> {
self.execution_history.push_back(data_point.clone());
if self.execution_history.len() > self.config.max_history_size {
self.execution_history.pop_front();
}
self.update_prediction_accuracy(&data_point);
if self.execution_history.len() % 100 == 0 {
self.retrain_models()?;
}
Ok(())
}
fn update_prediction_accuracy(&mut self, data_point: &ExecutionDataPoint) {
if !data_point.success {
return;
}
self.prediction_stats.successful_predictions += 1;
let Some(model) = self.trained_models.get(&data_point.backend_type) else {
return;
};
let Ok(predicted_seconds) = self.apply_model(model, &data_point.complexity) else {
return;
};
let actual_seconds = data_point.execution_time.as_secs_f64();
let denom = actual_seconds.max(1e-9);
let sample_accuracy =
(1.0 - (predicted_seconds - actual_seconds).abs() / denom).clamp(0.0, 1.0);
let n = self.prediction_stats.successful_predictions as f64;
let prev = self.prediction_stats.average_accuracy;
self.prediction_stats.average_accuracy = prev + (sample_accuracy - prev) / n;
}
fn retrain_models(&mut self) -> Result<()> {
let backends = vec![
BackendType::StateVector,
BackendType::SciRS2Gpu,
BackendType::LargeScale,
BackendType::Distributed,
];
for backend in backends {
if self
.execution_history
.iter()
.any(|d| d.backend_type == backend)
{
self.train_model_for_backend(backend)?;
}
}
Ok(())
}
fn detect_hardware_specs() -> Result<PerformanceHardwareSpecs> {
let cpu_cores = num_cpus::get();
let total_memory = scirs2_core::resource::get_total_memory()
.map_err(|e| SimulatorError::ComputationError(format!("memory probe failed: {e}")))?;
let available_memory = scirs2_core::resource::get_available_memory()
.map_err(|e| SimulatorError::ComputationError(format!("memory probe failed: {e}")))?;
let load_average = Self::read_load_average();
Ok(PerformanceHardwareSpecs {
cpu_cores,
total_memory,
available_memory,
gpu_memory: None,
cpu_frequency: 0.0,
network_bandwidth: None,
load_average,
})
}
fn read_load_average() -> f64 {
std::fs::read_to_string("/proc/loadavg")
.ok()
.and_then(|content| {
content
.split_whitespace()
.next()
.and_then(|first| first.parse::<f64>().ok())
})
.unwrap_or(0.0)
}
fn update_timing_stats(&mut self, elapsed: Duration) {
let nanos = elapsed.as_nanos() as f64;
let (count, sum, sum_sq) = self.timing_accumulator;
let new_count = count + 1;
let new_sum = sum + nanos;
let new_sum_sq = sum_sq + nanos * nanos;
self.timing_accumulator = (new_count, new_sum, new_sum_sq);
let mean = new_sum / new_count as f64;
let variance = (new_sum_sq / new_count as f64) - mean * mean;
let std_dev = variance.max(0.0).sqrt();
let stats = &mut self.prediction_stats.prediction_time_stats;
stats.average = Duration::from_nanos(mean as u64);
stats.std_deviation = Duration::from_nanos(std_dev as u64);
if new_count == 1 {
stats.minimum = elapsed;
stats.maximum = elapsed;
} else {
stats.minimum = stats.minimum.min(elapsed);
stats.maximum = stats.maximum.max(elapsed);
}
}
#[must_use]
pub const fn get_statistics(&self) -> &PredictionStatistics {
&self.prediction_stats
}
pub fn export_models(&self) -> Result<Vec<u8>> {
let serialized = serde_json::to_vec(&self.trained_models)
.map_err(|e| SimulatorError::ComputationError(format!("Serialization error: {e}")))?;
Ok(serialized)
}
pub fn import_models(&mut self, _data: &[u8]) -> Result<()> {
Err(SimulatorError::ComputationError(
"Import not supported in current implementation".to_string(),
))
}
}
impl Default for ResourceMetrics {
fn default() -> Self {
Self {
cpu_time_estimate: 0.0,
memory_usage_estimate: 0,
io_operations_estimate: 0,
network_bandwidth_estimate: 0,
gpu_memory_estimate: 0,
thread_requirement: 1,
}
}
}
impl Default for PredictionStatistics {
fn default() -> Self {
Self {
total_predictions: 0,
successful_predictions: 0,
average_accuracy: 0.0,
prediction_time_stats: PerformanceTimingStatistics {
average: Duration::from_millis(0),
minimum: Duration::from_millis(0),
maximum: Duration::from_millis(0),
std_deviation: Duration::from_millis(0),
},
model_updates: 0,
cache_hit_rate: 0.0,
}
}
}
pub fn create_performance_predictor() -> Result<PerformancePredictionEngine> {
PerformancePredictionEngine::new(PerformancePredictionConfig::default())
}
pub fn predict_circuit_execution_time<const N: usize>(
predictor: &mut PerformancePredictionEngine,
circuit: &Circuit<N>,
backend_type: BackendType,
) -> Result<PredictionResult> {
predictor.predict_execution_time(circuit, backend_type)
}