use anyhow::Result;
#[allow(unused_imports)]
use scirs2_core::random::{Random, RngExt};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::fmt;
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NeuroEvolutionConfig {
pub population_size: usize,
pub num_generations: usize,
pub mutation_rate: f64,
pub crossover_rate: f64,
pub selection_pressure: f64,
pub elite_ratio: f64,
pub tournament_size: usize,
pub max_depth: usize,
pub max_width: usize,
pub diversity_threshold: f64,
pub hardware_constraints: HardwareConstraints,
pub objective_weights: ObjectiveWeights,
pub complexity_penalty: f64,
}
impl Default for NeuroEvolutionConfig {
fn default() -> Self {
Self {
population_size: 50,
num_generations: 100,
mutation_rate: 0.1,
crossover_rate: 0.8,
selection_pressure: 2.0,
elite_ratio: 0.1,
tournament_size: 3,
max_depth: 10,
max_width: 512,
diversity_threshold: 0.7,
hardware_constraints: HardwareConstraints::default(),
objective_weights: ObjectiveWeights::default(),
complexity_penalty: 0.01,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareConstraints {
pub max_memory_mb: usize,
pub max_inference_time_ms: f64,
pub max_parameters: usize,
pub max_flops: usize,
pub target_platform: HardwarePlatform,
}
impl Default for HardwareConstraints {
fn default() -> Self {
Self {
max_memory_mb: 8192, max_inference_time_ms: 100.0,
max_parameters: 10_000_000, max_flops: 1_000_000_000, target_platform: HardwarePlatform::GPU,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HardwarePlatform {
CPU,
GPU,
TPU,
Mobile,
Edge,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectiveWeights {
pub accuracy: f64,
pub efficiency: f64,
pub memory: f64,
pub generalization: f64,
pub robustness: f64,
}
impl Default for ObjectiveWeights {
fn default() -> Self {
Self {
accuracy: 0.4,
efficiency: 0.3,
memory: 0.1,
generalization: 0.1,
robustness: 0.1,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NeuralArchitecture {
pub id: Uuid,
pub layers: Vec<ArchitectureLayer>,
pub skip_connections: Vec<SkipConnection>,
pub hyperparameters: ArchitectureHyperparameters,
pub performance: Option<PerformanceMetrics>,
pub complexity: ArchitectureComplexity,
}
impl NeuralArchitecture {
pub fn random(config: &NeuroEvolutionConfig, rng: &mut Random) -> Self {
let mut layers = Vec::new();
let depth = rng.random_range(1..config.max_depth + 1);
for i in 0..depth {
let layer = ArchitectureLayer::random(config, i, rng);
layers.push(layer);
}
let skip_connections = Self::generate_skip_connections(&layers, rng);
let hyperparameters = ArchitectureHyperparameters::random(rng);
let complexity = Self::calculate_complexity(&layers, &skip_connections);
Self {
id: Uuid::new_v4(),
layers,
skip_connections,
hyperparameters,
performance: None,
complexity,
}
}
fn generate_skip_connections(
layers: &[ArchitectureLayer],
rng: &mut Random,
) -> Vec<SkipConnection> {
let mut connections = Vec::new();
for i in 0..layers.len() {
for j in (i + 2)..layers.len() {
if rng.random_bool_with_chance(0.2) {
connections.push(SkipConnection {
from_layer: i,
to_layer: j,
connection_type: SkipConnectionType::random(rng),
});
}
}
}
connections
}
fn calculate_complexity(
layers: &[ArchitectureLayer],
skip_connections: &[SkipConnection],
) -> ArchitectureComplexity {
let mut parameters = 0;
let mut flops = 0;
let mut memory_mb = 0;
for layer in layers {
parameters += layer.estimate_parameters();
flops += layer.estimate_flops();
memory_mb += layer.estimate_memory_mb();
}
for _conn in skip_connections {
parameters += 1000; flops += 10000;
memory_mb += 1;
}
ArchitectureComplexity {
parameters,
flops,
memory_mb,
depth: layers.len(),
width: layers.iter().map(|l| l.output_size).max().unwrap_or(0),
}
}
pub fn mutate(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
if rng.random_bool_with_chance(config.mutation_rate) {
match rng.random_range(0..4) {
0 => self.mutate_layers(config, rng),
1 => self.mutate_skip_connections(rng),
2 => self.mutate_hyperparameters(rng),
3 => self.mutate_layer_parameters(rng),
_ => unreachable!(),
}
self.complexity = Self::calculate_complexity(&self.layers, &self.skip_connections);
}
}
fn mutate_layers(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
match rng.random_range(0..3) {
0 => {
if self.layers.len() < config.max_depth {
let position = rng.random_range(0..self.layers.len() + 1);
let layer = ArchitectureLayer::random(config, position, rng);
self.layers.insert(position, layer);
}
}
1 => {
if self.layers.len() > 1 {
let position = rng.random_range(0..self.layers.len());
self.layers.remove(position);
}
}
2 => {
if !self.layers.is_empty() {
let position = rng.random_range(0..self.layers.len());
self.layers[position].mutate(config, rng);
}
}
_ => unreachable!(),
}
}
fn mutate_skip_connections(&mut self, rng: &mut Random) {
match rng.random_range(0..3) {
0 => {
if self.layers.len() >= 3 {
let from = rng.random_range(0..self.layers.len() - 2);
let to = rng.random_range(from + 2..self.layers.len());
let connection = SkipConnection {
from_layer: from,
to_layer: to,
connection_type: SkipConnectionType::random(rng),
};
self.skip_connections.push(connection);
}
}
1 => {
if !self.skip_connections.is_empty() {
let position = rng.random_range(0..self.skip_connections.len());
self.skip_connections.remove(position);
}
}
2 => {
if !self.skip_connections.is_empty() {
let position = rng.random_range(0..self.skip_connections.len());
self.skip_connections[position].connection_type =
SkipConnectionType::random(rng);
}
}
_ => unreachable!(),
}
}
fn mutate_hyperparameters(&mut self, rng: &mut Random) {
self.hyperparameters.mutate(rng);
}
fn mutate_layer_parameters(&mut self, rng: &mut Random) {
if !self.layers.is_empty() {
let layer_idx = rng.random_range(0..self.layers.len());
self.layers[layer_idx].mutate_parameters(rng);
}
}
pub fn crossover(&self, other: &Self, rng: &mut Random) -> (Self, Self) {
let min_layers = self.layers.len().min(other.layers.len());
let crossover_point = if min_layers <= 1 {
0 } else {
rng.random_range(1..min_layers)
};
let mut child1_layers = self.layers[..crossover_point].to_vec();
child1_layers.extend_from_slice(&other.layers[crossover_point..]);
let mut child2_layers = other.layers[..crossover_point].to_vec();
child2_layers.extend_from_slice(&self.layers[crossover_point..]);
let child1 = Self {
id: Uuid::new_v4(),
layers: child1_layers,
skip_connections: self.skip_connections.clone(),
hyperparameters: self.hyperparameters.crossover(&other.hyperparameters, rng),
performance: None,
complexity: ArchitectureComplexity::default(),
};
let child2 = Self {
id: Uuid::new_v4(),
layers: child2_layers,
skip_connections: other.skip_connections.clone(),
hyperparameters: other.hyperparameters.crossover(&self.hyperparameters, rng),
performance: None,
complexity: ArchitectureComplexity::default(),
};
(child1, child2)
}
pub fn diversity_distance(&self, other: &Self) -> f64 {
let layer_distance = self.layer_distance(other);
let connection_distance = self.connection_distance(other);
let hyperparameter_distance = self.hyperparameters.distance(&other.hyperparameters);
(layer_distance + connection_distance + hyperparameter_distance) / 3.0
}
fn layer_distance(&self, other: &Self) -> f64 {
let max_len = self.layers.len().max(other.layers.len());
if max_len == 0 {
return 0.0;
}
let mut differences = 0;
for i in 0..max_len {
match (self.layers.get(i), other.layers.get(i)) {
(Some(l1), Some(l2)) => {
if l1.layer_type != l2.layer_type || l1.output_size != l2.output_size {
differences += 1;
}
}
(None, Some(_)) | (Some(_), None) => differences += 1,
(None, None) => continue,
}
}
differences as f64 / max_len as f64
}
fn connection_distance(&self, other: &Self) -> f64 {
let max_connections = self
.skip_connections
.len()
.max(other.skip_connections.len());
if max_connections == 0 {
return 0.0;
}
let self_set: HashSet<_> = self
.skip_connections
.iter()
.map(|c| (c.from_layer, c.to_layer))
.collect();
let other_set: HashSet<_> = other
.skip_connections
.iter()
.map(|c| (c.from_layer, c.to_layer))
.collect();
let intersection = self_set.intersection(&other_set).count();
let union = self_set.union(&other_set).count();
1.0 - (intersection as f64 / union as f64)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ArchitectureLayer {
pub layer_type: LayerType,
pub input_size: usize,
pub output_size: usize,
pub parameters: LayerParameters,
}
impl ArchitectureLayer {
pub fn random(config: &NeuroEvolutionConfig, layer_index: usize, rng: &mut Random) -> Self {
let layer_type = LayerType::random(rng);
let output_size = rng.random_range(16..config.max_width + 1);
let input_size = if layer_index == 0 {
128
} else {
output_size / 2
};
let parameters = LayerParameters::random(&layer_type, rng);
Self {
layer_type,
input_size,
output_size,
parameters,
}
}
pub fn mutate(&mut self, config: &NeuroEvolutionConfig, rng: &mut Random) {
match rng.random_range(0..3) {
0 => self.layer_type = LayerType::random(rng),
1 => self.output_size = rng.random_range(16..config.max_width + 1),
2 => self.parameters.mutate(rng),
_ => unreachable!(),
}
}
pub fn mutate_parameters(&mut self, rng: &mut Random) {
self.parameters.mutate(rng);
}
pub fn estimate_parameters(&self) -> usize {
match self.layer_type {
LayerType::Dense => self.input_size * self.output_size + self.output_size,
LayerType::Attention => {
let head_dim = self.output_size / 8; self.input_size * self.output_size * 3 + self.output_size * head_dim
}
LayerType::Convolution => {
let kernel_size = 3; kernel_size * kernel_size * self.input_size * self.output_size + self.output_size
}
LayerType::GraphConv => self.input_size * self.output_size + self.output_size,
LayerType::LSTM => {
4 * (self.input_size * self.output_size + self.output_size * self.output_size)
}
LayerType::Transformer => {
let ff_size = self.output_size * 4;
self.input_size * self.output_size * 3
+ self.output_size * ff_size
+ ff_size * self.output_size
}
LayerType::Embedding => self.input_size * self.output_size,
}
}
pub fn estimate_flops(&self) -> usize {
match self.layer_type {
LayerType::Dense => self.input_size * self.output_size * 2,
LayerType::Attention => self.input_size * self.output_size * 6,
LayerType::Convolution => {
let kernel_size = 3;
kernel_size * kernel_size * self.input_size * self.output_size * 2
}
LayerType::GraphConv => self.input_size * self.output_size * 3,
LayerType::LSTM => self.input_size * self.output_size * 8,
LayerType::Transformer => self.input_size * self.output_size * 12,
LayerType::Embedding => self.input_size,
}
}
pub fn estimate_memory_mb(&self) -> usize {
let params = self.estimate_parameters();
let activations = self.output_size * 1000; (params + activations) * 4 / (1024 * 1024) }
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum LayerType {
Dense,
Attention,
Convolution,
GraphConv,
LSTM,
Transformer,
Embedding,
}
impl LayerType {
pub fn random(rng: &mut Random) -> Self {
match rng.random_range(0..7) {
0 => LayerType::Dense,
1 => LayerType::Attention,
2 => LayerType::Convolution,
3 => LayerType::GraphConv,
4 => LayerType::LSTM,
5 => LayerType::Transformer,
6 => LayerType::Embedding,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct LayerParameters {
pub activation: ActivationFunction,
pub dropout: f64,
pub normalization: NormalizationType,
pub settings: HashMap<String, f64>,
}
impl LayerParameters {
pub fn random(layer_type: &LayerType, rng: &mut Random) -> Self {
let activation = ActivationFunction::random(rng);
let dropout = rng.random_range(0.0..0.5);
let normalization = NormalizationType::random(rng);
let mut settings = HashMap::new();
match layer_type {
LayerType::Attention => {
settings.insert("num_heads".to_string(), rng.random_range(1..16) as f64);
settings.insert("head_dim".to_string(), rng.random_range(32..128) as f64);
}
LayerType::Convolution => {
settings.insert("kernel_size".to_string(), rng.random_range(1..7) as f64);
settings.insert("stride".to_string(), rng.random_range(1..3) as f64);
}
LayerType::LSTM => {
settings.insert(
"bidirectional".to_string(),
if rng.random_bool_with_chance(0.5) {
1.0
} else {
0.0
},
);
}
_ => {}
}
Self {
activation,
dropout,
normalization,
settings,
}
}
pub fn mutate(&mut self, rng: &mut Random) {
match rng.random_range(0..4) {
0 => self.activation = ActivationFunction::random(rng),
1 => self.dropout = rng.random_range(0.0..0.5),
2 => self.normalization = NormalizationType::random(rng),
3 => {
for value in self.settings.values_mut() {
if rng.random_bool_with_chance(0.3) {
*value *= rng.random_range(0.8..1.2);
}
}
}
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum ActivationFunction {
ReLU,
GELU,
Swish,
Tanh,
Sigmoid,
LeakyReLU,
ELU,
}
impl ActivationFunction {
pub fn random(rng: &mut Random) -> Self {
match rng.random_range(0..7) {
0 => ActivationFunction::ReLU,
1 => ActivationFunction::GELU,
2 => ActivationFunction::Swish,
3 => ActivationFunction::Tanh,
4 => ActivationFunction::Sigmoid,
5 => ActivationFunction::LeakyReLU,
6 => ActivationFunction::ELU,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum NormalizationType {
None,
LayerNorm,
BatchNorm,
GroupNorm,
RMSNorm,
}
impl NormalizationType {
pub fn random(rng: &mut Random) -> Self {
match rng.random_range(0..5) {
0 => NormalizationType::None,
1 => NormalizationType::LayerNorm,
2 => NormalizationType::BatchNorm,
3 => NormalizationType::GroupNorm,
4 => NormalizationType::RMSNorm,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SkipConnection {
pub from_layer: usize,
pub to_layer: usize,
pub connection_type: SkipConnectionType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SkipConnectionType {
Add,
Concat,
Multiply,
Gate,
}
impl SkipConnectionType {
pub fn random(rng: &mut Random) -> Self {
match rng.random_range(0..4) {
0 => SkipConnectionType::Add,
1 => SkipConnectionType::Concat,
2 => SkipConnectionType::Multiply,
3 => SkipConnectionType::Gate,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureHyperparameters {
pub learning_rate: f64,
pub batch_size: usize,
pub weight_decay: f64,
pub gradient_clipping: f64,
pub optimizer: OptimizerType,
pub scheduler: SchedulerType,
}
impl ArchitectureHyperparameters {
pub fn random(rng: &mut Random) -> Self {
Self {
learning_rate: rng.random_f64() * (1e-2 - 1e-5) + 1e-5,
batch_size: {
let options = [16, 32, 64, 128, 256];
let idx = rng.random_range(0..options.len());
options[idx]
},
weight_decay: rng.random_f64() * (1e-3 - 1e-6) + 1e-6,
gradient_clipping: rng.random_f64() * (10.0 - 0.1) + 0.1,
optimizer: OptimizerType::random(rng),
scheduler: SchedulerType::random(rng),
}
}
pub fn mutate(&mut self, rng: &mut Random) {
match rng.random_range(0..6) {
0 => self.learning_rate *= rng.random_f64() * (2.0 - 0.5) + 0.5,
1 => {
let options = [16, 32, 64, 128, 256];
let idx = rng.random_range(0..options.len());
self.batch_size = options[idx];
}
2 => self.weight_decay *= rng.random_f64() * (2.0 - 0.5) + 0.5,
3 => self.gradient_clipping *= rng.random_f64() * (2.0 - 0.5) + 0.5,
4 => self.optimizer = OptimizerType::random(rng),
5 => self.scheduler = SchedulerType::random(rng),
_ => unreachable!(),
}
}
pub fn crossover(&self, other: &Self, rng: &mut Random) -> Self {
Self {
learning_rate: if rng.random_bool_with_chance(0.5) {
self.learning_rate
} else {
other.learning_rate
},
batch_size: if rng.random_bool_with_chance(0.5) {
self.batch_size
} else {
other.batch_size
},
weight_decay: if rng.random_bool_with_chance(0.5) {
self.weight_decay
} else {
other.weight_decay
},
gradient_clipping: if rng.random_bool_with_chance(0.5) {
self.gradient_clipping
} else {
other.gradient_clipping
},
optimizer: if rng.random_bool_with_chance(0.5) {
self.optimizer.clone()
} else {
other.optimizer.clone()
},
scheduler: if rng.random_bool_with_chance(0.5) {
self.scheduler.clone()
} else {
other.scheduler.clone()
},
}
}
pub fn distance(&self, other: &Self) -> f64 {
let lr_diff = (self.learning_rate - other.learning_rate).abs()
/ self.learning_rate.max(other.learning_rate);
let batch_diff = (self.batch_size as f64 - other.batch_size as f64).abs()
/ (self.batch_size as f64).max(other.batch_size as f64);
let wd_diff = (self.weight_decay - other.weight_decay).abs()
/ self.weight_decay.max(other.weight_decay);
(lr_diff + batch_diff + wd_diff) / 3.0
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OptimizerType {
Adam,
AdamW,
SGD,
RMSprop,
AdaGrad,
}
impl OptimizerType {
pub fn random(rng: &mut Random) -> Self {
match rng.random_range(0..5) {
0 => OptimizerType::Adam,
1 => OptimizerType::AdamW,
2 => OptimizerType::SGD,
3 => OptimizerType::RMSprop,
4 => OptimizerType::AdaGrad,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum SchedulerType {
Constant,
Linear,
Cosine,
Exponential,
StepLR,
}
impl SchedulerType {
pub fn random(rng: &mut Random) -> Self {
match rng.random_range(0..5) {
0 => SchedulerType::Constant,
1 => SchedulerType::Linear,
2 => SchedulerType::Cosine,
3 => SchedulerType::Exponential,
4 => SchedulerType::StepLR,
_ => unreachable!(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
pub accuracy: f64,
pub inference_time_ms: f64,
pub memory_usage_mb: f64,
pub parameter_count: usize,
pub flops: usize,
pub generalization_score: f64,
pub robustness_score: f64,
pub multi_objective_score: f64,
}
impl PerformanceMetrics {
pub fn calculate_fitness(&self, weights: &ObjectiveWeights, complexity_penalty: f64) -> f64 {
let accuracy_score = self.accuracy;
let efficiency_score = 1.0 / (1.0 + self.inference_time_ms / 100.0); let memory_score = 1.0 / (1.0 + self.memory_usage_mb / 1000.0); let generalization_score = self.generalization_score;
let robustness_score = self.robustness_score;
let weighted_score = weights.accuracy * accuracy_score
+ weights.efficiency * efficiency_score
+ weights.memory * memory_score
+ weights.generalization * generalization_score
+ weights.robustness * robustness_score;
let complexity_factor =
1.0 / (1.0 + complexity_penalty * self.parameter_count as f64 / 1e6);
weighted_score * complexity_factor
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ArchitectureComplexity {
pub parameters: usize,
pub flops: usize,
pub memory_mb: usize,
pub depth: usize,
pub width: usize,
}
#[derive(Debug, Clone)]
pub struct Population {
pub individuals: Vec<NeuralArchitecture>,
pub generation: usize,
pub best_fitness: f64,
pub average_fitness: f64,
pub diversity_score: f64,
}
impl Population {
pub fn initialize(config: &NeuroEvolutionConfig) -> Self {
let mut rng = Random::default();
let mut individuals = Vec::new();
for _ in 0..config.population_size {
individuals.push(NeuralArchitecture::random(config, &mut rng));
}
Self {
individuals,
generation: 0,
best_fitness: 0.0,
average_fitness: 0.0,
diversity_score: 0.0,
}
}
pub async fn evaluate(&mut self, evaluator: &ArchitectureEvaluator) -> Result<()> {
let mut total_fitness = 0.0;
let mut best_fitness: f64 = 0.0;
for individual in &mut self.individuals {
let performance = evaluator.evaluate(individual).await?;
individual.performance = Some(performance.clone());
let fitness = performance.calculate_fitness(
&evaluator.config.objective_weights,
evaluator.config.complexity_penalty,
);
total_fitness += fitness;
best_fitness = best_fitness.max(fitness);
}
self.best_fitness = best_fitness;
self.average_fitness = total_fitness / self.individuals.len() as f64;
self.diversity_score = self.calculate_diversity();
Ok(())
}
fn calculate_diversity(&self) -> f64 {
let mut total_distance = 0.0;
let mut count = 0;
for i in 0..self.individuals.len() {
for j in (i + 1)..self.individuals.len() {
total_distance += self.individuals[i].diversity_distance(&self.individuals[j]);
count += 1;
}
}
if count > 0 {
total_distance / count as f64
} else {
0.0
}
}
pub fn evolve(&mut self, config: &NeuroEvolutionConfig) -> Result<()> {
let mut rng = Random::default();
self.individuals.sort_by(|a, b| {
let fitness_a = a
.performance
.as_ref()
.expect("performance should be evaluated before evolve")
.calculate_fitness(&config.objective_weights, config.complexity_penalty);
let fitness_b = b
.performance
.as_ref()
.expect("performance should be evaluated before evolve")
.calculate_fitness(&config.objective_weights, config.complexity_penalty);
fitness_b
.partial_cmp(&fitness_a)
.expect("fitness values should be finite")
});
let mut new_population = Vec::new();
let elite_count = (config.population_size as f64 * config.elite_ratio) as usize;
for i in 0..elite_count {
new_population.push(self.individuals[i].clone());
}
while new_population.len() < config.population_size {
if rng.random_bool_with_chance(config.crossover_rate) {
let parent1 = self.tournament_selection(config, &mut rng);
let parent2 = self.tournament_selection(config, &mut rng);
let (mut child1, mut child2) = parent1.crossover(parent2, &mut rng);
child1.mutate(config, &mut rng);
child2.mutate(config, &mut rng);
new_population.push(child1);
if new_population.len() < config.population_size {
new_population.push(child2);
}
} else {
let parent = self.tournament_selection(config, &mut rng);
let mut child = parent.clone();
child.id = Uuid::new_v4();
child.mutate(config, &mut rng);
new_population.push(child);
}
}
self.individuals = new_population;
self.generation += 1;
Ok(())
}
fn tournament_selection(
&self,
config: &NeuroEvolutionConfig,
rng: &mut Random,
) -> &NeuralArchitecture {
let mut best = &self.individuals[0];
let mut best_fitness = 0.0;
for _ in 0..config.tournament_size {
let candidate_idx = rng.random_range(0..self.individuals.len());
let candidate = &self.individuals[candidate_idx];
if let Some(ref performance) = candidate.performance {
let fitness = performance
.calculate_fitness(&config.objective_weights, config.complexity_penalty);
if fitness > best_fitness {
best = candidate;
best_fitness = fitness;
}
}
}
best
}
pub fn get_best(&self) -> Option<&NeuralArchitecture> {
self.individuals.first()
}
}
#[derive(Debug, Clone)]
pub struct ArchitectureEvaluator {
pub config: NeuroEvolutionConfig,
pub evaluation_cache: HashMap<Uuid, PerformanceMetrics>,
}
impl ArchitectureEvaluator {
pub fn new(config: NeuroEvolutionConfig) -> Self {
Self {
config,
evaluation_cache: HashMap::new(),
}
}
pub async fn evaluate(&self, architecture: &NeuralArchitecture) -> Result<PerformanceMetrics> {
if let Some(cached) = self.evaluation_cache.get(&architecture.id) {
return Ok(cached.clone());
}
let metrics = self.simulate_evaluation(architecture)?;
Ok(metrics)
}
fn simulate_evaluation(&self, architecture: &NeuralArchitecture) -> Result<PerformanceMetrics> {
let mut rng = Random::default();
let base_accuracy = 0.7 + rng.random_range(0.0..0.2);
let complexity_factor = 1.0 / (1.0 + architecture.complexity.parameters as f64 / 1e6);
let accuracy = (base_accuracy * (0.8 + 0.4 * complexity_factor)).min(1.0);
let inference_time = 10.0 + architecture.complexity.parameters as f64 / 1e5;
let memory_usage = architecture.complexity.memory_mb as f64;
let generalization_score = (accuracy * (0.9 + 0.1 * rng.random_range(0.0..1.0))).min(1.0);
let robustness_score = (accuracy * (0.85 + 0.15 * rng.random_range(0.0..1.0))).min(1.0);
let metrics = PerformanceMetrics {
accuracy,
inference_time_ms: inference_time,
memory_usage_mb: memory_usage,
parameter_count: architecture.complexity.parameters,
flops: architecture.complexity.flops,
generalization_score,
robustness_score,
multi_objective_score: 0.0, };
Ok(metrics)
}
pub fn validate_constraints(&self, architecture: &NeuralArchitecture) -> bool {
let constraints = &self.config.hardware_constraints;
architecture.complexity.memory_mb <= constraints.max_memory_mb
&& architecture.complexity.parameters <= constraints.max_parameters
&& architecture.complexity.flops <= constraints.max_flops
}
}
#[derive(Debug, Clone)]
pub struct NeuroEvolutionSystem {
pub config: NeuroEvolutionConfig,
pub population: Population,
pub evaluator: ArchitectureEvaluator,
pub evolution_history: Vec<EvolutionStats>,
}
impl NeuroEvolutionSystem {
pub fn new(config: NeuroEvolutionConfig) -> Self {
let population = Population::initialize(&config);
let evaluator = ArchitectureEvaluator::new(config.clone());
Self {
config,
population,
evaluator,
evolution_history: Vec::new(),
}
}
pub async fn evolve(&mut self) -> Result<NeuralArchitecture> {
for generation in 0..self.config.num_generations {
self.population.evaluate(&self.evaluator).await?;
let stats = EvolutionStats {
generation,
best_fitness: self.population.best_fitness,
average_fitness: self.population.average_fitness,
diversity_score: self.population.diversity_score,
best_architecture: self.population.get_best().cloned(),
};
self.evolution_history.push(stats);
if self.check_convergence() {
break;
}
if generation < self.config.num_generations - 1 {
self.population.evolve(&self.config)?;
}
}
self.population
.get_best()
.cloned()
.ok_or_else(|| anyhow::anyhow!("No best architecture found"))
}
fn check_convergence(&self) -> bool {
if self.evolution_history.len() < 10 {
return false;
}
let recent_best: Vec<f64> = self
.evolution_history
.iter()
.rev()
.take(10)
.map(|s| s.best_fitness)
.collect();
let improvement = recent_best[0] - recent_best[9];
improvement < 0.001 }
pub fn get_stats(&self) -> &[EvolutionStats] {
&self.evolution_history
}
}
#[derive(Debug, Clone)]
pub struct EvolutionStats {
pub generation: usize,
pub best_fitness: f64,
pub average_fitness: f64,
pub diversity_score: f64,
pub best_architecture: Option<NeuralArchitecture>,
}
impl fmt::Display for EvolutionStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Gen {}: Best={:.4}, Avg={:.4}, Diversity={:.4}",
self.generation, self.best_fitness, self.average_fitness, self.diversity_score
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_neuro_evolution_config() {
let config = NeuroEvolutionConfig::default();
assert_eq!(config.population_size, 50);
assert_eq!(config.num_generations, 100);
assert!(config.mutation_rate > 0.0);
}
#[test]
fn test_neural_architecture_creation() {
let config = NeuroEvolutionConfig::default();
let mut rng = Random::default();
let arch = NeuralArchitecture::random(&config, &mut rng);
assert!(!arch.layers.is_empty());
assert!(arch.layers.len() <= config.max_depth);
assert!(arch.complexity.parameters > 0);
}
#[test]
fn test_architecture_mutation() {
let config = NeuroEvolutionConfig::default();
let mut rng = Random::default();
let mut arch = NeuralArchitecture::random(&config, &mut rng);
let original_id = arch.id;
arch.mutate(&config, &mut rng);
assert_eq!(arch.id, original_id); }
#[test]
fn test_architecture_crossover() {
let config = NeuroEvolutionConfig::default();
let mut rng = Random::default();
let parent1 = NeuralArchitecture::random(&config, &mut rng);
let parent2 = NeuralArchitecture::random(&config, &mut rng);
let (child1, child2) = parent1.crossover(&parent2, &mut rng);
assert_ne!(child1.id, parent1.id);
assert_ne!(child2.id, parent2.id);
assert_ne!(child1.id, child2.id);
}
#[test]
fn test_layer_parameter_estimation() {
let layer = ArchitectureLayer {
layer_type: LayerType::Dense,
input_size: 128,
output_size: 256,
parameters: LayerParameters {
activation: ActivationFunction::ReLU,
dropout: 0.1,
normalization: NormalizationType::LayerNorm,
settings: HashMap::new(),
},
};
let params = layer.estimate_parameters();
let expected = 128 * 256 + 256; assert_eq!(params, expected);
}
#[test]
fn test_population_initialization() {
let config = NeuroEvolutionConfig::default();
let population = Population::initialize(&config);
assert_eq!(population.individuals.len(), config.population_size);
assert_eq!(population.generation, 0);
}
#[test]
fn test_diversity_calculation() {
let config = NeuroEvolutionConfig::default();
let mut rng = Random::default();
let arch1 = NeuralArchitecture::random(&config, &mut rng);
let arch2 = NeuralArchitecture::random(&config, &mut rng);
let distance = arch1.diversity_distance(&arch2);
assert!((0.0..=1.0).contains(&distance));
}
#[tokio::test]
async fn test_architecture_evaluation() {
let config = NeuroEvolutionConfig::default();
let evaluator = ArchitectureEvaluator::new(config.clone());
let mut rng = Random::default();
let arch = NeuralArchitecture::random(&config, &mut rng);
let metrics = evaluator.evaluate(&arch).await.expect("should succeed");
assert!(metrics.accuracy >= 0.0 && metrics.accuracy <= 1.0);
assert!(metrics.inference_time_ms > 0.0);
}
#[test]
fn test_hardware_constraints() {
let config = NeuroEvolutionConfig::default();
let evaluator = ArchitectureEvaluator::new(config.clone());
let mut rng = Random::default();
let arch = NeuralArchitecture::random(&config, &mut rng);
let is_valid = evaluator.validate_constraints(&arch);
assert!(
is_valid || arch.complexity.parameters > config.hardware_constraints.max_parameters
);
}
#[tokio::test]
async fn test_neuro_evolution_system() {
let config = NeuroEvolutionConfig {
population_size: 5, num_generations: 2, max_depth: 3, max_width: 16, ..Default::default()
};
let mut system = NeuroEvolutionSystem::new(config);
let best_arch = system.evolve().await.expect("should succeed");
assert!(!best_arch.layers.is_empty());
assert!(system.evolution_history.len() <= 2);
}
}