use std::fmt::Debug;
use super::NASConfig;
#[allow(unused_imports)]
use crate::error::Result;
use scirs2_core::numeric::Float;
use std::collections::HashMap;
use std::time::Duration;
#[derive(Debug, Clone)]
pub struct ProgressiveNAS<T: Float + Debug + Send + Sync + 'static> {
pub current_phase: SearchPhase,
pub progression_strategy: ProgressionStrategy,
pub complexity_scheduler: ComplexityScheduler<T>,
pub architecture_progression: ArchitectureProgression<T>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum SearchPhase {
Initial,
Intermediate,
Advanced,
Final,
}
#[derive(Debug, Clone)]
pub enum ProgressionStrategy {
TimeBased(std::time::Duration),
PerformanceBased(f64),
BudgetBased(usize),
Adaptive,
}
#[derive(Debug, Clone)]
pub struct ComplexityScheduler<T: Float + Debug + Send + Sync + 'static> {
pub current_complexity: T,
pub max_complexity: T,
pub increase_rate: T,
pub strategy: SchedulingStrategy,
}
#[derive(Debug, Clone)]
pub enum SchedulingStrategy {
Linear,
Exponential,
StepWise,
Adaptive,
}
#[derive(Debug, Clone)]
pub struct ArchitectureProgression<T: Float + Debug + Send + Sync + 'static> {
pub history: Vec<ProgressionRecord<T>>,
pub best_per_phase: HashMap<SearchPhase, Vec<String>>,
pub performance_trends: Vec<T>,
}
#[derive(Debug, Clone)]
pub struct ProgressionRecord<T: Float + Debug + Send + Sync + 'static> {
pub phase: SearchPhase,
pub complexity: T,
pub best_performance: T,
pub architectures_evaluated: usize,
pub timestamp: std::time::Instant,
}
impl<T: Float + Debug + Send + Sync + std::iter::Sum> ProgressiveNAS<T> {
pub fn new(config: &NASConfig) -> Result<Self> {
let progression_strategy = match config.search_budget {
budget if budget < 50 => ProgressionStrategy::TimeBased(Duration::from_secs(300)),
budget if budget < 200 => ProgressionStrategy::BudgetBased(budget / 4),
_ => ProgressionStrategy::Adaptive,
};
Ok(Self {
current_phase: SearchPhase::Initial,
progression_strategy,
complexity_scheduler: ComplexityScheduler::new()?,
architecture_progression: ArchitectureProgression::new(),
})
}
pub fn update_search_phase(&mut self, generation: usize) -> Result<()> {
match self.progression_strategy {
ProgressionStrategy::BudgetBased(budget_per_phase) => {
let phase_index = generation / budget_per_phase;
self.current_phase = match phase_index {
0 => SearchPhase::Initial,
1 => SearchPhase::Intermediate,
2 => SearchPhase::Advanced,
_ => SearchPhase::Final,
};
}
ProgressionStrategy::Adaptive => {
if let Some(trend) = self.analyze_performance_trend() {
if trend
< scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero())
&& self.current_phase != SearchPhase::Final
{
self.advance_phase();
}
}
}
ProgressionStrategy::TimeBased(duration_per_phase) => {
let elapsed = self
.architecture_progression
.history
.first()
.map(|first| first.timestamp.elapsed())
.unwrap_or(Duration::from_secs(0));
let phases_elapsed = elapsed.as_secs() / duration_per_phase.as_secs();
self.current_phase = match phases_elapsed {
0 => SearchPhase::Initial,
1 => SearchPhase::Intermediate,
2 => SearchPhase::Advanced,
_ => SearchPhase::Final,
};
}
ProgressionStrategy::PerformanceBased(threshold) => {
if let Some(latest_performance) =
self.architecture_progression.performance_trends.last()
{
if *latest_performance
> scirs2_core::numeric::NumCast::from(threshold)
.unwrap_or_else(|| T::zero())
&& self.current_phase != SearchPhase::Final
{
self.advance_phase();
}
}
}
}
Ok(())
}
fn advance_phase(&mut self) {
self.current_phase = match self.current_phase {
SearchPhase::Initial => SearchPhase::Intermediate,
SearchPhase::Intermediate => SearchPhase::Advanced,
SearchPhase::Advanced => SearchPhase::Final,
SearchPhase::Final => SearchPhase::Final,
};
}
fn analyze_performance_trend(&self) -> Option<T> {
if self.architecture_progression.performance_trends.len() < 10 {
return None;
}
let recent_trends = &self.architecture_progression.performance_trends
[self.architecture_progression.performance_trends.len() - 10..];
let n = T::from(recent_trends.len()).expect("unwrap failed");
let sum_x = n * (n - T::one())
/ scirs2_core::numeric::NumCast::from(2).unwrap_or_else(|| T::zero());
let sum_y = recent_trends.iter().cloned().sum::<T>();
let sum_xy = recent_trends
.iter()
.enumerate()
.map(|(i, &y)| scirs2_core::numeric::NumCast::from(i).unwrap_or_else(|| T::zero()) * y)
.sum::<T>();
let sum_x2 = recent_trends
.iter()
.enumerate()
.map(|(i, _)| scirs2_core::numeric::NumCast::from(i * i).unwrap_or_else(|| T::zero()))
.sum::<T>();
let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
Some(slope)
}
pub fn get_current_search_config(&self) -> SearchPhaseConfig<T> {
match self.current_phase {
SearchPhase::Initial => SearchPhaseConfig {
complexity_limit: scirs2_core::numeric::NumCast::from(0.25)
.unwrap_or_else(|| T::zero()),
max_components: 2,
max_depth: 3,
exploration_factor: scirs2_core::numeric::NumCast::from(0.8)
.unwrap_or_else(|| T::zero()),
mutation_rate: scirs2_core::numeric::NumCast::from(0.3)
.unwrap_or_else(|| T::zero()),
population_diversity_weight: scirs2_core::numeric::NumCast::from(0.7)
.unwrap_or_else(|| T::zero()),
conservative_search: true,
},
SearchPhase::Intermediate => SearchPhaseConfig {
complexity_limit: scirs2_core::numeric::NumCast::from(0.5)
.unwrap_or_else(|| T::zero()),
max_components: 4,
max_depth: 5,
exploration_factor: scirs2_core::numeric::NumCast::from(0.6)
.unwrap_or_else(|| T::zero()),
mutation_rate: scirs2_core::numeric::NumCast::from(0.2)
.unwrap_or_else(|| T::zero()),
population_diversity_weight: scirs2_core::numeric::NumCast::from(0.5)
.unwrap_or_else(|| T::zero()),
conservative_search: false,
},
SearchPhase::Advanced => SearchPhaseConfig {
complexity_limit: scirs2_core::numeric::NumCast::from(0.75)
.unwrap_or_else(|| T::zero()),
max_components: 6,
max_depth: 7,
exploration_factor: scirs2_core::numeric::NumCast::from(0.4)
.unwrap_or_else(|| T::zero()),
mutation_rate: scirs2_core::numeric::NumCast::from(0.15)
.unwrap_or_else(|| T::zero()),
population_diversity_weight: scirs2_core::numeric::NumCast::from(0.3)
.unwrap_or_else(|| T::zero()),
conservative_search: false,
},
SearchPhase::Final => SearchPhaseConfig {
complexity_limit: T::one(),
max_components: 8,
max_depth: 10,
exploration_factor: scirs2_core::numeric::NumCast::from(0.2)
.unwrap_or_else(|| T::zero()),
mutation_rate: scirs2_core::numeric::NumCast::from(0.1)
.unwrap_or_else(|| T::zero()),
population_diversity_weight: scirs2_core::numeric::NumCast::from(0.2)
.unwrap_or_else(|| T::zero()),
conservative_search: false,
},
}
}
pub fn record_architecture_evaluation(
&mut self,
architecture_id: String,
performance: T,
complexity: T,
) -> Result<()> {
let record = ProgressionRecord {
phase: self.current_phase,
complexity,
best_performance: performance,
architectures_evaluated: self.architecture_progression.history.len() + 1,
timestamp: std::time::Instant::now(),
};
self.architecture_progression.record_step(record);
let current_phase_key = self.current_phase;
let best_for_phase = self
.architecture_progression
.best_per_phase
.entry(current_phase_key)
.or_default();
best_for_phase.push(architecture_id);
if best_for_phase.len() > 3 {
best_for_phase.remove(0);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct SearchPhaseConfig<T: Float + Debug + Send + Sync + 'static> {
pub complexity_limit: T,
pub max_components: usize,
pub max_depth: usize,
pub exploration_factor: T,
pub mutation_rate: T,
pub population_diversity_weight: T,
pub conservative_search: bool,
}
impl<T: Float + Debug + Send + Sync + 'static> ComplexityScheduler<T> {
pub fn new() -> Result<Self> {
Ok(Self {
current_complexity: scirs2_core::numeric::NumCast::from(0.1)
.unwrap_or_else(|| T::zero()),
max_complexity: T::one(),
increase_rate: scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()),
strategy: SchedulingStrategy::Linear,
})
}
pub fn update_complexity(&mut self, phase: SearchPhase) -> T {
match self.strategy {
SchedulingStrategy::Linear => {
let phase_factor = match phase {
SearchPhase::Initial => {
scirs2_core::numeric::NumCast::from(0.25).unwrap_or_else(|| T::zero())
}
SearchPhase::Intermediate => {
scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero())
}
SearchPhase::Advanced => {
scirs2_core::numeric::NumCast::from(0.75).unwrap_or_else(|| T::zero())
}
SearchPhase::Final => T::one(),
};
self.current_complexity = self.max_complexity * phase_factor;
}
SchedulingStrategy::Exponential => {
self.current_complexity = self.current_complexity * (T::one() + self.increase_rate);
if self.current_complexity > self.max_complexity {
self.current_complexity = self.max_complexity;
}
}
_ => {
}
}
self.current_complexity
}
}
impl<T: Float + Debug + Send + Sync + 'static> Default for ArchitectureProgression<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Float + Debug + Send + Sync + 'static> ArchitectureProgression<T> {
pub fn new() -> Self {
Self {
history: Vec::new(),
best_per_phase: HashMap::new(),
performance_trends: Vec::new(),
}
}
pub fn record_step(&mut self, record: ProgressionRecord<T>) {
self.performance_trends.push(record.best_performance);
self.history.push(record);
}
pub fn get_best_for_phase(&self, phase: SearchPhase) -> Vec<String> {
self.best_per_phase.get(&phase).cloned().unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_progressive_nas_creation() {
let config = NASConfig::default();
let progressive_nas = ProgressiveNAS::new(&config);
assert!(progressive_nas.is_ok());
let nas: ProgressiveNAS<f64> = progressive_nas.expect("unwrap failed");
assert_eq!(nas.current_phase, SearchPhase::Initial);
}
#[test]
fn test_complexity_scheduler() {
let mut scheduler = ComplexityScheduler::<f64>::new().expect("unwrap failed");
let initial_complexity = scheduler.update_complexity(SearchPhase::Initial);
let intermediate_complexity = scheduler.update_complexity(SearchPhase::Intermediate);
let advanced_complexity = scheduler.update_complexity(SearchPhase::Advanced);
assert!(initial_complexity < intermediate_complexity);
assert!(intermediate_complexity < advanced_complexity);
}
#[test]
fn test_architecture_progression() {
let mut progression = ArchitectureProgression::<f64>::new();
let record = ProgressionRecord {
phase: SearchPhase::Initial,
complexity: 0.1,
best_performance: 0.8,
architectures_evaluated: 10,
timestamp: std::time::Instant::now(),
};
progression.record_step(record);
assert_eq!(progression.history.len(), 1);
assert_eq!(progression.performance_trends.len(), 1);
}
}