use crate::error::Result;
use crate::nas_engine::config::*;
use crate::nas_engine::results::*;
use scirs2_core::numeric::Float;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use super::support::SearchStrategy;
pub(super) struct EvolutionaryStrategy<
T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum,
> {
pub(super) inner: crate::search_strategies::EvolutionarySearch<T>,
pub(super) search_space: SearchSpaceConfig,
pub(super) batch_size: usize,
pub(super) best_score_history: Vec<T>,
pub(super) convergence_patience: usize,
pub(super) early_stopping_enabled: bool,
pub(super) min_generations: usize,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum>
EvolutionaryStrategy<T>
{
pub fn new(config: &NASConfig<T>) -> Result<Self> {
use crate::search_strategies::SearchStrategy as InnerSearchStrategy;
let population_size = config.population_size.max(2);
let convergence_patience = config.early_stopping.patience.max(5);
let mut inner =
crate::search_strategies::EvolutionarySearch::<T>::new(population_size, 0.1, 0.8, 3);
inner.initialize(&config.search_space)?;
let batch_size = if config.population_size > 0 {
config.population_size
} else {
8
};
Ok(Self {
inner,
search_space: config.search_space.clone(),
batch_size,
best_score_history: Vec::new(),
convergence_patience,
early_stopping_enabled: config.early_stopping.enabled,
min_generations: config.early_stopping.min_generations,
})
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum> SearchStrategy<T>
for EvolutionaryStrategy<T>
{
fn generate_candidates(
&mut self,
history: &VecDeque<SearchResult<T>>,
) -> Result<Vec<OptimizerArchitecture<T>>> {
use crate::search_strategies::SearchStrategy as InnerSearchStrategy;
let mut candidates = Vec::with_capacity(self.batch_size);
for _ in 0..self.batch_size {
let architecture = self
.inner
.generate_architecture(&self.search_space, history)?;
candidates.push(architecture);
}
Ok(candidates)
}
fn update_strategy(&mut self, results: &[SearchResult<T>]) -> Result<()> {
use crate::search_strategies::SearchStrategy as InnerSearchStrategy;
if !results.is_empty() {
let best = results
.iter()
.map(|r| r.evaluation_results.overall_score)
.fold(
T::neg_infinity(),
|acc, score| {
if score > acc {
score
} else {
acc
}
},
);
if best > T::neg_infinity() {
self.best_score_history.push(best);
}
}
self.inner.update_with_results(results)
}
fn has_converged(&self) -> bool {
if !self.early_stopping_enabled {
return false;
}
if self.best_score_history.len() < self.min_generations {
return false;
}
let window = self.convergence_patience;
if self.best_score_history.len() <= window {
return false;
}
let len = self.best_score_history.len();
let recent_best = self.best_score_history[len - window..]
.iter()
.fold(T::neg_infinity(), |acc, &s| if s > acc { s } else { acc });
let prior_best = self.best_score_history[..len - window]
.iter()
.fold(T::neg_infinity(), |acc, &s| if s > acc { s } else { acc });
recent_best <= prior_best
}
fn strategy_name(&self) -> &str {
"EvolutionaryStrategy"
}
}
pub(super) struct RandomStrategy<
T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum,
> {
pub(super) inner: crate::search_strategies::RandomSearch<T>,
pub(super) search_space: SearchSpaceConfig,
pub(super) batch_size: usize,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum>
RandomStrategy<T>
{
pub fn new(config: &NASConfig<T>) -> Result<Self> {
use crate::search_strategies::SearchStrategy as InnerSearchStrategy;
let mut inner = crate::search_strategies::RandomSearch::<T>::new(Some(42));
inner.initialize(&config.search_space)?;
let batch_size = if config.population_size > 0 {
config.population_size
} else {
8
};
Ok(Self {
inner,
search_space: config.search_space.clone(),
batch_size,
})
}
pub(super) fn synthesize_from_component_types(&self, index: usize) -> OptimizerArchitecture<T> {
let component_label = self
.search_space
.component_types
.first()
.map(|ct| format!("{:?}", ct))
.unwrap_or_else(|| "Adam".to_string());
OptimizerArchitecture {
components: vec![component_label],
parameters: HashMap::new(),
connections: Vec::new(),
metadata: HashMap::new(),
hyperparameters: HashMap::new(),
architecture_id: format!("random_arch_{}", index),
}
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static + std::iter::Sum> SearchStrategy<T>
for RandomStrategy<T>
{
fn generate_candidates(
&mut self,
history: &VecDeque<SearchResult<T>>,
) -> Result<Vec<OptimizerArchitecture<T>>> {
use crate::search_strategies::SearchStrategy as InnerSearchStrategy;
let mut candidates = Vec::with_capacity(self.batch_size);
for idx in 0..self.batch_size {
if self.search_space.components.is_empty() {
candidates.push(self.synthesize_from_component_types(idx));
} else {
let architecture = self
.inner
.generate_architecture(&self.search_space, history)?;
candidates.push(architecture);
}
}
Ok(candidates)
}
fn update_strategy(&mut self, results: &[SearchResult<T>]) -> Result<()> {
use crate::search_strategies::SearchStrategy as InnerSearchStrategy;
self.inner.update_with_results(results)
}
fn has_converged(&self) -> bool {
false
}
fn strategy_name(&self) -> &str {
"RandomStrategy"
}
}