#[allow(dead_code)]
use crate::error::Result;
use crate::optimizers::*;
use crate::schedulers::*;
use scirs2_core::ndarray::{Array, Dimension, ScalarOperand};
use scirs2_core::numeric::Float;
use scirs2_core::random::{thread_rng, Rng};
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct SelfTuningConfig {
pub evaluation_window: usize,
pub improvement_threshold: f64,
pub max_switches_per_epoch: usize,
pub auto_lr_adjustment: bool,
pub auto_optimizer_selection: bool,
pub auto_batch_size_tuning: bool,
pub warmup_steps: usize,
pub exploration_rate: f64,
pub exploration_decay: f64,
pub target_metric: TargetMetric,
}
impl Default for SelfTuningConfig {
fn default() -> Self {
Self {
evaluation_window: 100,
improvement_threshold: 0.01,
max_switches_per_epoch: 3,
auto_lr_adjustment: true,
auto_optimizer_selection: true,
auto_batch_size_tuning: false,
warmup_steps: 1000,
exploration_rate: 0.1,
exploration_decay: 0.99,
target_metric: TargetMetric::Loss,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum TargetMetric {
Loss,
Accuracy,
ConvergenceTime,
Throughput,
Custom,
}
#[derive(Debug, Clone)]
pub struct PerformanceStats {
pub loss: f64,
pub accuracy: Option<f64>,
pub gradient_norm: f64,
pub throughput: f64,
pub memory_usage: f64,
pub step_time: Duration,
pub learning_rate: f64,
pub optimizer_type: String,
pub custom_metrics: HashMap<String, f64>,
}
pub struct SelfTuningOptimizer<A: Float, D: Dimension> {
config: SelfTuningConfig,
current_optimizer: Box<dyn OptimizerTrait<A, D>>,
optimizer_candidates: Vec<OptimizerCandidate<A, D>>,
performance_history: VecDeque<PerformanceStats>,
search_state: HyperparameterSearchState,
lr_scheduler: Option<Box<dyn LearningRateScheduler<A>>>,
selection_strategy: OptimizerSelectionStrategy,
step_count: usize,
switches_this_epoch: usize,
best_performance: Option<f64>,
last_adaptation_time: Instant,
bandit_state: BanditState,
}
struct OptimizerCandidate<A: Float, D: Dimension> {
name: String,
factory: Box<dyn Fn() -> Box<dyn OptimizerTrait<A, D>>>,
performance_history: Vec<f64>,
usage_count: usize,
average_performance: f64,
confidence_interval: (f64, f64),
}
#[derive(Debug)]
struct HyperparameterSearchState {
learning_rate: f64,
lr_bounds: (f64, f64),
batch_size: usize,
batch_size_bounds: (usize, usize),
search_iterations: usize,
best_hyperparameters: HashMap<String, f64>,
search_algorithm: SearchAlgorithm,
}
#[derive(Debug)]
enum SearchAlgorithm {
Random {
seed: u64,
},
Bayesian {
gp_state: GaussianProcessState,
},
Grid {
position: Vec<usize>,
dimensions: Vec<usize>,
},
SuccessiveHalving {
bracket: usize,
configurations: Vec<HashMap<String, f64>>,
},
}
#[derive(Debug)]
struct GaussianProcessState {
observed_points: Vec<Vec<f64>>,
observed_values: Vec<f64>,
kernel_params: Vec<f64>,
acquisition_function: AcquisitionFunction,
}
#[derive(Debug, Clone, Copy)]
enum AcquisitionFunction {
ExpectedImprovement,
ProbabilityOfImprovement,
UpperConfidenceBound,
}
#[derive(Debug, Clone)]
enum OptimizerSelectionStrategy {
MultiArmedBandit {
algorithm: BanditAlgorithm,
},
PerformanceBased {
min_difference: f64,
},
RoundRobin {
current_index: usize,
},
MetaLearning {
problem_features: Vec<f64>,
optimizer_mappings: HashMap<String, f64>,
},
}
#[derive(Debug, Clone, Copy)]
enum BanditAlgorithm {
EpsilonGreedy,
UCB1,
ThompsonSampling,
LinUCB,
}
#[derive(Debug)]
struct BanditState {
reward_estimates: Vec<f64>,
confidence_bounds: Vec<f64>,
selection_counts: Vec<usize>,
total_selections: usize,
exploration_param: f64,
}
pub trait OptimizerTrait<A: Float + ScalarOperand + Debug, D: Dimension>: Send + Sync {
fn name(&self) -> &str;
fn step(&mut self, params: &mut [Array<A, D>], grads: &[Array<A, D>]) -> Result<()>;
fn learning_rate(&self) -> A;
fn set_learning_rate(&mut self, lr: A);
fn get_state(&self) -> HashMap<String, Vec<u8>>;
fn set_state(&mut self, state: HashMap<String, Vec<u8>>) -> Result<()>;
fn clone_optimizer(&self) -> Box<dyn OptimizerTrait<A, D>>;
}
impl<
A: Float + ScalarOperand + Debug + Send + Sync + 'static + scirs2_core::numeric::FromPrimitive,
D: Dimension + 'static,
> SelfTuningOptimizer<A, D>
{
pub fn new(config: SelfTuningConfig) -> Result<Self> {
let mut optimizer_candidates = Vec::new();
optimizer_candidates.push(OptimizerCandidate {
name: "Adam".to_string(),
factory: Box::new(|| Box::new(AdamOptimizerWrapper::new(0.001, 0.9, 0.999, 1e-8, 0.0))),
performance_history: Vec::new(),
usage_count: 0,
average_performance: 0.0,
confidence_interval: (0.0, 0.0),
});
optimizer_candidates.push(OptimizerCandidate {
name: "SGD".to_string(),
factory: Box::new(|| Box::new(SGDOptimizerWrapper::new(0.01, 0.9, 0.0, false))),
performance_history: Vec::new(),
usage_count: 0,
average_performance: 0.0,
confidence_interval: (0.0, 0.0),
});
optimizer_candidates.push(OptimizerCandidate {
name: "AdamW".to_string(),
factory: Box::new(|| {
Box::new(AdamWOptimizerWrapper::new(0.001, 0.9, 0.999, 1e-8, 0.01))
}),
performance_history: Vec::new(),
usage_count: 0,
average_performance: 0.0,
confidence_interval: (0.0, 0.0),
});
let current_optimizer = (optimizer_candidates[0].factory)();
let search_state = HyperparameterSearchState {
learning_rate: 0.001,
lr_bounds: (1e-6, 1.0),
batch_size: 32,
batch_size_bounds: (8, 512),
search_iterations: 0,
best_hyperparameters: HashMap::new(),
search_algorithm: SearchAlgorithm::Random { seed: 42 },
};
let selection_strategy = OptimizerSelectionStrategy::MultiArmedBandit {
algorithm: BanditAlgorithm::UCB1,
};
let bandit_state = BanditState {
reward_estimates: vec![0.0; optimizer_candidates.len()],
confidence_bounds: vec![1.0; optimizer_candidates.len()],
selection_counts: vec![0; optimizer_candidates.len()],
total_selections: 0,
exploration_param: 2.0,
};
Ok(Self {
config,
current_optimizer,
optimizer_candidates,
performance_history: VecDeque::new(),
search_state,
lr_scheduler: None,
selection_strategy,
step_count: 0,
switches_this_epoch: 0,
best_performance: None,
last_adaptation_time: Instant::now(),
bandit_state,
})
}
pub fn add_optimizer_candidate<F>(&mut self, name: String, factory: F)
where
F: Fn() -> Box<dyn OptimizerTrait<A, D>> + 'static,
{
self.optimizer_candidates.push(OptimizerCandidate {
name,
factory: Box::new(factory),
performance_history: Vec::new(),
usage_count: 0,
average_performance: 0.0,
confidence_interval: (0.0, 0.0),
});
self.bandit_state.reward_estimates.push(0.0);
self.bandit_state.confidence_bounds.push(1.0);
self.bandit_state.selection_counts.push(0);
}
pub fn step(
&mut self,
params: &mut [Array<A, D>],
grads: &[Array<A, D>],
stats: PerformanceStats,
) -> Result<()> {
self.step_count += 1;
self.performance_history.push_back(stats.clone());
if self.performance_history.len() > self.config.evaluation_window {
self.performance_history.pop_front();
}
self.current_optimizer.step(params, grads)?;
if self.step_count > self.config.warmup_steps {
self.maybe_adapt_optimizer(&stats)?;
self.maybe_adapt_learning_rate(&stats)?;
self.maybe_adapt_hyperparameters(&stats)?;
}
let current_performance = self.extract_performance_metric(&stats);
if let Some(performance) = current_performance {
if self.best_performance.is_none()
|| self.is_better_performance(
performance,
self.best_performance.expect("unwrap failed"),
)
{
self.best_performance = Some(performance);
}
}
Ok(())
}
fn maybe_adapt_optimizer(&mut self, stats: &PerformanceStats) -> Result<()> {
if !self.config.auto_optimizer_selection {
return Ok(());
}
if self.switches_this_epoch >= self.config.max_switches_per_epoch {
return Ok(());
}
let should_adapt = self.should_adapt_optimizer(stats);
if should_adapt {
self.adapt_optimizer(stats)?;
self.switches_this_epoch += 1;
}
Ok(())
}
fn should_adapt_optimizer(&self, stats: &PerformanceStats) -> bool {
if self.performance_history.len() < self.config.evaluation_window / 2 {
return false;
}
let recent_performance: Vec<f64> = self
.performance_history
.iter()
.rev()
.take(self.config.evaluation_window / 4)
.filter_map(|s| self.extract_performance_metric(s))
.collect();
let older_performance: Vec<f64> = self
.performance_history
.iter()
.rev()
.skip(self.config.evaluation_window / 4)
.take(self.config.evaluation_window / 4)
.filter_map(|s| self.extract_performance_metric(s))
.collect();
if recent_performance.is_empty() || older_performance.is_empty() {
return false;
}
let recent_avg = recent_performance.iter().sum::<f64>() / recent_performance.len() as f64;
let older_avg = older_performance.iter().sum::<f64>() / older_performance.len() as f64;
match self.config.target_metric {
TargetMetric::Loss => {
(recent_avg - older_avg).abs() < self.config.improvement_threshold
|| recent_avg > older_avg
}
TargetMetric::Accuracy | TargetMetric::Throughput => {
(recent_avg - older_avg).abs() < self.config.improvement_threshold
|| recent_avg < older_avg
}
_ => false,
}
}
fn adapt_optimizer(&mut self, stats: &PerformanceStats) -> Result<()> {
let new_optimizer_idx = match &self.selection_strategy {
OptimizerSelectionStrategy::MultiArmedBandit { algorithm } => {
self.select_optimizer_bandit(*algorithm)
}
OptimizerSelectionStrategy::PerformanceBased { .. } => {
self.select_optimizer_performance_based()
}
OptimizerSelectionStrategy::RoundRobin { current_index } => {
(*current_index + 1) % self.optimizer_candidates.len()
}
OptimizerSelectionStrategy::MetaLearning { .. } => {
self.select_optimizer_meta_learning(stats)
}
};
if new_optimizer_idx < self.optimizer_candidates.len() {
let current_lr = self.current_optimizer.learning_rate();
let current_state = self.current_optimizer.get_state();
self.current_optimizer = (self.optimizer_candidates[new_optimizer_idx].factory)();
self.current_optimizer.set_learning_rate(current_lr);
if self.current_optimizer.set_state(current_state).is_err() {
}
self.optimizer_candidates[new_optimizer_idx].usage_count += 1;
}
Ok(())
}
fn select_optimizer_bandit(&mut self, algorithm: BanditAlgorithm) -> usize {
match algorithm {
BanditAlgorithm::UCB1 => self.select_ucb1(),
BanditAlgorithm::EpsilonGreedy => self.select_epsilon_greedy(),
BanditAlgorithm::ThompsonSampling => self.select_thompson_sampling(),
BanditAlgorithm::LinUCB => self.select_linucb(),
}
}
fn select_ucb1(&self) -> usize {
if self.bandit_state.total_selections == 0 {
return 0;
}
let mut best_score = f64::NEG_INFINITY;
let mut best_idx = 0;
for (i, candidate) in self.optimizer_candidates.iter().enumerate() {
let ucb_score = if self.bandit_state.selection_counts[i] == 0 {
f64::INFINITY
} else {
let mean_reward = self.bandit_state.reward_estimates[i];
let confidence = (self.bandit_state.exploration_param
* (self.bandit_state.total_selections as f64).ln()
/ self.bandit_state.selection_counts[i] as f64)
.sqrt();
mean_reward + confidence
};
if ucb_score > best_score {
best_score = ucb_score;
best_idx = i;
}
}
best_idx
}
fn select_epsilon_greedy(&self) -> usize {
let mut rng = thread_rng();
if A::from(rng.random::<f64>()).expect("unwrap failed")
< A::from(self.config.exploration_rate).expect("unwrap failed")
{
rng.gen_range(0..self.optimizer_candidates.len())
} else {
self.bandit_state
.reward_estimates
.iter()
.enumerate()
.max_by(|a, b| a.1.partial_cmp(b.1).expect("unwrap failed"))
.map(|(idx, _)| idx)
.unwrap_or(0)
}
}
fn select_thompson_sampling(&self) -> usize {
let mut rng = thread_rng();
let mut best_sample = f64::NEG_INFINITY;
let mut best_idx = 0;
for (i, _) in self.optimizer_candidates.iter().enumerate() {
let mean = self.bandit_state.reward_estimates[i];
let std = self.bandit_state.confidence_bounds[i];
let sample = rng.gen_range(mean - std..mean + std);
if sample > best_sample {
best_sample = sample;
best_idx = i;
}
}
best_idx
}
fn select_linucb(&self) -> usize {
self.select_ucb1()
}
fn select_optimizer_performance_based(&self) -> usize {
self.optimizer_candidates
.iter()
.enumerate()
.max_by(|a, b| {
a.1.average_performance
.partial_cmp(&b.1.average_performance)
.expect("unwrap failed")
})
.map(|(idx, _)| idx)
.unwrap_or(0)
}
fn select_optimizer_meta_learning(&self, stats: &PerformanceStats) -> usize {
0
}
fn maybe_adapt_learning_rate(&mut self, stats: &PerformanceStats) -> Result<()> {
if !self.config.auto_lr_adjustment {
return Ok(());
}
let current_lr = self
.current_optimizer
.learning_rate()
.to_f64()
.expect("unwrap failed");
let gradient_norm = stats.gradient_norm;
let new_lr = if gradient_norm > 10.0 {
current_lr * 0.9
} else if gradient_norm < 0.1 {
current_lr * 1.1
} else {
current_lr
};
let clamped_lr = new_lr
.max(self.search_state.lr_bounds.0)
.min(self.search_state.lr_bounds.1);
if (clamped_lr - current_lr).abs() > current_lr * 0.01 {
self.current_optimizer
.set_learning_rate(A::from(clamped_lr).expect("unwrap failed"));
self.search_state.learning_rate = clamped_lr;
}
Ok(())
}
fn maybe_adapt_hyperparameters(&mut self, stats: &PerformanceStats) -> Result<()> {
Ok(())
}
fn extract_performance_metric(&self, stats: &PerformanceStats) -> Option<f64> {
match self.config.target_metric {
TargetMetric::Loss => Some(stats.loss),
TargetMetric::Accuracy => stats.accuracy,
TargetMetric::Throughput => Some(stats.throughput),
TargetMetric::ConvergenceTime => Some(stats.step_time.as_secs_f64()),
TargetMetric::Custom => stats.custom_metrics.values().next().copied(),
}
}
fn is_better_performance(&self, new_perf: f64, oldperf: f64) -> bool {
match self.config.target_metric {
TargetMetric::Loss | TargetMetric::ConvergenceTime => new_perf < oldperf,
TargetMetric::Accuracy | TargetMetric::Throughput => new_perf > oldperf,
TargetMetric::Custom => new_perf > oldperf, }
}
pub fn reset_epoch(&mut self) {
self.switches_this_epoch = 0;
}
pub fn get_optimizer_info(&self) -> OptimizerInfo {
OptimizerInfo {
name: self.current_optimizer.name().to_string(),
learning_rate: self
.current_optimizer
.learning_rate()
.to_f64()
.expect("unwrap failed"),
step_count: self.step_count,
switches_this_epoch: self.switches_this_epoch,
performance_window_size: self.performance_history.len(),
best_performance: self.best_performance,
}
}
pub fn get_statistics(&self) -> SelfTuningStatistics {
let optimizer_usage: HashMap<String, usize> = self
.optimizer_candidates
.iter()
.map(|c| (c.name.clone(), c.usage_count))
.collect();
SelfTuningStatistics {
total_steps: self.step_count,
total_optimizer_switches: self
.optimizer_candidates
.iter()
.map(|c| c.usage_count)
.sum(),
optimizer_usage,
current_learning_rate: self.search_state.learning_rate,
average_step_time: self
.performance_history
.iter()
.map(|s| s.step_time.as_secs_f64())
.sum::<f64>()
/ self.performance_history.len().max(1) as f64,
exploration_rate: self.config.exploration_rate,
}
}
}
#[derive(Debug, Clone)]
pub struct OptimizerInfo {
pub name: String,
pub learning_rate: f64,
pub step_count: usize,
pub switches_this_epoch: usize,
pub performance_window_size: usize,
pub best_performance: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct SelfTuningStatistics {
pub total_steps: usize,
pub total_optimizer_switches: usize,
pub optimizer_usage: HashMap<String, usize>,
pub current_learning_rate: f64,
pub average_step_time: f64,
pub exploration_rate: f64,
}
struct AdamOptimizerWrapper<A: Float + ScalarOperand + Debug, D: Dimension> {
inner: crate::optimizers::Adam<A>,
_phantom: std::marker::PhantomData<D>,
}
impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
AdamOptimizerWrapper<A, D>
{
fn new(_lr: f64, beta1: f64, beta2: f64, eps: f64, weightdecay: f64) -> Self {
Self {
inner: crate::optimizers::Adam::new_with_config(
A::from(_lr).expect("unwrap failed"),
A::from(beta1).expect("unwrap failed"),
A::from(beta2).expect("unwrap failed"),
A::from(eps).expect("unwrap failed"),
A::from(weightdecay).expect("unwrap failed"),
),
_phantom: std::marker::PhantomData,
}
}
}
impl<A: Float + ScalarOperand + Debug + Send + Sync + 'static, D: Dimension + 'static>
OptimizerTrait<A, D> for AdamOptimizerWrapper<A, D>
{
fn name(&self) -> &str {
"Adam"
}
fn step(&mut self, params: &mut [Array<A, D>], grads: &[Array<A, D>]) -> Result<()> {
if params.len() != grads.len() {
return Err(crate::error::OptimError::InvalidParameter(
"Mismatched number of parameters and gradients".to_string(),
));
}
for (param, grad) in params.iter_mut().zip(grads.iter()) {
let updated = self.inner.step(param, grad)?;
*param = updated;
}
Ok(())
}
fn learning_rate(&self) -> A {
self.inner.learning_rate()
}
fn set_learning_rate(&mut self, lr: A) {
<crate::optimizers::Adam<A> as crate::optimizers::Optimizer<A, D>>::set_learning_rate(
&mut self.inner,
lr,
);
}
fn get_state(&self) -> HashMap<String, Vec<u8>> {
HashMap::new()
}
fn set_state(&mut self, state: HashMap<String, Vec<u8>>) -> Result<()> {
Ok(())
}
fn clone_optimizer(&self) -> Box<dyn OptimizerTrait<A, D>> {
Box::new(AdamOptimizerWrapper {
inner: self.inner.clone(),
_phantom: std::marker::PhantomData,
})
}
}
struct SGDOptimizerWrapper<A: Float + ScalarOperand + Debug, D: Dimension> {
inner: crate::optimizers::SGD<A>,
_phantom: std::marker::PhantomData<D>,
}
impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
SGDOptimizerWrapper<A, D>
{
fn new(_lr: f64, momentum: f64, weightdecay: f64, nesterov: bool) -> Self {
Self {
inner: crate::optimizers::SGD::new_with_config(
A::from(_lr).expect("unwrap failed"),
A::from(momentum).expect("unwrap failed"),
A::from(weightdecay).expect("unwrap failed"),
),
_phantom: std::marker::PhantomData,
}
}
}
impl<A: Float + ScalarOperand + Debug + Send + Sync + 'static, D: Dimension + 'static>
OptimizerTrait<A, D> for SGDOptimizerWrapper<A, D>
{
fn name(&self) -> &str {
"SGD"
}
fn step(&mut self, params: &mut [Array<A, D>], grads: &[Array<A, D>]) -> Result<()> {
if params.len() != grads.len() {
return Err(crate::error::OptimError::InvalidParameter(
"Mismatched number of parameters and gradients".to_string(),
));
}
for (param, grad) in params.iter_mut().zip(grads.iter()) {
let updated = self.inner.step(param, grad)?;
*param = updated;
}
Ok(())
}
fn learning_rate(&self) -> A {
self.inner.learning_rate()
}
fn set_learning_rate(&mut self, lr: A) {
<crate::optimizers::SGD<A> as crate::optimizers::Optimizer<A, D>>::set_learning_rate(
&mut self.inner,
lr,
);
}
fn get_state(&self) -> HashMap<String, Vec<u8>> {
HashMap::new()
}
fn set_state(&mut self, state: HashMap<String, Vec<u8>>) -> Result<()> {
Ok(())
}
fn clone_optimizer(&self) -> Box<dyn OptimizerTrait<A, D>> {
Box::new(SGDOptimizerWrapper {
inner: self.inner.clone(),
_phantom: std::marker::PhantomData,
})
}
}
struct AdamWOptimizerWrapper<A: Float + ScalarOperand + Debug, D: Dimension> {
inner: crate::optimizers::AdamW<A>,
_phantom: std::marker::PhantomData<D>,
}
impl<A: Float + ScalarOperand + Debug + Send + Sync, D: Dimension + Send + Sync>
AdamWOptimizerWrapper<A, D>
{
fn new(_lr: f64, beta1: f64, beta2: f64, eps: f64, weightdecay: f64) -> Self {
Self {
inner: crate::optimizers::AdamW::new_with_config(
A::from(_lr).expect("unwrap failed"),
A::from(beta1).expect("unwrap failed"),
A::from(beta2).expect("unwrap failed"),
A::from(eps).expect("unwrap failed"),
A::from(weightdecay).expect("unwrap failed"),
),
_phantom: std::marker::PhantomData,
}
}
}
impl<A: Float + ScalarOperand + Debug + Send + Sync + 'static, D: Dimension + 'static>
OptimizerTrait<A, D> for AdamWOptimizerWrapper<A, D>
{
fn name(&self) -> &str {
"AdamW"
}
fn step(&mut self, params: &mut [Array<A, D>], grads: &[Array<A, D>]) -> Result<()> {
if params.len() != grads.len() {
return Err(crate::error::OptimError::InvalidParameter(
"Mismatched number of parameters and gradients".to_string(),
));
}
for (param, grad) in params.iter_mut().zip(grads.iter()) {
let updated = self.inner.step(param, grad)?;
*param = updated;
}
Ok(())
}
fn learning_rate(&self) -> A {
self.inner.learning_rate()
}
fn set_learning_rate(&mut self, lr: A) {
<crate::optimizers::AdamW<A> as crate::optimizers::Optimizer<A, D>>::set_learning_rate(
&mut self.inner,
lr,
);
}
fn get_state(&self) -> HashMap<String, Vec<u8>> {
HashMap::new()
}
fn set_state(&mut self, state: HashMap<String, Vec<u8>>) -> Result<()> {
Ok(())
}
fn clone_optimizer(&self) -> Box<dyn OptimizerTrait<A, D>> {
Box::new(AdamWOptimizerWrapper {
inner: self.inner.clone(),
_phantom: std::marker::PhantomData,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::Array1;
use std::time::Duration;
#[test]
fn test_self_tuning_config_default() {
let config = SelfTuningConfig::default();
assert_eq!(config.evaluation_window, 100);
assert!(config.auto_lr_adjustment);
assert!(config.auto_optimizer_selection);
}
#[test]
fn test_self_tuning_optimizer_creation() {
let config = SelfTuningConfig::default();
let optimizer: Result<SelfTuningOptimizer<f64, scirs2_core::ndarray::Ix1>> =
SelfTuningOptimizer::new(config);
assert!(optimizer.is_ok());
}
#[test]
fn test_performance_stats() {
let stats = PerformanceStats {
loss: 0.5,
accuracy: Some(0.9),
gradient_norm: 1.2,
throughput: 100.0,
memory_usage: 1024.0,
step_time: Duration::from_millis(50),
learning_rate: 0.001,
optimizer_type: "Adam".to_string(),
custom_metrics: HashMap::new(),
};
assert_eq!(stats.loss, 0.5);
assert_eq!(stats.accuracy, Some(0.9));
}
#[test]
fn test_optimizer_step() {
let config = SelfTuningConfig::default();
let mut optimizer: SelfTuningOptimizer<f64, scirs2_core::ndarray::Ix1> =
SelfTuningOptimizer::new(config).expect("unwrap failed");
let mut params = vec![Array1::zeros(10)];
let grads = vec![Array1::ones(10)];
let stats = PerformanceStats {
loss: 1.0,
accuracy: None,
gradient_norm: 1.0,
throughput: 50.0,
memory_usage: 512.0,
step_time: Duration::from_millis(10),
learning_rate: 0.001,
optimizer_type: "Adam".to_string(),
custom_metrics: HashMap::new(),
};
let result = optimizer.step(&mut params, &grads, stats);
assert!(result.is_ok());
let info = optimizer.get_optimizer_info();
assert_eq!(info.name, "Adam");
assert_eq!(info.step_count, 1);
}
#[test]
fn test_bandit_selection() {
let config = SelfTuningConfig::default();
let optimizer: SelfTuningOptimizer<f64, scirs2_core::ndarray::Ix1> =
SelfTuningOptimizer::new(config).expect("unwrap failed");
let selection = optimizer.select_ucb1();
assert!(selection < optimizer.optimizer_candidates.len());
}
#[test]
fn test_performance_metric_extraction() {
let config = SelfTuningConfig {
target_metric: TargetMetric::Loss,
..Default::default()
};
let optimizer: SelfTuningOptimizer<f64, scirs2_core::ndarray::Ix1> =
SelfTuningOptimizer::new(config).expect("unwrap failed");
let stats = PerformanceStats {
loss: 0.8,
accuracy: Some(0.85),
gradient_norm: 1.1,
throughput: 75.0,
memory_usage: 800.0,
step_time: Duration::from_millis(20),
learning_rate: 0.001,
optimizer_type: "Adam".to_string(),
custom_metrics: HashMap::new(),
};
let metric = optimizer.extract_performance_metric(&stats);
assert_eq!(metric, Some(0.8));
}
#[test]
fn test_statistics() {
let config = SelfTuningConfig::default();
let optimizer: SelfTuningOptimizer<f64, scirs2_core::ndarray::Ix1> =
SelfTuningOptimizer::new(config).expect("unwrap failed");
let stats = optimizer.get_statistics();
assert_eq!(stats.total_steps, 0);
assert!(stats.optimizer_usage.contains_key("Adam"));
}
}