use super::config::*;
use crate::OptimizerError as OptimError;
use scirs2_core::ndarray::Array1;
use scirs2_core::numeric::Float;
use std::collections::HashMap;
use std::fmt::Debug;
use std::time::{Duration, SystemTime};
type Result<T> = std::result::Result<T, OptimError>;
#[derive(Debug)]
pub struct AdaptationController<T: Float + Debug + Send + Sync + Debug> {
pub triggers: Vec<Box<dyn AdaptationTrigger<T>>>,
pub adaptation_strategies: HashMap<AdaptationType, Box<dyn AdaptationStrategy<T>>>,
pub adaptation_history: Vec<AdaptationEvent<T>>,
pub current_state: AdaptationState<T>,
pub config: AdaptationConfig<T>,
}
#[derive(Debug, Clone)]
pub struct AdaptationState<T: Float + Debug + Send + Sync + 'static> {
pub last_adaptation_time: SystemTime,
pub adaptation_count: usize,
pub current_adaptation: Option<AdaptationType>,
pub adaptation_effectiveness: HashMap<AdaptationType, T>,
pub cooldown_until: HashMap<AdaptationType, SystemTime>,
}
#[derive(Debug, Clone)]
pub struct AdaptationConfig<T: Float + Debug + Send + Sync + 'static> {
pub max_adaptations_per_window: usize,
pub adaptation_window: Duration,
pub min_effectiveness_threshold: T,
pub failed_adaptation_cooldown: Duration,
pub conservative_mode: bool,
}
#[derive(Debug, Clone)]
pub struct AdaptationEvent<T: Float + Debug + Send + Sync + 'static> {
pub timestamp: SystemTime,
pub adaptation_type: AdaptationType,
pub trigger_name: String,
pub performance_before: T,
pub performance_after: Option<T>,
pub success: bool,
pub duration: Duration,
pub metadata: HashMap<String, String>,
}
pub trait AdaptationTrigger<T: Float + Debug + Send + Sync + 'static>: Send + Sync + Debug {
fn is_triggered(&self, context: &OptimizationContext<T>) -> bool;
fn trigger_type(&self) -> AdaptationType;
fn name(&self) -> &str;
fn urgency(&self, context: &OptimizationContext<T>) -> T {
scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero())
}
fn metadata(&self, context: &OptimizationContext<T>) -> HashMap<String, String> {
HashMap::new()
}
}
pub trait AdaptationStrategy<T: Float + Debug + Send + Sync + 'static>: Send + Sync + Debug {
fn execute_adaptation(
&mut self,
context: &OptimizationContext<T>,
trigger_metadata: &HashMap<String, String>,
) -> Result<AdaptationResult<T>>;
fn estimate_improvement(&self, context: &OptimizationContext<T>) -> Result<T>;
fn can_apply(&self, context: &OptimizationContext<T>) -> bool;
fn strategy_name(&self) -> &str;
}
#[derive(Debug, Clone)]
pub struct AdaptationResult<T: Float + Debug + Send + Sync + 'static> {
pub success: bool,
pub performance_improvement: T,
pub new_parameters: Option<Array1<T>>,
pub configuration_changes: HashMap<String, String>,
pub duration: Duration,
pub metadata: HashMap<String, String>,
}
#[derive(Debug)]
pub struct PerformanceDegradationTrigger<T: Float + Debug + Send + Sync + 'static> {
pub threshold: T,
pub consecutive_drops: usize,
pub min_history_length: usize,
}
#[derive(Debug)]
pub struct ResourceConstraintTrigger<T: Float + Debug + Send + Sync + 'static> {
pub memory_threshold: T,
pub cpu_threshold: T,
pub gpu_threshold: T,
}
#[derive(Debug)]
pub struct ConvergenceStagnationTrigger<T: Float + Debug + Send + Sync + 'static> {
pub stagnation_threshold: T,
pub check_iterations: usize,
}
#[derive(Debug)]
pub struct LearningRateAdaptationStrategy<T: Float + Debug + Send + Sync + 'static> {
pub adaptation_factor: T,
pub min_learning_rate: T,
pub max_learning_rate: T,
}
#[derive(Debug)]
pub struct OptimizerSelectionStrategy<T: Float + Debug + Send + Sync + 'static> {
pub available_optimizers: Vec<String>,
pub selection_criteria: SelectionCriteria<T>,
}
#[derive(Debug, Clone)]
pub struct SelectionCriteria<T: Float + Debug + Send + Sync + 'static> {
pub performance_weight: T,
pub efficiency_weight: T,
pub robustness_weight: T,
}
impl<T: Float + Debug + Send + Sync + Debug + 'static> AdaptationController<T> {
pub fn new(config: AdaptationConfig<T>) -> Self {
Self {
triggers: Vec::new(),
adaptation_strategies: HashMap::new(),
adaptation_history: Vec::new(),
current_state: AdaptationState::new(),
config,
}
}
pub fn add_trigger(&mut self, trigger: Box<dyn AdaptationTrigger<T>>) -> Result<()> {
self.triggers.push(trigger);
Ok(())
}
pub fn add_strategy(
&mut self,
adaptation_type: AdaptationType,
strategy: Box<dyn AdaptationStrategy<T>>,
) -> Result<()> {
self.adaptation_strategies.insert(adaptation_type, strategy);
Ok(())
}
pub fn check_and_adapt(
&mut self,
context: &OptimizationContext<T>,
) -> Result<Vec<AdaptationEvent<T>>> {
let mut adaptation_events = Vec::new();
if self.is_in_global_cooldown() {
return Ok(adaptation_events);
}
let mut triggers_to_execute = Vec::new();
for trigger in &self.triggers {
if self.should_execute_adaptation(trigger.as_ref(), context)? {
let adaptation_type = trigger.trigger_type();
if self.adaptation_strategies.contains_key(&adaptation_type) {
let metadata = trigger.metadata(context);
triggers_to_execute.push((adaptation_type, trigger.name().to_string(), metadata));
}
}
}
for (adaptation_type, trigger_name, metadata) in triggers_to_execute {
let start_time = SystemTime::now();
let performance_before = self.get_current_performance(context);
if let Some(strategy) = self.adaptation_strategies.get_mut(&adaptation_type) {
let adaptation_result = strategy.execute_adaptation(context, &metadata)?;
let duration = start_time.elapsed().unwrap_or(Duration::from_secs(0));
let event = AdaptationEvent {
timestamp: start_time,
adaptation_type,
trigger_name: trigger_name.clone(),
performance_before,
performance_after: None, success: adaptation_result.success,
duration,
metadata: adaptation_result.metadata.clone(),
};
adaptation_events.push(event);
self.current_state.last_adaptation_time = start_time;
self.current_state.adaptation_count += 1;
self.current_state.current_adaptation = Some(adaptation_type);
if adaptation_result.success {
let effectiveness = adaptation_result.performance_improvement;
self.current_state.adaptation_effectiveness
.insert(adaptation_type, effectiveness);
}
if self.config.conservative_mode {
self.current_state.cooldown_until.insert(
adaptation_type,
SystemTime::now() + self.config.failed_adaptation_cooldown,
);
}
}
}
Ok(adaptation_events)
}
fn should_execute_adaptation(
&self,
trigger: &dyn AdaptationTrigger<T>,
context: &OptimizationContext<T>,
) -> Result<bool> {
if !trigger.is_triggered(context) {
return Ok(false);
}
let adaptation_type = trigger.trigger_type();
if let Some(cooldown_until) = self.current_state.cooldown_until.get(&adaptation_type) {
if SystemTime::now() < *cooldown_until {
return Ok(false);
}
}
if self.is_rate_limited()? {
return Ok(false);
}
if let Some(effectiveness) = self.current_state.adaptation_effectiveness.get(&adaptation_type) {
if *effectiveness < self.config.min_effectiveness_threshold {
return Ok(false);
}
}
Ok(true)
}
fn execute_adaptation_strategy(
&mut self,
strategy: &mut dyn AdaptationStrategy<T>,
adaptation_type: AdaptationType,
trigger_name: &str,
context: &OptimizationContext<T>,
metadata: &HashMap<String, String>,
) -> Result<AdaptationEvent<T>> {
let start_time = SystemTime::now();
let performance_before = self.get_current_performance(context);
let adaptation_result = strategy.execute_adaptation(context, metadata)?;
let duration = start_time.elapsed().unwrap_or(Duration::from_secs(0));
let event = AdaptationEvent {
timestamp: start_time,
adaptation_type,
trigger_name: trigger_name.to_string(),
performance_before,
performance_after: None, success: adaptation_result.success,
duration,
metadata: adaptation_result.metadata.clone(),
};
self.current_state.last_adaptation_time = start_time;
self.current_state.adaptation_count += 1;
self.current_state.current_adaptation = Some(adaptation_type);
if adaptation_result.success {
let effectiveness = adaptation_result.performance_improvement;
self.current_state.adaptation_effectiveness
.insert(adaptation_type, effectiveness);
}
self.adaptation_history.push(event.clone());
if self.adaptation_history.len() > 1000 {
self.adaptation_history.remove(0);
}
Ok(event)
}
fn is_in_global_cooldown(&self) -> bool {
let now = SystemTime::now();
let cooldown_duration = Duration::from_secs(60);
if let Ok(elapsed) = now.duration_since(self.current_state.last_adaptation_time) {
elapsed < cooldown_duration
} else {
false
}
}
fn is_rate_limited(&self) -> Result<bool> {
let now = SystemTime::now();
let window_start = now - self.config.adaptation_window;
let recent_adaptations = self.adaptation_history
.iter()
.filter(|event| event.timestamp >= window_start)
.count();
Ok(recent_adaptations >= self.config.max_adaptations_per_window)
}
fn get_current_performance(&self, context: &OptimizationContext<T>) -> T {
if !context.historical_performance.is_empty() {
context.historical_performance[context.historical_performance.len() - 1]
} else {
T::zero()
}
}
pub fn get_adaptation_statistics(&self) -> AdaptationStatistics<T> {
let total_adaptations = self.adaptation_history.len();
let successful_adaptations = self.adaptation_history
.iter()
.filter(|event| event.success)
.count();
let success_rate = if total_adaptations > 0 {
scirs2_core::numeric::NumCast::from(successful_adaptations as f64 / total_adaptations as f64).unwrap_or_else(|| T::zero())
} else {
T::zero()
};
let average_improvement = if successful_adaptations > 0 {
let total_improvement: T = self.adaptation_history
.iter()
.filter(|event| event.success)
.filter_map(|event| event.performance_after)
.map(|after| after - self.adaptation_history[0].performance_before)
.fold(T::zero(), |acc, x| acc + x);
total_improvement / scirs2_core::numeric::NumCast::from(successful_adaptations).unwrap_or_else(|| T::zero())
} else {
T::zero()
};
AdaptationStatistics {
total_adaptations,
successful_adaptations,
success_rate,
average_improvement,
most_effective_type: self.get_most_effective_adaptation_type(),
}
}
fn get_most_effective_adaptation_type(&self) -> Option<AdaptationType> {
self.current_state.adaptation_effectiveness
.iter()
.max_by(|a, b| a.1.partial_cmp(b.1).unwrap_or(std::cmp::Ordering::Equal))
.map(|(adaptation_type, _)| *adaptation_type)
}
pub fn setup_default_triggers(&mut self) -> Result<()> {
let degradation_trigger = Box::new(PerformanceDegradationTrigger::new(scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero())));
self.triggers.push(degradation_trigger);
let stagnation_trigger = Box::new(ConvergenceStagnationTrigger::new(scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero())));
self.triggers.push(stagnation_trigger);
Ok(())
}
pub fn trigger_adaptation(&mut self, context: &OptimizationContext<T>) -> Result<Option<AdaptationEvent<T>>> {
for trigger in &self.triggers {
if trigger.is_triggered(context) {
let adaptation_type = trigger.trigger_type();
let current_performance = if !context.historical_performance.is_empty() {
context.historical_performance[context.historical_performance.len() - 1]
} else {
T::zero()
};
return Ok(Some(AdaptationEvent {
timestamp: std::time::SystemTime::now(),
adaptation_type,
trigger_name: trigger.name().to_string(),
performance_before: current_performance,
performance_after: None,
success: false, duration: Duration::from_secs(0),
metadata: HashMap::new(),
}));
}
}
Ok(None)
}
pub fn get_effectiveness_score(&self) -> T {
let stats = self.get_adaptation_statistics();
T::from(stats.success_rate).unwrap_or(T::zero())
}
pub fn reset(&mut self) -> Result<()> {
self.adaptation_history.clear();
self.current_state.adaptation_effectiveness.clear();
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct AdaptationStatistics<T: Float + Debug + Send + Sync + 'static> {
pub total_adaptations: usize,
pub successful_adaptations: usize,
pub success_rate: T,
pub average_improvement: T,
pub most_effective_type: Option<AdaptationType>,
}
impl<T: Float + Debug + Send + Sync + 'static> AdaptationState<T> {
pub fn new() -> Self {
Self {
last_adaptation_time: SystemTime::UNIX_EPOCH,
adaptation_count: 0,
current_adaptation: None,
adaptation_effectiveness: HashMap::new(),
cooldown_until: HashMap::new(),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for AdaptationConfig<T> {
fn default() -> Self {
Self {
max_adaptations_per_window: 10,
adaptation_window: Duration::from_secs(300), min_effectiveness_threshold: scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero()),
failed_adaptation_cooldown: Duration::from_secs(60),
conservative_mode: false,
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> PerformanceDegradationTrigger<T> {
pub fn new(threshold: T) -> Self {
Self {
threshold,
consecutive_drops: 3,
min_history_length: 5,
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> AdaptationTrigger<T> for PerformanceDegradationTrigger<T> {
fn is_triggered(&self, context: &OptimizationContext<T>) -> bool {
if context.historical_performance.len() < self.min_history_length {
return false;
}
let history = &context.historical_performance;
let mut consecutive_drops = 0;
for i in 1..history.len().min(self.consecutive_drops + 1) {
let current = history[history.len() - i];
let previous = history[history.len() - i - 1];
if previous - current > self.threshold {
consecutive_drops += 1;
} else {
break;
}
}
consecutive_drops >= self.consecutive_drops
}
fn trigger_type(&self) -> AdaptationType {
AdaptationType::LearningRate
}
fn name(&self) -> &str {
"PerformanceDegradationTrigger"
}
fn urgency(&self, context: &OptimizationContext<T>) -> T {
if context.historical_performance.len() >= 2 {
let recent = context.historical_performance[context.historical_performance.len() - 1];
let previous = context.historical_performance[context.historical_performance.len() - 2];
let drop = (previous - recent).max(T::zero());
(drop / self.threshold).min(T::one())
} else {
scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero())
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> ResourceConstraintTrigger<T> {
pub fn new() -> Self {
Self {
memory_threshold: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
cpu_threshold: scirs2_core::numeric::NumCast::from(0.95).unwrap_or_else(|| T::zero()),
gpu_threshold: scirs2_core::numeric::NumCast::from(0.9).unwrap_or_else(|| T::zero()),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> AdaptationTrigger<T> for ResourceConstraintTrigger<T> {
fn is_triggered(&self, context: &OptimizationContext<T>) -> bool {
context.computational_budget.max_memory > 8192 * 1024 * 1024 }
fn trigger_type(&self) -> AdaptationType {
AdaptationType::ResourceAllocation
}
fn name(&self) -> &str {
"ResourceConstraintTrigger"
}
}
impl<T: Float + Debug + Send + Sync + 'static> ConvergenceStagnationTrigger<T> {
pub fn new(stagnation_threshold: T) -> Self {
Self {
stagnation_threshold,
check_iterations: 20,
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> AdaptationTrigger<T> for ConvergenceStagnationTrigger<T> {
fn is_triggered(&self, context: &OptimizationContext<T>) -> bool {
if context.historical_performance.len() < self.check_iterations {
return false;
}
let history = &context.historical_performance;
let recent_slice = &history[history.len() - self.check_iterations..];
let max_change = recent_slice.windows(2)
.map(|window| (window[1] - window[0]).abs())
.fold(T::zero(), |acc, x| acc.max(x));
max_change < self.stagnation_threshold
}
fn trigger_type(&self) -> AdaptationType {
AdaptationType::OptimizerSelection
}
fn name(&self) -> &str {
"ConvergenceStagnationTrigger"
}
}
impl<T: Float + Debug + Send + Sync + 'static> LearningRateAdaptationStrategy<T> {
pub fn new() -> Self {
Self {
adaptation_factor: scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()),
min_learning_rate: scirs2_core::numeric::NumCast::from(1e-6).unwrap_or_else(|| T::zero()),
max_learning_rate: scirs2_core::numeric::NumCast::from(1.0).unwrap_or_else(|| T::zero()),
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> AdaptationStrategy<T> for LearningRateAdaptationStrategy<T> {
fn execute_adaptation(
&mut self,
context: &OptimizationContext<T>,
_trigger_metadata: &HashMap<String, String>,
) -> Result<AdaptationResult<T>> {
let improvement = self.adaptation_factor * scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero());
let mut configuration_changes = HashMap::new();
configuration_changes.insert(
"learning_rate_adaptation".to_string(),
"applied".to_string(),
);
Ok(AdaptationResult {
success: true,
performance_improvement: improvement,
new_parameters: None,
configuration_changes,
duration: Duration::from_millis(50),
metadata: HashMap::new(),
})
}
fn estimate_improvement(&self, _context: &OptimizationContext<T>) -> Result<T> {
Ok(scirs2_core::numeric::NumCast::from(0.05).unwrap_or_else(|| T::zero()))
}
fn can_apply(&self, _context: &OptimizationContext<T>) -> bool {
true
}
fn strategy_name(&self) -> &str {
"LearningRateAdaptationStrategy"
}
}
impl<T: Float + Debug + Send + Sync + 'static> OptimizerSelectionStrategy<T> {
pub fn new() -> Self {
Self {
available_optimizers: vec![
"adam".to_string(),
"sgd".to_string(),
"lbfgs".to_string(),
],
selection_criteria: SelectionCriteria {
performance_weight: scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero()),
efficiency_weight: scirs2_core::numeric::NumCast::from(0.3).unwrap_or_else(|| T::zero()),
robustness_weight: scirs2_core::numeric::NumCast::from(0.2).unwrap_or_else(|| T::zero()),
},
}
}
}
impl<T: Float + Debug + Send + Sync + 'static> AdaptationStrategy<T> for OptimizerSelectionStrategy<T> {
fn execute_adaptation(
&mut self,
_context: &OptimizationContext<T>,
_trigger_metadata: &HashMap<String, String>,
) -> Result<AdaptationResult<T>> {
let improvement = scirs2_core::numeric::NumCast::from(0.08).unwrap_or_else(|| T::zero());
let mut configuration_changes = HashMap::new();
configuration_changes.insert(
"optimizer_selection".to_string(),
"switched_optimizer".to_string(),
);
Ok(AdaptationResult {
success: true,
performance_improvement: improvement,
new_parameters: None,
configuration_changes,
duration: Duration::from_millis(100),
metadata: HashMap::new(),
})
}
fn estimate_improvement(&self, _context: &OptimizationContext<T>) -> Result<T> {
Ok(scirs2_core::numeric::NumCast::from(0.08).unwrap_or_else(|| T::zero()))
}
fn can_apply(&self, _context: &OptimizationContext<T>) -> bool {
true
}
fn strategy_name(&self) -> &str {
"OptimizerSelectionStrategy"
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_adaptation_controller_creation() {
let config = AdaptationConfig::<f32>::default();
let controller = AdaptationController::new(config);
assert_eq!(controller.triggers.len(), 0);
assert_eq!(controller.adaptation_strategies.len(), 0);
}
#[test]
fn test_performance_degradation_trigger() {
let trigger = PerformanceDegradationTrigger::new(0.1f32);
let mut context = OptimizationContext::default();
assert!(!trigger.is_triggered(&context));
context.historical_performance = vec![1.0, 0.8, 0.6, 0.4, 0.2];
assert!(trigger.is_triggered(&context));
}
#[test]
fn test_adaptation_statistics() {
let config = AdaptationConfig::<f32>::default();
let controller = AdaptationController::new(config);
let stats = controller.get_adaptation_statistics();
assert_eq!(stats.total_adaptations, 0);
assert_eq!(stats.successful_adaptations, 0);
assert_eq!(stats.success_rate, 0.0);
}
}