use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::fmt::Debug;
use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
use super::{OptimizationContext, LandscapeFeatures};
use crate::error::Result;
#[derive(Debug)]
pub struct PerformancePredictor<T: Float + Debug + Send + Sync + 'static> {
models: HashMap<String, PredictionModel<T>>,
feature_extractors: Vec<Box<dyn FeatureExtractor<T>>>,
prediction_cache: PredictionCache<T>,
uncertainty_estimator: UncertaintyEstimator<T>,
model_ensemble: ModelEnsemble<T>,
performance_history: HashMap<String, VecDeque<PerformanceRecord<T>>>,
accuracy_tracker: AccuracyTracker<T>,
}
impl<T: Float + Debug + std::fmt::Debug + Send + Sync + 'static> PerformancePredictor<T> {
pub fn new() -> Result<Self> {
let mut predictor = Self {
models: HashMap::new(),
feature_extractors: Vec::new(),
prediction_cache: PredictionCache::new(1000)?,
uncertainty_estimator: UncertaintyEstimator::new()?,
model_ensemble: ModelEnsemble::new()?,
performance_history: HashMap::new(),
accuracy_tracker: AccuracyTracker::new(),
};
predictor.initialize_models()?;
predictor.initialize_feature_extractors()?;
Ok(predictor)
}
fn initialize_models(&mut self) -> Result<()> {
self.register_model("linear_regression".to_string(),
PredictionModel::new(ModelType::LinearRegression)?)?;
self.register_model("neural_network".to_string(),
PredictionModel::new(ModelType::NeuralNetwork)?)?;
self.register_model("gaussian_process".to_string(),
PredictionModel::new(ModelType::GaussianProcess)?)?;
self.register_model("random_forest".to_string(),
PredictionModel::new(ModelType::RandomForest)?)?;
self.register_model("ensemble_model".to_string(),
PredictionModel::new(ModelType::Ensemble)?)?;
Ok(())
}
fn initialize_feature_extractors(&mut self) -> Result<()> {
self.feature_extractors.push(Box::new(LandscapeFeatureExtractor::new()?));
self.feature_extractors.push(Box::new(HistoricalFeatureExtractor::new()?));
self.feature_extractors.push(Box::new(ContextualFeatureExtractor::new()?));
self.feature_extractors.push(Box::new(MetaFeatureExtractor::new()?));
self.feature_extractors.push(Box::new(TemporalFeatureExtractor::new()?));
Ok(())
}
pub fn register_model(&mut self, id: String, model: PredictionModel<T>) -> Result<()> {
self.models.insert(id.clone(), model);
self.performance_history.insert(id, VecDeque::new());
Ok(())
}
pub fn predict_performance(
&mut self,
optimizers: &[String],
landscape_features: &LandscapeFeatures<T>,
context: &OptimizationContext<T>,
) -> Result<HashMap<String, T>> {
let start_time = Instant::now();
let mut predictions = HashMap::new();
let cache_key = self.generate_cache_key(optimizers, landscape_features, context)?;
if let Some(cached_predictions) = self.prediction_cache.get(&cache_key) {
return Ok(cached_predictions.clone());
}
let features = self.extract_features(landscape_features, context)?;
for optimizer_id in optimizers {
let optimizer_specific_features = self.augment_features_for_optimizer(&features, optimizer_id)?;
let prediction = self.predict_single_optimizer(optimizer_id, &optimizer_specific_features)?;
predictions.insert(optimizer_id.clone(), prediction);
}
let ensemble_predictions = self.apply_ensemble_prediction(&predictions, &features)?;
let uncertainty_estimates = self.uncertainty_estimator.estimate_uncertainty(
&ensemble_predictions,
&features,
optimizers,
)?;
let adjusted_predictions = self.adjust_predictions_for_uncertainty(
&ensemble_predictions,
&uncertainty_estimates,
)?;
self.prediction_cache.insert(cache_key, adjusted_predictions.clone());
let prediction_time = start_time.elapsed();
self.accuracy_tracker.record_prediction_time(prediction_time);
Ok(adjusted_predictions)
}
pub fn update_with_results(
&mut self,
actual_results: &HashMap<String, Array1<T>>,
landscape_features: &LandscapeFeatures<T>,
context: &OptimizationContext<T>,
) -> Result<()> {
let features = self.extract_features(landscape_features, context)?;
for (optimizer_id, result) in actual_results {
let performance_score = self.calculate_performance_score(result)?;
let optimizer_features = self.augment_features_for_optimizer(&features, optimizer_id)?;
let mut model_ids_to_check = Vec::new();
for (model_id, model) in &mut self.models {
model.update_with_observation(&optimizer_features, performance_score)?;
model_ids_to_check.push(model_id.clone());
}
for model_id in model_ids_to_check {
if let Some(previous_prediction) = self.get_recent_prediction(&model_id, optimizer_id) {
self.accuracy_tracker.record_prediction_accuracy(
model_id,
previous_prediction,
performance_score,
);
}
}
self.update_performance_history(optimizer_id, performance_score, &features)?;
}
self.uncertainty_estimator.update_with_results(actual_results, &features)?;
self.model_ensemble.update_weights(&self.accuracy_tracker)?;
Ok(())
}
fn extract_features(
&mut self,
landscape_features: &LandscapeFeatures<T>,
context: &OptimizationContext<T>,
) -> Result<FeatureVector<T>> {
let mut combined_features = FeatureVector::new();
for extractor in &mut self.feature_extractors {
let extracted_features = extractor.extract_features(landscape_features, context)?;
combined_features.extend(extracted_features);
}
let derived_features = self.compute_derived_features(&combined_features)?;
combined_features.extend(derived_features);
combined_features.normalize()?;
Ok(combined_features)
}
fn augment_features_for_optimizer(
&self,
base_features: &FeatureVector<T>,
optimizer_id: &str,
) -> Result<FeatureVector<T>> {
let mut augmented_features = base_features.clone();
let optimizer_features = self.get_optimizer_characteristics(optimizer_id)?;
augmented_features.extend(optimizer_features);
let historical_features = self.get_historical_performance_features(optimizer_id)?;
augmented_features.extend(historical_features);
Ok(augmented_features)
}
fn predict_single_optimizer(
&mut self,
optimizer_id: &str,
features: &FeatureVector<T>,
) -> Result<T> {
let mut model_predictions = HashMap::new();
for (model_id, model) in &mut self.models {
let prediction = model.predict(features)?;
model_predictions.insert(model_id.clone(), prediction);
}
let ensemble_prediction = self.model_ensemble.combine_predictions(&model_predictions)?;
let adjusted_prediction = self.apply_optimizer_adjustments(optimizer_id, ensemble_prediction)?;
Ok(adjusted_prediction)
}
fn apply_ensemble_prediction(
&self,
individual_predictions: &HashMap<String, T>,
_features: &FeatureVector<T>,
) -> Result<HashMap<String, T>> {
Ok(individual_predictions.clone())
}
fn adjust_predictions_for_uncertainty(
&self,
predictions: &HashMap<String, T>,
uncertainty_estimates: &HashMap<String, T>,
) -> Result<HashMap<String, T>> {
let mut adjusted_predictions = HashMap::new();
for (optimizer_id, &prediction) in predictions {
let uncertainty = uncertainty_estimates.get(optimizer_id).cloned().unwrap_or(T::zero());
let adjustment_factor = T::one() - uncertainty * scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero());
let adjusted_prediction = prediction * adjustment_factor;
adjusted_predictions.insert(optimizer_id.clone(), adjusted_prediction);
}
Ok(adjusted_predictions)
}
fn calculate_performance_score(&self, result: &Array1<T>) -> Result<T> {
let norm = result.iter().map(|&x| x * x).fold(T::zero(), |acc, x| acc + x).sqrt();
let performance = T::one() / (T::one() + norm);
Ok(performance)
}
fn compute_derived_features(&self, base_features: &FeatureVector<T>) -> Result<FeatureVector<T>> {
let mut derived = FeatureVector::new();
for i in 0..base_features.len().min(10) {
let feature_value = base_features.get(i);
derived.push(feature_value * feature_value); derived.push(feature_value * feature_value * feature_value); }
for i in 0..base_features.len().min(5) {
for j in (i + 1)..base_features.len().min(5) {
let interaction = base_features.get(i) * base_features.get(j);
derived.push(interaction);
}
}
Ok(derived)
}
fn get_optimizer_characteristics(&self, optimizer_id: &str) -> Result<FeatureVector<T>> {
let mut features = FeatureVector::new();
match optimizer_id {
"adam" => {
features.push(scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero())); features.push(scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero())); features.push(scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero())); }
"sgd_momentum" => {
features.push(scirs2_core::numeric::NumCast::from(0.3).unwrap_or_else(|| T::zero())); features.push(scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero())); features.push(scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero())); }
_ => {
features.push(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
features.push(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
features.push(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
}
}
Ok(features)
}
fn get_historical_performance_features(&self, optimizer_id: &str) -> Result<FeatureVector<T>> {
let mut features = FeatureVector::new();
if let Some(history) = self.performance_history.get(optimizer_id) {
if !history.is_empty() {
let recent_avg = history.iter()
.rev()
.take(10)
.map(|record| record.performance_score)
.fold(T::zero(), |acc, x| acc + x) / T::from(history.len().min(10)).expect("unwrap failed");
features.push(recent_avg);
if history.len() >= 2 {
let recent = history.back().expect("unwrap failed").performance_score;
let older = history.iter().rev().nth(5).map(|r| r.performance_score).unwrap_or(recent);
let trend = recent - older;
features.push(trend);
} else {
features.push(T::zero());
}
let variance = self.calculate_performance_variance(history)?;
features.push(variance);
} else {
features.push(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero())); features.push(T::zero());
features.push(T::zero());
}
} else {
features.push(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
features.push(T::zero());
features.push(T::zero());
}
Ok(features)
}
fn calculate_performance_variance(&self, history: &VecDeque<PerformanceRecord<T>>) -> Result<T> {
if history.len() < 2 {
return Ok(T::zero());
}
let mean = history.iter()
.map(|record| record.performance_score)
.fold(T::zero(), |acc, x| acc + x) / T::from(history.len()).expect("unwrap failed");
let variance = history.iter()
.map(|record| {
let diff = record.performance_score - mean;
diff * diff
})
.fold(T::zero(), |acc, x| acc + x) / T::from(history.len()).expect("unwrap failed");
Ok(variance)
}
fn apply_optimizer_adjustments(&self, optimizer_id: &str, prediction: T) -> Result<T> {
let adjustment_factor = match optimizer_id {
"adam" => scirs2_core::numeric::NumCast::from(1.1).unwrap_or_else(|| T::zero()), "sgd_momentum" => scirs2_core::numeric::NumCast::from(0.95).unwrap_or_else(|| T::zero()), "learned_lstm" => scirs2_core::numeric::NumCast::from(1.15).unwrap_or_else(|| T::zero()), _ => T::one(),
};
Ok(prediction * adjustment_factor)
}
fn generate_cache_key(
&self,
optimizers: &[String],
landscape_features: &LandscapeFeatures<T>,
context: &OptimizationContext<T>,
) -> Result<String> {
let mut key = String::new();
for optimizer in optimizers {
key.push_str(optimizer);
key.push('_');
}
key.push_str(&format!("{}", context.iteration));
key.push_str(&format!("{}", context.dimensionality));
key.push_str(&format!("{:.3}", landscape_features.curvature_info.mean_curvature.to_f64().unwrap_or(0.0)));
Ok(key)
}
fn get_recent_prediction(&self, model_id: &str, optimizer_id: &str) -> Option<T> {
Some(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()))
}
fn update_performance_history(
&mut self,
optimizer_id: &str,
performance_score: T,
features: &FeatureVector<T>,
) -> Result<()> {
let record = PerformanceRecord {
performance_score,
features: features.clone(),
timestamp: Instant::now(),
};
if let Some(history) = self.performance_history.get_mut(optimizer_id) {
history.push_back(record);
if history.len() > 1000 {
history.pop_front();
}
}
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
for model in self.models.values_mut() {
model.reset()?;
}
self.prediction_cache.clear();
self.performance_history.clear();
self.accuracy_tracker.reset();
Ok(())
}
pub fn get_accuracy_statistics(&self) -> AccuracyStatistics<T> {
self.accuracy_tracker.get_statistics()
}
pub fn get_model_comparison(&self) -> HashMap<String, ModelPerformance<T>> {
self.accuracy_tracker.get_model_comparison()
}
}
pub trait FeatureExtractor<T: Float + Debug + Send + Sync + 'static>: Send + Sync + std::fmt::Debug {
fn extract_features(
&mut self,
landscape_features: &LandscapeFeatures<T>,
context: &OptimizationContext<T>,
) -> Result<FeatureVector<T>>;
}
#[derive(Debug, Clone)]
pub struct FeatureVector<T: Float + Debug + Send + Sync + 'static> {
features: Vec<T>,
}
impl<T: Float + Debug + Send + Sync + 'static> FeatureVector<T> {
pub fn new() -> Self {
Self {
features: Vec::new(),
}
}
pub fn push(&mut self, value: T) {
self.features.push(value);
}
pub fn extend(&mut self, other: FeatureVector<T>) {
self.features.extend(other.features);
}
pub fn get(&self, index: usize) -> T {
self.features.get(index).cloned().unwrap_or(T::zero())
}
pub fn len(&self) -> usize {
self.features.len()
}
pub fn normalize(&mut self) -> Result<()> {
if self.features.is_empty() {
return Ok(());
}
let mean = self.features.iter().fold(T::zero(), |acc, &x| acc + x) / T::from(self.features.len()).expect("unwrap failed");
let variance = self.features.iter()
.map(|&x| (x - mean) * (x - mean))
.fold(T::zero(), |acc, x| acc + x) / T::from(self.features.len()).expect("unwrap failed");
let std_dev = variance.sqrt();
if std_dev > T::zero() {
for feature in &mut self.features {
*feature = (*feature - mean) / std_dev;
}
}
Ok(())
}
pub fn as_slice(&self) -> &[T] {
&self.features
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModelType {
LinearRegression,
NeuralNetwork,
GaussianProcess,
RandomForest,
Ensemble,
}
#[derive(Debug)]
pub struct PredictionModel<T: Float + Debug + Send + Sync + 'static> {
model_type: ModelType,
parameters: Vec<T>,
training_data: Vec<(FeatureVector<T>, T)>,
is_trained: bool,
}
impl<T: Float + Debug + Send + Sync + 'static> PredictionModel<T> {
pub fn new(model_type: ModelType) -> Result<Self> {
Ok(Self {
model_type,
parameters: Vec::new(),
training_data: Vec::new(),
is_trained: false,
})
}
pub fn predict(&mut self, features: &FeatureVector<T>) -> Result<T> {
if !self.is_trained && !self.training_data.is_empty() {
self.train()?;
}
match self.model_type {
ModelType::LinearRegression => self.linear_predict(features),
ModelType::NeuralNetwork => self.neural_predict(features),
ModelType::GaussianProcess => self.gp_predict(features),
ModelType::RandomForest => self.rf_predict(features),
ModelType::Ensemble => self.ensemble_predict(features),
}
}
pub fn update_with_observation(&mut self, features: &FeatureVector<T>, target: T) -> Result<()> {
self.training_data.push((features.clone(), target));
if self.training_data.len() > 10000 {
self.training_data.remove(0);
}
if self.training_data.len() % 100 == 0 {
self.train()?;
}
Ok(())
}
fn train(&mut self) -> Result<()> {
match self.model_type {
ModelType::LinearRegression => self.train_linear(),
ModelType::NeuralNetwork => self.train_neural(),
ModelType::GaussianProcess => self.train_gp(),
ModelType::RandomForest => self.train_rf(),
ModelType::Ensemble => self.train_ensemble(),
}?;
self.is_trained = true;
Ok(())
}
fn linear_predict(&self, features: &FeatureVector<T>) -> Result<T> {
if self.parameters.is_empty() {
return Ok(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
}
let mut prediction = T::zero();
for (i, &feature) in features.as_slice().iter().enumerate() {
if i < self.parameters.len() {
prediction = prediction + feature * self.parameters[i];
}
}
Ok(prediction.max(T::zero()).min(T::one()))
}
fn neural_predict(&self, _features: &FeatureVector<T>) -> Result<T> {
Ok(scirs2_core::numeric::NumCast::from(0.6).unwrap_or_else(|| T::zero()))
}
fn gp_predict(&self, _features: &FeatureVector<T>) -> Result<T> {
Ok(scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero()))
}
fn rf_predict(&self, _features: &FeatureVector<T>) -> Result<T> {
Ok(scirs2_core::numeric::NumCast::from(0.65).unwrap_or_else(|| T::zero()))
}
fn ensemble_predict(&self, features: &FeatureVector<T>) -> Result<T> {
let linear = self.linear_predict(features)?;
let neural = self.neural_predict(features)?;
let gp = self.gp_predict(features)?;
let rf = self.rf_predict(features)?;
Ok((linear + neural + gp + rf) / scirs2_core::numeric::NumCast::from(4.0).unwrap_or_else(|| T::zero()))
}
fn train_linear(&mut self) -> Result<()> {
if self.training_data.is_empty() {
return Ok(());
}
let feature_dim = self.training_data[0].0.len();
self.parameters = vec![scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()); feature_dim];
for _ in 0..10 {
let mut gradients = vec![T::zero(); feature_dim];
for (features, target) in &self.training_data {
let prediction = self.linear_predict(features)?;
let error = prediction - *target;
for (i, &feature) in features.as_slice().iter().enumerate() {
if i < gradients.len() {
gradients[i] = gradients[i] + error * feature;
}
}
}
let learning_rate = scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero()) / T::from(self.training_data.len()).expect("unwrap failed");
for (param, grad) in self.parameters.iter_mut().zip(gradients.iter()) {
*param = *param - learning_rate * *grad;
}
}
Ok(())
}
fn train_neural(&mut self) -> Result<()> {
Ok(())
}
fn train_gp(&mut self) -> Result<()> {
Ok(())
}
fn train_rf(&mut self) -> Result<()> {
Ok(())
}
fn train_ensemble(&mut self) -> Result<()> {
Ok(())
}
pub fn reset(&mut self) -> Result<()> {
self.parameters.clear();
self.training_data.clear();
self.is_trained = false;
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct PerformanceRecord<T: Float + Debug + Send + Sync + 'static> {
pub performance_score: T,
pub features: FeatureVector<T>,
pub timestamp: Instant,
}
#[derive(Debug)]
pub struct PredictionCache<T: Float + Debug + Send + Sync + 'static> {
cache: HashMap<String, HashMap<String, T>>,
max_size: usize,
access_order: VecDeque<String>,
}
impl<T: Float + Debug + Send + Sync + 'static> PredictionCache<T> {
pub fn new(max_size: usize) -> Result<Self> {
Ok(Self {
cache: HashMap::new(),
max_size,
access_order: VecDeque::new(),
})
}
pub fn get(&mut self, key: &str) -> Option<HashMap<String, T>> {
if let Some(value) = self.cache.get(key) {
self.access_order.retain(|k| k != key);
self.access_order.push_back(key.to_string());
Some(value.clone())
} else {
None
}
}
pub fn insert(&mut self, key: String, value: HashMap<String, T>) {
while self.cache.len() >= self.max_size && !self.access_order.is_empty() {
if let Some(oldest_key) = self.access_order.pop_front() {
self.cache.remove(&oldest_key);
}
}
self.cache.insert(key.clone(), value);
self.access_order.push_back(key);
}
pub fn clear(&mut self) {
self.cache.clear();
self.access_order.clear();
}
}
#[derive(Debug)]
pub struct UncertaintyEstimator<T: Float + Debug + Send + Sync + 'static> {
variance_model: VarianceModel<T>,
confidence_intervals: HashMap<String, (T, T)>,
}
impl<T: Float + Debug + Send + Sync + 'static> UncertaintyEstimator<T> {
pub fn new() -> Result<Self> {
Ok(Self {
variance_model: VarianceModel::new()?,
confidence_intervals: HashMap::new(),
})
}
pub fn estimate_uncertainty(
&mut self,
predictions: &HashMap<String, T>,
features: &FeatureVector<T>,
optimizers: &[String],
) -> Result<HashMap<String, T>> {
let mut uncertainties = HashMap::new();
for optimizer_id in optimizers {
let prediction = predictions.get(optimizer_id).cloned().unwrap_or(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
let uncertainty = self.variance_model.predict_variance(features, prediction)?;
uncertainties.insert(optimizer_id.clone(), uncertainty);
}
Ok(uncertainties)
}
pub fn update_with_results(
&mut self,
_actual_results: &HashMap<String, Array1<T>>,
_features: &FeatureVector<T>,
) -> Result<()> {
Ok(())
}
}
#[derive(Debug)]
pub struct VarianceModel<T: Float + Debug + Send + Sync + 'static> {
_phantom: std::marker::PhantomData<T>,
}
impl<T: Float + Debug + Send + Sync + 'static> VarianceModel<T> {
pub fn new() -> Result<Self> {
Ok(Self {
_phantom: std::marker::PhantomData,
})
}
pub fn predict_variance(&self, _features: &FeatureVector<T>, _prediction: T) -> Result<T> {
Ok(scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()))
}
}
#[derive(Debug)]
pub struct ModelEnsemble<T: Float + Debug + Send + Sync + 'static> {
weights: HashMap<String, T>,
performance_history: HashMap<String, VecDeque<T>>,
}
impl<T: Float + Debug + Send + Sync + 'static> ModelEnsemble<T> {
pub fn new() -> Result<Self> {
Ok(Self {
weights: HashMap::new(),
performance_history: HashMap::new(),
})
}
pub fn combine_predictions(&self, predictions: &HashMap<String, T>) -> Result<T> {
if predictions.is_empty() {
return Ok(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()));
}
let mut weighted_sum = T::zero();
let mut total_weight = T::zero();
for (model_id, &prediction) in predictions {
let weight = self.weights.get(model_id).cloned().unwrap_or(T::one());
weighted_sum = weighted_sum + weight * prediction;
total_weight = total_weight + weight;
}
if total_weight > T::zero() {
Ok(weighted_sum / total_weight)
} else {
Ok(scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()))
}
}
pub fn update_weights(&mut self, accuracy_tracker: &AccuracyTracker<T>) -> Result<()> {
let model_performance = accuracy_tracker.get_model_comparison();
for (model_id, performance) in model_performance {
let weight = performance.average_accuracy;
self.weights.insert(model_id, weight);
}
Ok(())
}
}
#[derive(Debug)]
pub struct AccuracyTracker<T: Float + Debug + Send + Sync + 'static> {
model_accuracies: HashMap<String, VecDeque<T>>,
prediction_times: VecDeque<Duration>,
total_predictions: usize,
}
impl<T: Float + Debug + Send + Sync + 'static> AccuracyTracker<T> {
pub fn new() -> Self {
Self {
model_accuracies: HashMap::new(),
prediction_times: VecDeque::new(),
total_predictions: 0,
}
}
pub fn record_prediction_accuracy(&mut self, model_id: String, predicted: T, actual: T) {
let accuracy = T::one() - (predicted - actual).abs();
let accuracies = self.model_accuracies.entry(model_id).or_insert_with(VecDeque::new);
accuracies.push_back(accuracy);
if accuracies.len() > 1000 {
accuracies.pop_front();
}
}
pub fn record_prediction_time(&mut self, time: Duration) {
self.prediction_times.push_back(time);
self.total_predictions += 1;
if self.prediction_times.len() > 1000 {
self.prediction_times.pop_front();
}
}
pub fn get_statistics(&self) -> AccuracyStatistics<T> {
let average_prediction_time = if self.prediction_times.is_empty() {
Duration::new(0, 0)
} else {
self.prediction_times.iter().sum::<Duration>() / self.prediction_times.len() as u32
};
AccuracyStatistics {
total_predictions: self.total_predictions,
average_prediction_time,
model_count: self.model_accuracies.len(),
_phantom: std::marker::PhantomData,
}
}
pub fn get_model_comparison(&self) -> HashMap<String, ModelPerformance<T>> {
let mut comparison = HashMap::new();
for (model_id, accuracies) in &self.model_accuracies {
if !accuracies.is_empty() {
let average_accuracy = accuracies.iter().fold(T::zero(), |acc, &x| acc + x)
/ T::from(accuracies.len()).expect("unwrap failed");
let variance = accuracies.iter()
.map(|&x| (x - average_accuracy) * (x - average_accuracy))
.fold(T::zero(), |acc, x| acc + x) / T::from(accuracies.len()).expect("unwrap failed");
comparison.insert(model_id.clone(), ModelPerformance {
average_accuracy,
accuracy_variance: variance,
prediction_count: accuracies.len(),
});
}
}
comparison
}
pub fn reset(&mut self) {
self.model_accuracies.clear();
self.prediction_times.clear();
self.total_predictions = 0;
}
}
#[derive(Debug, Clone)]
pub struct AccuracyStatistics<T: Float + Debug + Send + Sync + 'static> {
pub total_predictions: usize,
pub average_prediction_time: Duration,
pub model_count: usize,
_phantom: std::marker::PhantomData<T>,
}
#[derive(Debug, Clone)]
pub struct ModelPerformance<T: Float + Debug + Send + Sync + 'static> {
pub average_accuracy: T,
pub accuracy_variance: T,
pub prediction_count: usize,
}
macro_rules! impl_feature_extractor {
($name:ident, $feature_count:expr) => {
#[derive(Debug)]
pub struct $name<T: Float + Debug + Send + Sync + 'static> {
_phantom: std::marker::PhantomData<T>,
}
impl<T: Float + Debug + Send + Sync + 'static> $name<T> {
pub fn new() -> Result<Self> {
Ok(Self {
_phantom: std::marker::PhantomData,
})
}
}
impl<T: Float + Debug + std::fmt::Debug + Send + Sync + Send + Sync> FeatureExtractor<T> for $name<T> {
fn extract_features(
&mut self,
_landscape_features: &LandscapeFeatures<T>,
_context: &OptimizationContext<T>,
) -> Result<FeatureVector<T>> {
let mut features = FeatureVector::new();
for i in 0..$feature_count {
features.push(scirs2_core::numeric::NumCast::from(i as f64 / $feature_count as f64).unwrap_or_else(|| T::zero()));
}
Ok(features)
}
}
};
}
impl_feature_extractor!(LandscapeFeatureExtractor, 10);
impl_feature_extractor!(HistoricalFeatureExtractor, 8);
impl_feature_extractor!(ContextualFeatureExtractor, 12);
impl_feature_extractor!(MetaFeatureExtractor, 6);
impl_feature_extractor!(TemporalFeatureExtractor, 5);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_performance_predictor_creation() {
let predictor = PerformancePredictor::<f32>::new();
assert!(predictor.is_ok());
}
#[test]
fn test_feature_vector() {
let mut features = FeatureVector::<f32>::new();
features.push(1.0);
features.push(2.0);
features.push(3.0);
assert_eq!(features.len(), 3);
assert_eq!(features.get(0), 1.0);
assert_eq!(features.get(1), 2.0);
}
#[test]
fn test_prediction_model() {
let mut model = PredictionModel::<f32>::new(ModelType::LinearRegression).expect("unwrap failed");
let features = FeatureVector::<f32>::new();
let prediction = model.predict(&features);
assert!(prediction.is_ok());
}
#[test]
fn test_prediction_cache() {
let mut cache = PredictionCache::<f32>::new(2).expect("unwrap failed");
let mut predictions = HashMap::new();
predictions.insert("optimizer1".to_string(), 0.8);
cache.insert("key1".to_string(), predictions.clone());
let retrieved = cache.get("key1");
assert!(retrieved.is_some());
}
}