use super::{
LearnedOptimizationConfig, LearnedOptimizer, MetaOptimizerState, OptimizationProblem,
TrainingTask,
};
use crate::error::OptimizeResult;
use crate::result::OptimizeResults;
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use scirs2_core::random::{Rng, RngExt};
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct MetaLearningOptimizer {
config: LearnedOptimizationConfig,
meta_state: MetaOptimizerState,
task_optimizers: HashMap<String, TaskSpecificOptimizer>,
meta_stats: MetaLearningStats,
}
#[derive(Debug, Clone)]
pub struct TaskSpecificOptimizer {
parameters: Array1<f64>,
performance_history: Vec<f64>,
task_id: String,
}
#[derive(Debug, Clone)]
pub struct MetaLearningStats {
tasks_learned: usize,
avg_adaptation_speed: f64,
transfer_efficiency: f64,
meta_gradient_norm: f64,
}
impl MetaLearningOptimizer {
pub fn new(config: LearnedOptimizationConfig) -> Self {
let hidden_size = config.hidden_size;
Self {
config,
meta_state: MetaOptimizerState {
meta_params: Array1::zeros(hidden_size),
network_weights: Array2::zeros((hidden_size, hidden_size)),
performance_history: Vec::new(),
adaptation_stats: super::AdaptationStatistics::default(),
episode: 0,
},
task_optimizers: HashMap::new(),
meta_stats: MetaLearningStats::default(),
}
}
pub fn learn_meta_strategy(&mut self, training_tasks: &[TrainingTask]) -> OptimizeResult<()> {
for task in training_tasks {
let task_optimizer = self.create_task_optimizer(&task.problem)?;
let performance = self.train_on_task(&task_optimizer, task)?;
self.update_meta_parameters(&task.problem, performance)?;
self.task_optimizers
.insert(task.problem.name.clone(), task_optimizer);
}
self.meta_stats.tasks_learned = training_tasks.len();
Ok(())
}
fn create_task_optimizer(
&self,
problem: &OptimizationProblem,
) -> OptimizeResult<TaskSpecificOptimizer> {
let param_size = self.estimate_parameter_size(problem);
Ok(TaskSpecificOptimizer {
parameters: Array1::from_shape_fn(param_size, |_| {
scirs2_core::random::rng().random_range(0.0..0.1)
}),
performance_history: Vec::new(),
task_id: problem.name.clone(),
})
}
fn estimate_parameter_size(&self, problem: &OptimizationProblem) -> usize {
let base_size = 64;
let dimension_factor = (problem.dimension as f64).sqrt() as usize;
match problem.problem_class.as_str() {
"quadratic" => base_size,
"neural_network" => base_size * 2 + dimension_factor,
"sparse" => base_size + dimension_factor / 2,
_ => base_size + dimension_factor,
}
}
fn train_on_task(
&mut self,
optimizer: &TaskSpecificOptimizer,
task: &TrainingTask,
) -> OptimizeResult<f64> {
let initial_params = match &task.initial_distribution {
super::ParameterDistribution::Uniform { low, high } => {
Array1::from_shape_fn(task.problem.dimension, |_| {
low + scirs2_core::random::rng().random_range(0.0..1.0) * (high - low)
})
}
super::ParameterDistribution::Normal { mean, std } => {
Array1::from_shape_fn(task.problem.dimension, |_| {
mean + std * (scirs2_core::random::rng().random_range(0.0..1.0) - 0.5) * 2.0
})
}
super::ParameterDistribution::Custom { samples } => {
if !samples.is_empty() {
samples[0].clone()
} else {
Array1::zeros(task.problem.dimension)
}
}
};
let training_objective = |x: &ArrayView1<f64>| x.iter().map(|&xi| xi * xi).sum::<f64>();
let initial_value = training_objective(&initial_params.view());
let mut current_params = initial_params;
let mut current_value = initial_value;
for _ in 0..self.config.inner_steps {
let direction = self.compute_meta_direction(¤t_params, &training_objective)?;
let step_size = self.compute_meta_step_size(&optimizer.parameters)?;
for i in 0..current_params.len().min(direction.len()) {
current_params[i] -= step_size * direction[i];
}
current_value = training_objective(¤t_params.view());
}
let improvement = initial_value - current_value;
Ok(improvement.max(0.0))
}
fn compute_meta_direction<F>(
&self,
params: &Array1<f64>,
objective: &F,
) -> OptimizeResult<Array1<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let h = 1e-6;
let f0 = objective(¶ms.view());
let mut direction = Array1::zeros(params.len());
for i in 0..params.len() {
let mut params_plus = params.clone();
params_plus[i] += h;
let f_plus = objective(¶ms_plus.view());
direction[i] = (f_plus - f0) / h;
}
self.apply_meta_transformation(&mut direction)?;
Ok(direction)
}
fn apply_meta_transformation(&self, gradient: &mut Array1<f64>) -> OptimizeResult<()> {
for i in 0..gradient.len() {
let meta_idx = i % self.meta_state.meta_params.len();
let meta_factor = self.meta_state.meta_params[meta_idx];
gradient[i] *= 1.0 + meta_factor * 0.1;
}
Ok(())
}
fn compute_meta_step_size(&self, task_params: &Array1<f64>) -> OptimizeResult<f64> {
let mut step_size = self.config.inner_learning_rate;
if !task_params.is_empty() {
let param_norm = (task_params.iter().map(|&x| x * x).sum::<f64>()).sqrt();
step_size *= (1.0 + param_norm * 0.1).recip();
}
if !self.meta_state.meta_params.is_empty() {
let meta_factor = self.meta_state.meta_params[0];
step_size *= (1.0 + meta_factor * 0.2).max(0.1).min(2.0);
}
Ok(step_size)
}
fn update_meta_parameters(
&mut self,
problem: &OptimizationProblem,
performance: f64,
) -> OptimizeResult<()> {
let learning_rate = self.config.meta_learning_rate;
let performance_gradient = if performance > 0.0 { 1.0 } else { -1.0 };
for i in 0..self.meta_state.meta_params.len() {
let update = learning_rate
* performance_gradient
* (scirs2_core::random::rng().random_range(0.0..1.0) - 0.5)
* 0.1;
self.meta_state.meta_params[i] += update;
self.meta_state.meta_params[i] = self.meta_state.meta_params[i].max(-1.0).min(1.0);
}
self.meta_state.performance_history.push(performance);
self.meta_state.adaptation_stats.avg_convergence_rate =
self.meta_state.performance_history.iter().sum::<f64>()
/ self.meta_state.performance_history.len() as f64;
Ok(())
}
pub fn adapt_to_new_problem(
&mut self,
problem: &OptimizationProblem,
) -> OptimizeResult<TaskSpecificOptimizer> {
let similar_task = self.find_most_similar_task(problem)?;
let mut new_optimizer = if let Some(similar_optimizer) = similar_task {
let mut adapted = similar_optimizer.clone();
adapted.task_id = problem.name.clone();
self.adapt_optimizer_parameters(&mut adapted, problem)?;
adapted
} else {
self.create_task_optimizer(problem)?
};
self.fine_tune_for_problem(&mut new_optimizer, problem)?;
Ok(new_optimizer)
}
fn find_most_similar_task(
&self,
problem: &OptimizationProblem,
) -> OptimizeResult<Option<&TaskSpecificOptimizer>> {
let mut best_similarity = 0.0;
let mut best_optimizer = None;
for (task_name, optimizer) in &self.task_optimizers {
let similarity = self.compute_task_similarity(problem, task_name)?;
if similarity > best_similarity {
best_similarity = similarity;
best_optimizer = Some(optimizer);
}
}
if best_similarity > 0.5 {
Ok(best_optimizer)
} else {
Ok(None)
}
}
fn compute_task_similarity(
&self,
problem: &OptimizationProblem,
task_name: &str,
) -> OptimizeResult<f64> {
let similarity = if task_name.contains(&problem.problem_class) {
0.8
} else {
0.2
};
let dim_factor = 1.0 / (1.0 + (problem.dimension as f64 - 100.0).abs() / 100.0);
Ok(similarity * dim_factor)
}
fn adapt_optimizer_parameters(
&self,
optimizer: &mut TaskSpecificOptimizer,
problem: &OptimizationProblem,
) -> OptimizeResult<()> {
let adaptation_factor = match problem.problem_class.as_str() {
"quadratic" => 1.0,
"neural_network" => 1.2,
"sparse" => 0.8,
_ => 1.0,
};
for param in &mut optimizer.parameters {
*param *= adaptation_factor;
}
Ok(())
}
fn fine_tune_for_problem(
&mut self,
optimizer: &mut TaskSpecificOptimizer,
problem: &OptimizationProblem,
) -> OptimizeResult<()> {
let meta_influence = 0.1;
for (i, param) in optimizer.parameters.iter_mut().enumerate() {
let meta_idx = i % self.meta_state.meta_params.len();
let meta_adjustment = self.meta_state.meta_params[meta_idx] * meta_influence;
*param += meta_adjustment;
}
Ok(())
}
pub fn get_meta_stats(&self) -> &MetaLearningStats {
&self.meta_stats
}
fn update_meta_stats(&mut self) {
if !self.meta_state.performance_history.is_empty() {
let recent_improvements: Vec<f64> = self
.meta_state
.performance_history
.windows(2)
.map(|w| w[1] - w[0])
.collect();
if !recent_improvements.is_empty() {
self.meta_stats.avg_adaptation_speed = recent_improvements
.iter()
.map(|&x| if x > 0.0 { 1.0 } else { 0.0 })
.sum::<f64>()
/ recent_improvements.len() as f64;
}
}
self.meta_stats.transfer_efficiency = if self.meta_stats.tasks_learned > 1 {
self.meta_stats.avg_adaptation_speed / self.meta_stats.tasks_learned as f64
} else {
0.0
};
self.meta_stats.meta_gradient_norm = (self
.meta_state
.meta_params
.iter()
.map(|&x| x * x)
.sum::<f64>())
.sqrt();
}
}
impl Default for MetaLearningStats {
fn default() -> Self {
Self {
tasks_learned: 0,
avg_adaptation_speed: 0.0,
transfer_efficiency: 0.0,
meta_gradient_norm: 0.0,
}
}
}
impl LearnedOptimizer for MetaLearningOptimizer {
fn meta_train(&mut self, training_tasks: &[TrainingTask]) -> OptimizeResult<()> {
self.learn_meta_strategy(training_tasks)?;
self.update_meta_stats();
Ok(())
}
fn adapt_to_problem(
&mut self,
problem: &OptimizationProblem,
initial_params: &ArrayView1<f64>,
) -> OptimizeResult<()> {
let adapted_optimizer = self.adapt_to_new_problem(problem)?;
self.task_optimizers
.insert(problem.name.clone(), adapted_optimizer);
Ok(())
}
fn optimize<F>(
&mut self,
objective: F,
initial_params: &ArrayView1<f64>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let mut current_params = initial_params.to_owned();
let mut best_value = objective(initial_params);
let mut iterations = 0;
for iter in 0..1000 {
iterations = iter;
let direction = self.compute_meta_direction(¤t_params, &objective)?;
let step_size = if !self.meta_state.meta_params.is_empty() {
let base_step = self.config.inner_learning_rate;
let meta_factor = self.meta_state.meta_params[0];
base_step * (1.0 + meta_factor * 0.1)
} else {
self.config.inner_learning_rate
};
for i in 0..current_params.len().min(direction.len()) {
current_params[i] -= step_size * direction[i];
}
let current_value = objective(¤t_params.view());
if current_value < best_value {
best_value = current_value;
}
if direction
.iter()
.map(|&x| x.abs())
.max_by(|a, b| a.partial_cmp(b).expect("Operation failed"))
.unwrap_or(0.0)
< 1e-8
{
break;
}
}
Ok(OptimizeResults::<f64> {
x: current_params,
fun: best_value,
success: true,
nit: iterations,
message: "Meta-learning optimization completed".to_string(),
jac: None,
hess: None,
constr: None,
nfev: iterations * 10, njev: 0,
nhev: 0,
maxcv: 0,
status: 0,
})
}
fn get_state(&self) -> &MetaOptimizerState {
&self.meta_state
}
fn reset(&mut self) {
self.task_optimizers.clear();
self.meta_stats = MetaLearningStats::default();
self.meta_state.episode = 0;
self.meta_state.performance_history.clear();
}
}
#[allow(dead_code)]
pub fn meta_learning_optimize<F>(
objective: F,
initial_params: &ArrayView1<f64>,
config: Option<LearnedOptimizationConfig>,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&ArrayView1<f64>) -> f64,
{
let config = config.unwrap_or_default();
let mut optimizer = MetaLearningOptimizer::new(config);
optimizer.optimize(objective, initial_params)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_meta_learning_optimizer_creation() {
let config = LearnedOptimizationConfig::default();
let optimizer = MetaLearningOptimizer::new(config);
assert_eq!(optimizer.meta_stats.tasks_learned, 0);
assert!(optimizer.task_optimizers.is_empty());
}
#[test]
fn test_task_optimizer_creation() {
let config = LearnedOptimizationConfig::default();
let optimizer = MetaLearningOptimizer::new(config);
let problem = OptimizationProblem {
name: "test".to_string(),
dimension: 10,
problem_class: "quadratic".to_string(),
metadata: HashMap::new(),
max_evaluations: 1000,
target_accuracy: 1e-6,
};
let task_optimizer = optimizer
.create_task_optimizer(&problem)
.expect("Operation failed");
assert_eq!(task_optimizer.task_id, "test");
assert!(!task_optimizer.parameters.is_empty());
}
#[test]
fn test_meta_direction_computation() {
let config = LearnedOptimizationConfig::default();
let optimizer = MetaLearningOptimizer::new(config);
let params = Array1::from(vec![1.0, 2.0]);
let objective = |x: &ArrayView1<f64>| x[0].powi(2) + x[1].powi(2);
let direction = optimizer
.compute_meta_direction(¶ms, &objective)
.expect("Operation failed");
assert_eq!(direction.len(), 2);
assert!(direction.iter().all(|&x| x.is_finite()));
}
#[test]
fn test_meta_step_size_computation() {
let config = LearnedOptimizationConfig::default();
let mut optimizer = MetaLearningOptimizer::new(config);
optimizer.meta_state.meta_params[0] = 0.5;
let task_params = Array1::from(vec![0.1, 0.2, 0.3]);
let step_size = optimizer
.compute_meta_step_size(&task_params)
.expect("Operation failed");
assert!(step_size > 0.0);
assert!(step_size < 1.0);
}
#[test]
fn test_task_similarity() {
let config = LearnedOptimizationConfig::default();
let optimizer = MetaLearningOptimizer::new(config);
let problem = OptimizationProblem {
name: "test".to_string(),
dimension: 100,
problem_class: "quadratic".to_string(),
metadata: HashMap::new(),
max_evaluations: 1000,
target_accuracy: 1e-6,
};
let similarity1 = optimizer
.compute_task_similarity(&problem, "quadratic_task")
.expect("Operation failed");
let similarity2 = optimizer
.compute_task_similarity(&problem, "neural_network_task")
.expect("Operation failed");
assert!(similarity1 > similarity2);
}
#[test]
fn test_meta_learning_optimization() {
let objective = |x: &ArrayView1<f64>| x[0].powi(2) + x[1].powi(2);
let initial = Array1::from(vec![2.0, 2.0]);
let config = LearnedOptimizationConfig {
inner_steps: 10,
inner_learning_rate: 0.1,
..Default::default()
};
let result = meta_learning_optimize(objective, &initial.view(), Some(config))
.expect("Operation failed");
assert!(result.fun >= 0.0);
assert_eq!(result.x.len(), 2);
assert!(result.success);
}
#[test]
fn test_meta_parameter_update() {
let config = LearnedOptimizationConfig::default();
let mut optimizer = MetaLearningOptimizer::new(config);
let problem = OptimizationProblem {
name: "test".to_string(),
dimension: 5,
problem_class: "quadratic".to_string(),
metadata: HashMap::new(),
max_evaluations: 100,
target_accuracy: 1e-6,
};
let initial_params = optimizer.meta_state.meta_params.clone();
optimizer
.update_meta_parameters(&problem, 1.5)
.expect("Operation failed");
assert!(optimizer.meta_state.meta_params != initial_params);
assert_eq!(optimizer.meta_state.performance_history.len(), 1);
}
}
#[allow(dead_code)]
pub fn placeholder() {
}