use crate::error::Result;
use crate::nas_engine::config::*;
use crate::nas_engine::results::*;
use scirs2_core::numeric::Float;
use scirs2_core::RngExt;
use std::collections::HashMap;
use std::fmt::Debug;
use super::support::ArchitectureController;
pub(super) struct DefaultArchitectureController<T: Float + Debug + Send + Sync + 'static> {
pub(super) search_space: SearchSpaceConfig,
pub(super) rng: scirs2_core::random::Random<scirs2_core::random::rngs::StdRng>,
pub(super) counter: usize,
pub(super) _phantom: std::marker::PhantomData<T>,
}
impl<T: Float + Debug + Send + Sync + 'static> DefaultArchitectureController<T> {
pub fn new(config: &NASConfig<T>) -> Result<Self> {
Ok(Self {
search_space: config.search_space.clone(),
rng: scirs2_core::random::Random::seed(42),
counter: 0,
_phantom: std::marker::PhantomData,
})
}
pub(super) fn component_labels(&self) -> Vec<String> {
if !self.search_space.component_types.is_empty() {
self.search_space
.component_types
.iter()
.map(|c| format!("{:?}", c))
.collect()
} else if !self.search_space.components.is_empty() {
self.search_space
.components
.iter()
.map(|c| format!("{:?}", c.component_type))
.collect()
} else {
vec!["Adam".to_string()]
}
}
pub(super) fn component_count_bounds(&self) -> (usize, usize) {
let max = self.search_space.max_components.max(1);
let min = self.search_space.min_components.max(1).min(max);
(min, max)
}
pub(super) fn sample_learning_rate(&mut self) -> T {
let log_min = (1e-4f64).ln();
let log_max = (1e-1f64).ln();
let log_val: f64 = self.rng.gen_range(log_min..log_max);
scirs2_core::numeric::NumCast::from(log_val.exp()).unwrap_or_else(|| T::zero())
}
pub(super) fn next_id(&mut self, kind: &str) -> String {
self.counter += 1;
format!(
"ctrl_{}_{}_{:08x}",
kind,
self.counter,
self.rng.random::<u32>()
)
}
}
impl<T: Float + Debug + Send + Sync + 'static> ArchitectureController<T>
for DefaultArchitectureController<T>
{
fn generate_random(&mut self) -> Result<OptimizerArchitecture<T>> {
let labels = self.component_labels();
let (min_c, max_c) = self.component_count_bounds();
let num = self.rng.gen_range(min_c..=max_c);
let mut components = Vec::with_capacity(num);
for _ in 0..num {
let idx = self.rng.gen_range(0..labels.len());
components.push(labels[idx].clone());
}
let mut hyperparameters = HashMap::new();
hyperparameters.insert("learning_rate".to_string(), self.sample_learning_rate());
let architecture_id = self.next_id("random");
Ok(OptimizerArchitecture {
components,
parameters: HashMap::new(),
connections: Vec::new(),
hyperparameters,
architecture_id,
metadata: HashMap::new(),
})
}
fn mutate(
&mut self,
architecture: &OptimizerArchitecture<T>,
) -> Result<OptimizerArchitecture<T>> {
let labels = self.component_labels();
let mut child = architecture.clone();
if child.components.is_empty() {
let idx = self.rng.gen_range(0..labels.len());
child.components.push(labels[idx].clone());
} else {
let pos = self.rng.gen_range(0..child.components.len());
let idx = self.rng.gen_range(0..labels.len());
child.components[pos] = labels[idx].clone();
}
let new_lr = self.sample_learning_rate();
child
.hyperparameters
.insert("learning_rate".to_string(), new_lr);
child.architecture_id = self.next_id("mutant");
Ok(child)
}
fn crossover(
&mut self,
parent1: &OptimizerArchitecture<T>,
parent2: &OptimizerArchitecture<T>,
) -> Result<OptimizerArchitecture<T>> {
let (min_c, max_c) = self.component_count_bounds();
let len1 = parent1.components.len();
let len2 = parent2.components.len();
let mut components = Vec::new();
if len1 == 0 && len2 == 0 {
let labels = self.component_labels();
let idx = self.rng.gen_range(0..labels.len());
components.push(labels[idx].clone());
} else {
let cut1 = self.rng.gen_range(0..=len1);
components.extend(parent1.components[..cut1].iter().cloned());
let cut2 = self.rng.gen_range(0..=len2);
components.extend(parent2.components[cut2..].iter().cloned());
if components.is_empty() {
let source = if len1 >= len2 {
&parent1.components
} else {
&parent2.components
};
components.push(source[0].clone());
}
}
if components.len() > max_c {
components.truncate(max_c);
}
while components.len() < min_c {
let labels = self.component_labels();
let idx = self.rng.gen_range(0..labels.len());
components.push(labels[idx].clone());
}
let lr1 = parent1.hyperparameters.get("learning_rate").copied();
let lr2 = parent2.hyperparameters.get("learning_rate").copied();
let lr = match (lr1, lr2) {
(Some(a), Some(b)) => {
let half: T = scirs2_core::numeric::NumCast::from(0.5).unwrap_or_else(|| T::zero());
(a + b) * half
}
(Some(a), None) => a,
(None, Some(b)) => b,
(None, None) => self.sample_learning_rate(),
};
let mut hyperparameters = HashMap::new();
hyperparameters.insert("learning_rate".to_string(), lr);
let architecture_id = self.next_id("cross");
Ok(OptimizerArchitecture {
components,
parameters: HashMap::new(),
connections: Vec::new(),
hyperparameters,
architecture_id,
metadata: HashMap::new(),
})
}
fn validate(&self, architecture: &OptimizerArchitecture<T>) -> Result<bool> {
if architecture.components.is_empty() {
return Ok(false);
}
let max_c = self.search_space.max_components;
if max_c > 0 && architecture.components.len() > max_c {
return Ok(false);
}
Ok(true)
}
}