use std::collections::{HashMap, VecDeque};
use std::time::{Duration, Instant};
use scirs2_core::rand_prelude::IndexedRandom;
use scirs2_core::random::thread_rng;
use super::config::{NeuralArchitectureSearchConfig, SearchSpace, SearchStrategy};
use super::feature_extraction::{
ArchitectureSpec, ConnectionPattern, LayerSpec, OptimizationSettings, OptimizerType,
ProblemFeatures, RegularizationConfig,
};
pub struct NeuralArchitectureSearch {
pub config: NeuralArchitectureSearchConfig,
pub search_space: SearchSpace,
pub current_architectures: Vec<ArchitectureCandidate>,
pub search_history: VecDeque<SearchIteration>,
pub performance_predictor: PerformancePredictor,
}
impl NeuralArchitectureSearch {
#[must_use]
pub fn new(config: NeuralArchitectureSearchConfig) -> Self {
Self {
search_space: config.search_space.clone(),
config,
current_architectures: Vec::new(),
search_history: VecDeque::new(),
performance_predictor: PerformancePredictor::new(),
}
}
pub fn search_architecture(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
match self.config.search_strategy {
SearchStrategy::DifferentiableNAS => self.differentiable_search(problem_features),
SearchStrategy::EvolutionarySearch => self.evolutionary_search(problem_features),
SearchStrategy::ReinforcementLearning => self.rl_search(problem_features),
SearchStrategy::BayesianOptimization => self.bayesian_search(problem_features),
SearchStrategy::RandomSearch => self.random_search(problem_features),
SearchStrategy::ProgressiveSearch => self.progressive_search(problem_features),
}
}
fn differentiable_search(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
let candidate = self.generate_random_architecture(problem_features)?;
let performance = self
.performance_predictor
.predict(&candidate.architecture)?;
let mut improved_candidate = candidate;
improved_candidate.estimated_performance = performance;
improved_candidate.generation_method = GenerationMethod::GradientBased;
self.current_architectures.push(improved_candidate.clone());
Ok(improved_candidate)
}
fn evolutionary_search(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
if self.current_architectures.is_empty() {
return self.random_search(problem_features);
}
let parents = self.select_parents(2)?;
let mut offspring = self.crossover(&parents[0], &parents[1])?;
offspring = self.mutate(offspring)?;
let performance = self
.performance_predictor
.predict(&offspring.architecture)?;
offspring.estimated_performance = performance;
offspring.generation_method = GenerationMethod::Mutation;
self.current_architectures.push(offspring.clone());
Ok(offspring)
}
fn rl_search(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
let candidate = self.generate_random_architecture(problem_features)?;
let performance = self
.performance_predictor
.predict(&candidate.architecture)?;
let mut improved_candidate = candidate;
improved_candidate.estimated_performance = performance;
improved_candidate.generation_method = GenerationMethod::ReinforcementLearning;
self.current_architectures.push(improved_candidate.clone());
Ok(improved_candidate)
}
fn bayesian_search(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
let candidate = self.generate_random_architecture(problem_features)?;
let performance = self
.performance_predictor
.predict(&candidate.architecture)?;
let mut improved_candidate = candidate;
improved_candidate.estimated_performance = performance;
improved_candidate.generation_method = GenerationMethod::GradientBased;
self.current_architectures.push(improved_candidate.clone());
Ok(improved_candidate)
}
fn random_search(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
let candidate = self.generate_random_architecture(problem_features)?;
self.current_architectures.push(candidate.clone());
Ok(candidate)
}
fn progressive_search(
&mut self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
let candidate = self.generate_random_architecture(problem_features)?;
let performance = self
.performance_predictor
.predict(&candidate.architecture)?;
let mut improved_candidate = candidate;
improved_candidate.estimated_performance = performance;
self.current_architectures.push(improved_candidate.clone());
Ok(improved_candidate)
}
fn generate_random_architecture(
&self,
problem_features: &ProblemFeatures,
) -> Result<ArchitectureCandidate, String> {
use scirs2_core::random::prelude::*;
let mut rng = thread_rng();
let num_layers = rng.random_range(
self.search_space.num_layers_range.0..=self.search_space.num_layers_range.1,
);
let input_dim = problem_features.size.min(512);
let mut layers = Vec::new();
let mut current_dim = input_dim;
for i in 0..num_layers {
let layer_type = self
.search_space
.layer_types
.choose(&mut rng)
.ok_or("No layer types available")?;
let hidden_dim = self
.search_space
.hidden_dims
.choose(&mut rng)
.ok_or("No hidden dimensions available")?;
let activation = self
.search_space
.activations
.choose(&mut rng)
.ok_or("No activations available")?;
let dropout = self
.search_space
.dropout_rates
.choose(&mut rng)
.ok_or("No dropout rates available")?;
let output_dim = if i == num_layers - 1 { 1 } else { *hidden_dim };
layers.push(LayerSpec {
layer_type: layer_type.clone(),
input_dim: current_dim,
output_dim,
activation: activation.clone(),
dropout: *dropout,
parameters: HashMap::new(),
});
current_dim = output_dim;
}
let num_layers = layers.len();
let total_params: usize = layers.iter().map(|l| l.input_dim * l.output_dim).sum();
let total_flops: usize = layers.iter().map(|l| l.input_dim * l.output_dim * 2).sum();
let architecture = ArchitectureSpec {
layers,
connections: ConnectionPattern::Sequential,
optimization: OptimizationSettings {
optimizer: OptimizerType::Adam,
learning_rate: 0.001,
batch_size: 32,
epochs: 100,
regularization: RegularizationConfig {
l1_weight: 0.0,
l2_weight: 0.001,
dropout: 0.1,
batch_norm: true,
early_stopping: true,
},
},
};
let resource_requirements = ResourceRequirements {
memory: num_layers * 64, compute_time: Duration::from_secs(60),
parameters: total_params,
flops: total_flops as u64,
};
Ok(ArchitectureCandidate {
id: format!("arch_{}", Instant::now().elapsed().as_nanos()),
architecture,
estimated_performance: 0.5,
actual_performance: None,
resource_requirements,
generation_method: GenerationMethod::Random,
})
}
fn select_parents(&self, count: usize) -> Result<Vec<ArchitectureCandidate>, String> {
if self.current_architectures.len() < count {
return Err("Not enough architectures for parent selection".to_string());
}
use scirs2_core::random::prelude::*;
let mut rng = thread_rng();
let mut parents = Vec::new();
for _ in 0..count {
let tournament_size = 3.min(self.current_architectures.len());
let mut tournament: Vec<_> = self
.current_architectures
.choose_multiple(&mut rng, tournament_size)
.collect();
tournament.sort_by(|a, b| {
b.estimated_performance
.partial_cmp(&a.estimated_performance)
.unwrap_or(std::cmp::Ordering::Equal)
});
parents.push(tournament[0].clone());
}
Ok(parents)
}
fn crossover(
&self,
parent1: &ArchitectureCandidate,
parent2: &ArchitectureCandidate,
) -> Result<ArchitectureCandidate, String> {
use scirs2_core::random::prelude::*;
let mut rng = thread_rng();
let min_layers = parent1
.architecture
.layers
.len()
.min(parent2.architecture.layers.len());
let mut child_layers = Vec::new();
for i in 0..min_layers {
let layer = if rng.random_bool(0.5) {
parent1.architecture.layers[i].clone()
} else {
parent2.architecture.layers[i].clone()
};
child_layers.push(layer);
}
let child_architecture = ArchitectureSpec {
layers: child_layers,
connections: parent1.architecture.connections.clone(),
optimization: parent1.architecture.optimization.clone(),
};
Ok(ArchitectureCandidate {
id: format!("crossover_{}", Instant::now().elapsed().as_nanos()),
architecture: child_architecture,
estimated_performance: 0.0,
actual_performance: None,
resource_requirements: parent1.resource_requirements.clone(),
generation_method: GenerationMethod::Crossover,
})
}
fn mutate(
&self,
mut candidate: ArchitectureCandidate,
) -> Result<ArchitectureCandidate, String> {
use scirs2_core::random::prelude::*;
let mut rng = thread_rng();
if rng.random_bool(0.3) {
if !candidate.architecture.layers.is_empty() {
let layer_idx = rng.random_range(0..candidate.architecture.layers.len());
let layer = &mut candidate.architecture.layers[layer_idx];
if let Some(new_activation) = self.search_space.activations.choose(&mut rng) {
layer.activation = new_activation.clone();
}
if let Some(new_dropout) = self.search_space.dropout_rates.choose(&mut rng) {
layer.dropout = *new_dropout;
}
}
}
candidate.generation_method = GenerationMethod::Mutation;
candidate.id = format!("mutated_{}", Instant::now().elapsed().as_nanos());
Ok(candidate)
}
pub fn record_iteration(&mut self, iteration: SearchIteration) {
self.search_history.push_back(iteration);
while self.search_history.len() > 1000 {
self.search_history.pop_front();
}
}
#[must_use]
pub fn get_best_architecture(&self) -> Option<&ArchitectureCandidate> {
self.current_architectures.iter().max_by(|a, b| {
a.estimated_performance
.partial_cmp(&b.estimated_performance)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
}
#[derive(Debug, Clone)]
pub struct ArchitectureCandidate {
pub id: String,
pub architecture: ArchitectureSpec,
pub estimated_performance: f64,
pub actual_performance: Option<f64>,
pub resource_requirements: ResourceRequirements,
pub generation_method: GenerationMethod,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum GenerationMethod {
Random,
Mutation,
Crossover,
GradientBased,
ReinforcementLearning,
}
#[derive(Debug, Clone)]
pub struct SearchIteration {
pub iteration: usize,
pub architectures_evaluated: Vec<String>,
pub best_performance: f64,
pub strategy_used: SearchStrategy,
pub computational_cost: f64,
pub timestamp: Instant,
}
#[derive(Debug)]
pub struct PerformancePredictor {
pub model: PredictorModel,
pub training_data: Vec<(ArchitectureSpec, f64)>,
pub accuracy: f64,
pub uncertainty_estimation: bool,
}
impl PerformancePredictor {
#[must_use]
pub const fn new() -> Self {
Self {
model: PredictorModel::NeuralNetwork,
training_data: Vec::new(),
accuracy: 0.8,
uncertainty_estimation: false,
}
}
pub fn predict(&self, architecture: &ArchitectureSpec) -> Result<f64, String> {
let complexity = architecture.layers.len() as f64;
let total_params: f64 = architecture
.layers
.iter()
.map(|l| (l.input_dim * l.output_dim) as f64)
.sum();
let performance =
complexity.mul_add(-0.05, 0.8).max(0.1) * (1.0 - (total_params / 1_000_000.0).min(0.5));
Ok(performance.clamp(0.1, 1.0))
}
pub fn update(&mut self, architecture: ArchitectureSpec, performance: f64) {
self.training_data.push((architecture, performance));
while self.training_data.len() > 1000 {
self.training_data.remove(0);
}
if self.training_data.len() > 10 {
self.accuracy = 0.85;
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PredictorModel {
NeuralNetwork,
GaussianProcess,
RandomForest,
SupportVectorMachine,
Ensemble(Vec<Self>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResourceRequirements {
pub memory: usize,
pub compute_time: Duration,
pub parameters: usize,
pub flops: u64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::meta_learning_optimization::feature_extraction::{
GraphFeatures, SpectralFeatures, StatisticalFeatures,
};
#[test]
fn test_nas_creation() {
let config = NeuralArchitectureSearchConfig::default();
let nas = NeuralArchitectureSearch::new(config);
assert!(nas.config.enable_nas);
}
#[test]
fn test_performance_predictor() {
let predictor = PerformancePredictor::new();
assert_eq!(predictor.model, PredictorModel::NeuralNetwork);
assert_eq!(predictor.accuracy, 0.8);
}
#[test]
fn test_architecture_generation() {
let config = NeuralArchitectureSearchConfig::default();
let nas = NeuralArchitectureSearch::new(config);
let features = ProblemFeatures {
size: 100,
density: 0.5,
graph_features: GraphFeatures::default(),
statistical_features: StatisticalFeatures::default(),
spectral_features: SpectralFeatures::default(),
domain_features: HashMap::new(),
};
let result = nas.generate_random_architecture(&features);
assert!(result.is_ok());
let arch = result.expect("generate_random_architecture should succeed");
assert!(!arch.architecture.layers.is_empty());
assert_eq!(arch.generation_method, GenerationMethod::Random);
}
}