use std::fmt::Debug;
#[allow(dead_code)]
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::numeric::Float;
use std::collections::{HashMap, HashSet, VecDeque};
use crate::error::{OptimError, Result};
#[derive(Debug, Clone)]
pub struct EvolutionaryConfig<T: Float + Debug + Send + Sync + 'static> {
pub population_size: usize,
pub num_generations: usize,
pub mutation_rate: T,
pub crossover_rate: T,
pub elitism_ratio: T,
pub tournament_size: usize,
pub complexity_penalty: T,
pub max_depth: usize,
pub max_parameters: usize,
}
#[derive(Debug, Clone)]
pub struct Individual<T: Float + Debug + Send + Sync + 'static> {
pub architecture: ArchitectureSpecification<T>,
pub fitness: T,
pub age: usize,
pub performance_metrics: PerformanceMetrics<T>,
pub birth_generation: usize,
}
#[derive(Debug, Clone)]
pub struct ArchitectureSpecification<T: Float + Debug + Send + Sync + 'static> {
pub layers: Vec<LayerSpec<T>>,
pub connections: ConnectionTopology,
pub global_params: HashMap<String, T>,
pub parameter_count: usize,
pub estimated_flops: u64,
}
#[derive(Debug, Clone)]
pub struct LayerSpec<T: Float + Debug + Send + Sync + 'static> {
pub layer_type: LayerType,
pub input_dims: Vec<usize>,
pub output_dims: Vec<usize>,
pub parameters: HashMap<String, LayerParameter<T>>,
pub id: String,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum LayerType {
Dense,
Conv1D,
LSTM,
GRU,
Attention,
BatchNorm,
Dropout,
Activation,
OptimizerLayer,
}
#[derive(Debug, Clone)]
pub enum LayerParameter<T: Float + Debug + Send + Sync + 'static> {
Integer(i64),
Float(T),
Boolean(bool),
String(String),
Array(Vec<T>),
}
#[derive(Debug, Clone)]
pub struct ConnectionTopology {
pub adjacency_matrix: Array2<f32>,
pub connection_types: HashMap<(usize, usize), ConnectionType>,
pub skip_connections: Vec<(usize, usize)>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ConnectionType {
Forward,
Residual,
Dense,
Attention,
}
#[derive(Debug, Clone)]
pub struct PerformanceMetrics<T: Float + Debug + Send + Sync + 'static> {
pub training_performance: T,
pub validation_performance: T,
pub convergence_speed: T,
pub memory_efficiency: T,
pub computational_efficiency: T,
pub generalization: T,
}
#[derive(Debug)]
pub struct EvolutionarySearcher<T: Float + Debug + Send + Sync + 'static> {
config: EvolutionaryConfig<T>,
population: Vec<Individual<T>>,
best_individual: Option<Individual<T>>,
current_generation: usize,
evolution_history: EvolutionHistory<T>,
mutation_operators: Vec<MutationOperator>,
crossover_operators: Vec<CrossoverOperator>,
selection_strategy: SelectionStrategy,
}
#[derive(Debug, Clone)]
pub struct EvolutionHistory<T: Float + Debug + Send + Sync + 'static> {
pub best_fitness_history: Vec<T>,
pub average_fitness_history: Vec<T>,
pub diversity_history: Vec<T>,
pub improvements: Vec<(usize, T)>,
}
#[derive(Debug, Clone, Copy)]
pub enum MutationOperator {
AddLayer,
RemoveLayer,
ModifyParameters,
ChangeLayerType,
ModifyConnections,
AddSkipConnection,
RemoveSkipConnection,
}
#[derive(Debug, Clone, Copy)]
pub enum CrossoverOperator {
SinglePoint,
MultiPoint,
Uniform,
LayerWise,
ConnectionWise,
}
#[derive(Debug, Clone, Copy)]
pub enum SelectionStrategy {
Tournament,
RouletteWheel,
RankBased,
Elite,
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> EvolutionarySearcher<T> {
pub fn new(config: EvolutionaryConfig<T>) -> Result<Self> {
Ok(Self {
config,
population: Vec::new(),
best_individual: None,
current_generation: 0,
evolution_history: EvolutionHistory::new(),
mutation_operators: vec![
MutationOperator::AddLayer,
MutationOperator::RemoveLayer,
MutationOperator::ModifyParameters,
MutationOperator::ChangeLayerType,
MutationOperator::ModifyConnections,
],
crossover_operators: vec![
CrossoverOperator::SinglePoint,
CrossoverOperator::LayerWise,
CrossoverOperator::ConnectionWise,
],
selection_strategy: SelectionStrategy::Tournament,
})
}
pub fn initialize_population(&mut self) -> Result<()> {
self.population.clear();
for i in 0..self.config.population_size {
let individual = self.create_random_individual(i)?;
self.population.push(individual);
}
Ok(())
}
pub fn search(&mut self, fitness_fn: &dyn Fn(&ArchitectureSpecification<T>) -> Result<T>) -> Result<Individual<T>> {
if self.population.is_empty() {
self.initialize_population()?;
}
self.evaluate_population(fitness_fn)?;
for generation in 0..self.config.num_generations {
self.current_generation = generation;
let selected = self.selection()?;
let mut offspring = self.crossover_and_mutation(&selected)?;
for individual in &mut offspring {
individual.fitness = fitness_fn(&individual.architecture)?;
individual.performance_metrics = self.evaluate_performance_metrics(&individual.architecture)?;
}
self.survivor_selection(&mut offspring)?;
self.update_evolution_history()?;
if let Some(ref best) = self.best_individual {
println!("Generation {}: Best fitness = {}", generation, best.fitness.to_f64().unwrap_or(0.0));
}
}
self.best_individual.clone().ok_or_else(||
OptimError::SearchFailed("No valid individuals found".to_string())
)
}
fn create_random_individual(&self, id: usize) -> Result<Individual<T>> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let num_layers = rng.gen_range(2..=self.config.max_depth.min(10));
let mut layers = Vec::new();
for i in 0..num_layers {
let layer = self.create_random_layer(i)?;
layers.push(layer);
}
let connections = self.create_random_connections(num_layers)?;
let parameter_count = self.estimate_parameters(&layers);
let architecture = ArchitectureSpecification {
layers,
connections,
global_params: HashMap::new(),
parameter_count,
estimated_flops: parameter_count as u64 * 1000, };
Ok(Individual {
architecture,
fitness: T::zero(),
age: 0,
performance_metrics: PerformanceMetrics::default(),
birth_generation: self.current_generation,
})
}
fn create_random_layer(&self, layer_id: usize) -> Result<LayerSpec<T>> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let layer_types = vec![
LayerType::Dense,
LayerType::Conv1D,
LayerType::LSTM,
LayerType::Attention,
LayerType::BatchNorm,
];
let layer_type = layer_types[rng.gen_range(0..layer_types.len())];
let mut parameters = HashMap::new();
match layer_type {
LayerType::Dense => {
let input_size = if layer_id == 0 { 128 } else { rng.gen_range(32..512) };
let output_size = rng.gen_range(32..512);
parameters.insert("input_size".to_string(), LayerParameter::Integer(input_size));
parameters.insert("output_size".to_string(), LayerParameter::Integer(output_size));
}
LayerType::LSTM => {
let hidden_size = rng.gen_range(64..256);
parameters.insert("hidden_size".to_string(), LayerParameter::Integer(hidden_size));
parameters.insert("num_layers".to_string(), LayerParameter::Integer(1));
}
LayerType::Attention => {
let hidden_dim = rng.gen_range(64..256);
let num_heads = rng.gen_range(4..16);
parameters.insert("hidden_dim".to_string(), LayerParameter::Integer(hidden_dim));
parameters.insert("num_heads".to_string(), LayerParameter::Integer(num_heads));
}
_ => {}
}
Ok(LayerSpec {
layer_type,
input_dims: vec![128], output_dims: vec![128],
parameters,
id: format!("layer_{}", layer_id),
})
}
fn create_random_connections(&self, num_layers: usize) -> Result<ConnectionTopology> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let mut adjacency_matrix = Array2::zeros((num_layers, num_layers));
let mut connection_types = HashMap::new();
let mut skip_connections = Vec::new();
for i in 0..num_layers-1 {
adjacency_matrix[[i, i+1]] = 1.0;
connection_types.insert((i, i+1), ConnectionType::Forward);
}
let num_skip = rng.gen_range(0..num_layers/2);
for _ in 0..num_skip {
let from = rng.gen_range(0..num_layers-2);
let to = rng.gen_range(from+2..num_layers);
if adjacency_matrix[[from, to]] == 0.0 {
adjacency_matrix[[from, to]] = 1.0;
connection_types.insert((from, to), ConnectionType::Residual);
skip_connections.push((from, to));
}
}
Ok(ConnectionTopology {
adjacency_matrix,
connection_types,
skip_connections,
})
}
fn estimate_parameters(&self, layers: &[LayerSpec<T>]) -> usize {
layers.iter().map(|layer| {
match layer.layer_type {
LayerType::Dense => {
if let (Some(LayerParameter::Integer(input)), Some(LayerParameter::Integer(output))) =
(layer.parameters.get("input_size"), layer.parameters.get("output_size")) {
(*input as usize) * (*output as usize) + (*output as usize)
} else { 1000 }
}
LayerType::LSTM => {
if let Some(LayerParameter::Integer(hidden)) = layer.parameters.get("hidden_size") {
4 * (*hidden as usize) * (*hidden as usize) } else { 10000 }
}
LayerType::Attention => {
if let Some(LayerParameter::Integer(hidden)) = layer.parameters.get("hidden_dim") {
3 * (*hidden as usize) * (*hidden as usize) } else { 5000 }
}
_ => 100 }
}).sum()
}
fn evaluate_population(&mut self, fitness_fn: &dyn Fn(&ArchitectureSpecification<T>) -> Result<T>) -> Result<()> {
for individual in &mut self.population {
individual.fitness = fitness_fn(&individual.architecture)?;
individual.performance_metrics = self.evaluate_performance_metrics(&individual.architecture)?;
}
if let Some(best) = self.population.iter().max_by(|a, b|
a.fitness.partial_cmp(&b.fitness).unwrap_or(std::cmp::Ordering::Equal)) {
self.best_individual = Some(best.clone());
}
Ok(())
}
fn selection(&self) -> Result<Vec<Individual<T>>> {
match self.selection_strategy {
SelectionStrategy::Tournament => self.tournament_selection(),
SelectionStrategy::RouletteWheel => self.roulette_wheel_selection(),
SelectionStrategy::RankBased => self.rank_based_selection(),
SelectionStrategy::Elite => self.elite_selection(),
}
}
fn tournament_selection(&self) -> Result<Vec<Individual<T>>> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let mut selected = Vec::new();
for _ in 0..self.config.population_size {
let mut tournament = Vec::new();
for _ in 0..self.config.tournament_size {
let idx = rng.gen_range(0..self.population.len());
tournament.push(&self.population[idx]);
}
let winner = tournament.into_iter().max_by(|a, b|
a.fitness.partial_cmp(&b.fitness).unwrap_or(std::cmp::Ordering::Equal)
).expect("unwrap failed");
selected.push(winner.clone());
}
Ok(selected)
}
fn roulette_wheel_selection(&self) -> Result<Vec<Individual<T>>> {
Ok(self.population.clone())
}
fn rank_based_selection(&self) -> Result<Vec<Individual<T>>> {
Ok(self.population.clone())
}
fn elite_selection(&self) -> Result<Vec<Individual<T>>> {
let mut sorted = self.population.clone();
sorted.sort_by(|a, b| b.fitness.partial_cmp(&a.fitness).unwrap_or(std::cmp::Ordering::Equal));
Ok(sorted.into_iter().take(self.config.population_size).collect())
}
fn crossover_and_mutation(&self, selected: &[Individual<T>]) -> Result<Vec<Individual<T>>> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let mut offspring = Vec::new();
for i in (0..selected.len()).step_by(2) {
let parent1 = &selected[i];
let parent2 = if i + 1 < selected.len() { &selected[i + 1] } else { &selected[0] };
let (mut child1, mut child2) = if rng.random::<f64>() < self.config.crossover_rate.to_f64().unwrap_or(0.7) {
self.crossover(parent1, parent2)?
} else {
(parent1.clone(), parent2.clone())
};
if rng.random::<f64>() < self.config.mutation_rate.to_f64().unwrap_or(0.1) {
self.mutate(&mut child1)?;
}
if rng.random::<f64>() < self.config.mutation_rate.to_f64().unwrap_or(0.1) {
self.mutate(&mut child2)?;
}
offspring.push(child1);
if offspring.len() < self.config.population_size {
offspring.push(child2);
}
}
Ok(offspring)
}
fn crossover(&self, parent1: &Individual<T>, parent2: &Individual<T>) -> Result<(Individual<T>, Individual<T>)> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let mut child1 = parent1.clone();
let mut child2 = parent2.clone();
let min_layers = parent1.architecture.layers.len().min(parent2.architecture.layers.len());
if min_layers > 1 {
let crossover_point = rng.gen_range(1..min_layers);
for i in crossover_point..min_layers {
if i < child1.architecture.layers.len() && i < parent2.architecture.layers.len() {
child1.architecture.layers[i] = parent2.architecture.layers[i].clone();
}
if i < child2.architecture.layers.len() && i < parent1.architecture.layers.len() {
child2.architecture.layers[i] = parent1.architecture.layers[i].clone();
}
}
}
Ok((child1, child2))
}
fn mutate(&self, individual: &mut Individual<T>) -> Result<()> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
let mutation_op = self.mutation_operators[rng.gen_range(0..self.mutation_operators.len())];
match mutation_op {
MutationOperator::AddLayer => {
if individual.architecture.layers.len() < self.config.max_depth {
let new_layer = self.create_random_layer(individual.architecture.layers.len())?;
individual.architecture.layers.push(new_layer);
}
}
MutationOperator::RemoveLayer => {
if individual.architecture.layers.len() > 2 {
let idx = rng.gen_range(1..individual.architecture.layers.len()-1);
individual.architecture.layers.remove(idx);
}
}
MutationOperator::ModifyParameters => {
if !individual.architecture.layers.is_empty() {
let layer_idx = rng.gen_range(0..individual.architecture.layers.len());
self.mutate_layer_parameters(&mut individual.architecture.layers[layer_idx])?;
}
}
_ => {} }
individual.birth_generation = self.current_generation;
Ok(())
}
fn mutate_layer_parameters(&self, layer: &mut LayerSpec<T>) -> Result<()> {
use scirs2_core::random::Rng;
let mut rng = scirs2_core::random::thread_rng();
match layer.layer_type {
LayerType::Dense => {
if let Some(LayerParameter::Integer(ref mut size)) = layer.parameters.get_mut("output_size") {
*size = rng.gen_range(32..512);
}
}
LayerType::LSTM => {
if let Some(LayerParameter::Integer(ref mut hidden)) = layer.parameters.get_mut("hidden_size") {
*hidden = rng.gen_range(64..256);
}
}
LayerType::Attention => {
if let Some(LayerParameter::Integer(ref mut heads)) = layer.parameters.get_mut("num_heads") {
*heads = rng.gen_range(4..16);
}
}
_ => {}
}
Ok(())
}
fn survivor_selection(&mut self, offspring: &mut Vec<Individual<T>>) -> Result<()> {
let elite_count = (self.config.elitism_ratio * scirs2_core::numeric::NumCast::from(self.config.population_size as f64).unwrap_or_else(|| T::zero())).to_usize().unwrap_or(1);
let mut combined = self.population.clone();
combined.append(offspring);
combined.sort_by(|a, b| b.fitness.partial_cmp(&a.fitness).unwrap_or(std::cmp::Ordering::Equal));
self.population = combined.into_iter().take(self.config.population_size).collect();
for individual in &mut self.population {
individual.age += 1;
}
Ok(())
}
fn update_evolution_history(&mut self) -> Result<()> {
if self.population.is_empty() {
return Ok(());
}
let best_fitness = self.population.iter()
.map(|ind| ind.fitness)
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(T::zero());
let avg_fitness = self.population.iter().map(|ind| ind.fitness).fold(T::zero(), |acc, f| acc + f) /
T::from(self.population.len() as f64).expect("unwrap failed");
let diversity = self.calculate_population_diversity()?;
self.evolution_history.best_fitness_history.push(best_fitness);
self.evolution_history.average_fitness_history.push(avg_fitness);
self.evolution_history.diversity_history.push(diversity);
Ok(())
}
fn calculate_population_diversity(&self) -> Result<T> {
if self.population.len() < 2 {
return Ok(T::zero());
}
let mut total_difference = T::zero();
let mut comparisons = 0;
for i in 0..self.population.len() {
for j in i+1..self.population.len() {
let diff = self.calculate_architecture_difference(
&self.population[i].architecture,
&self.population[j].architecture
);
total_difference = total_difference + diff;
comparisons += 1;
}
}
Ok(total_difference / scirs2_core::numeric::NumCast::from(comparisons as f64).unwrap_or_else(|| T::zero()))
}
fn calculate_architecture_difference(&self, arch1: &ArchitectureSpecification<T>, arch2: &ArchitectureSpecification<T>) -> T {
let layer_diff = T::from((arch1.layers.len() as i32 - arch2.layers.len() as i32).abs() as f64).expect("unwrap failed");
let param_diff = T::from((arch1.parameter_count as i32 - arch2.parameter_count as i32).abs() as f64).expect("unwrap failed") / scirs2_core::numeric::NumCast::from(1000.0).unwrap_or_else(|| T::zero());
layer_diff + param_diff
}
fn evaluate_performance_metrics(&self, architecture: &ArchitectureSpecification<T>) -> Result<PerformanceMetrics<T>> {
let complexity = scirs2_core::numeric::NumCast::from(architecture.parameter_count as f64).unwrap_or_else(|| T::zero()) / scirs2_core::numeric::NumCast::from(1000000.0).unwrap_or_else(|| T::zero()); let efficiency = T::one() / (T::one() + complexity);
Ok(PerformanceMetrics {
training_performance: scirs2_core::numeric::NumCast::from(0.8).unwrap_or_else(|| T::zero()) - complexity * scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()),
validation_performance: scirs2_core::numeric::NumCast::from(0.75).unwrap_or_else(|| T::zero()) - complexity * scirs2_core::numeric::NumCast::from(0.15).unwrap_or_else(|| T::zero()),
convergence_speed: efficiency,
memory_efficiency: efficiency,
computational_efficiency: efficiency,
generalization: scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero()),
})
}
pub fn best_individual(&self) -> Option<&Individual<T>> {
self.best_individual.as_ref()
}
pub fn evolution_history(&self) -> &EvolutionHistory<T> {
&self.evolution_history
}
pub fn population(&self) -> &[Individual<T>] {
&self.population
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> EvolutionHistory<T> {
fn new() -> Self {
Self {
best_fitness_history: Vec::new(),
average_fitness_history: Vec::new(),
diversity_history: Vec::new(),
improvements: Vec::new(),
}
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> Default for PerformanceMetrics<T> {
fn default() -> Self {
Self {
training_performance: T::zero(),
validation_performance: T::zero(),
convergence_speed: T::zero(),
memory_efficiency: T::zero(),
computational_efficiency: T::zero(),
generalization: T::zero(),
}
}
}
impl<T: Float + Debug + Default + Clone + Send + Sync + 'static> Default for EvolutionaryConfig<T> {
fn default() -> Self {
Self {
population_size: 50,
num_generations: 100,
mutation_rate: scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()),
crossover_rate: scirs2_core::numeric::NumCast::from(0.7).unwrap_or_else(|| T::zero()),
elitism_ratio: scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero()),
tournament_size: 3,
complexity_penalty: scirs2_core::numeric::NumCast::from(0.01).unwrap_or_else(|| T::zero()),
max_depth: 10,
max_parameters: 10_000_000,
}
}
}
impl Default for ConnectionTopology {
fn default() -> Self {
Self {
adjacency_matrix: Array2::zeros((0, 0)),
connection_types: HashMap::new(),
skip_connections: Vec::new(),
}
}
}