use crate::{EmbeddingModel, ModelConfig, ModelStats, Vector};
use anyhow::{anyhow, Result};
use scirs2_core::ndarray_ext::{Array1, Array2, Array3, Axis};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet, VecDeque};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tokio::sync::RwLock;
use tracing::{debug, info, warn, error};
use uuid::Uuid;
use scirs2_core::random::{seq::SliceRandom, rng, Random};
pub struct EvolutionaryNAS {
config: EvolutionaryConfig,
population: Arc<RwLock<Vec<ArchitectureCandidate>>>,
evolution_history: EvolutionHistory,
fitness_evaluator: FitnessEvaluator,
genetic_operators: GeneticOperators,
diversity_manager: DiversityManager,
hardware_optimizer: HardwareOptimizer,
performance_cache: Arc<RwLock<HashMap<String, PerformanceMetrics>>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvolutionaryConfig {
pub population_size: usize,
pub max_generations: usize,
pub elite_percentage: f32,
pub tournament_size: usize,
pub crossover_probability: f32,
pub mutation_probability: f32,
pub diversity_strength: f32,
pub objective_weights: ObjectiveWeights,
pub target_hardware: HardwareTarget,
pub progressive_config: ProgressiveConfig,
}
impl Default for EvolutionaryConfig {
fn default() -> Self {
Self {
population_size: 50,
max_generations: 100,
elite_percentage: 0.1,
tournament_size: 5,
crossover_probability: 0.8,
mutation_probability: 0.1,
diversity_strength: 0.3,
objective_weights: ObjectiveWeights::default(),
target_hardware: HardwareTarget::default(),
progressive_config: ProgressiveConfig::default(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ObjectiveWeights {
pub accuracy_weight: f32,
pub efficiency_weight: f32,
pub memory_weight: f32,
pub simplicity_weight: f32,
pub novelty_weight: f32,
}
impl Default for ObjectiveWeights {
fn default() -> Self {
Self {
accuracy_weight: 0.4,
efficiency_weight: 0.3,
memory_weight: 0.15,
simplicity_weight: 0.1,
novelty_weight: 0.05,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum HardwareTarget {
HighPerformanceGPU {
gpu_memory_gb: f32,
compute_capability: f32,
parallelism_factor: f32,
},
EdgeDevice {
cpu_cores: usize,
memory_mb: f32,
power_budget_watts: f32,
},
CloudDeployment {
instance_type: String,
cost_per_hour: f32,
scaling_factor: f32,
},
NeuromorphicChip {
neuron_count: usize,
synapse_count: usize,
spike_rate_khz: f32,
},
}
impl Default for HardwareTarget {
fn default() -> Self {
Self::HighPerformanceGPU {
gpu_memory_gb: 16.0,
compute_capability: 8.0,
parallelism_factor: 1.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProgressiveConfig {
pub start_complexity: usize,
pub max_complexity: usize,
pub complexity_increase_rate: f32,
pub enable_modular_building: bool,
pub enable_module_library: bool,
}
impl Default for ProgressiveConfig {
fn default() -> Self {
Self {
start_complexity: 3,
max_complexity: 20,
complexity_increase_rate: 0.1,
enable_modular_building: true,
enable_module_library: true,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureCandidate {
pub id: Uuid,
pub genome: ArchitectureGenome,
pub fitness: FitnessScores,
pub performance: Option<PerformanceMetrics>,
pub generation: usize,
pub parents: Vec<Uuid>,
pub novelty_score: f32,
pub hardware_metrics: HardwareMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchitectureGenome {
pub nodes: Vec<NodeGene>,
pub connections: Vec<ConnectionGene>,
pub global_params: GlobalParameters,
pub modules: Vec<ModuleDefinition>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NodeGene {
pub id: usize,
pub operation: OperationType,
pub parameters: HashMap<String, f32>,
pub active: bool,
pub innovation_number: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionGene {
pub from_node: usize,
pub to_node: usize,
pub weight: f32,
pub active: bool,
pub innovation_number: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum OperationType {
Linear { input_dim: usize, output_dim: usize },
Convolution { filters: usize, kernel_size: usize },
GraphConv { channels: usize, aggregation: String },
Attention { heads: usize, embed_dim: usize },
Transformer { layers: usize, heads: usize },
Embedding { vocab_size: usize, embed_dim: usize },
Activation { function: String },
Normalization { method: String },
Dropout { rate: f32 },
SkipConnection,
Pooling { method: String, size: usize },
Custom { operation_id: String, params: HashMap<String, f32> },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GlobalParameters {
pub learning_rate: f32,
pub optimizer: String,
pub regularization: f32,
pub batch_size: usize,
pub epochs: usize,
}
impl Default for GlobalParameters {
fn default() -> Self {
Self {
learning_rate: 0.001,
optimizer: "adam".to_string(),
regularization: 0.01,
batch_size: 32,
epochs: 100,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleDefinition {
pub id: String,
pub nodes: Vec<NodeGene>,
pub connections: Vec<ConnectionGene>,
pub interface: ModuleInterface,
pub characteristics: ModuleCharacteristics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleInterface {
pub input_dim: usize,
pub output_dim: usize,
pub input_types: Vec<String>,
pub output_types: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ModuleCharacteristics {
pub computational_cost: f64,
pub memory_cost: f64,
pub accuracy_contribution: f32,
pub stability: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FitnessScores {
pub overall_fitness: f32,
pub accuracy: f32,
pub efficiency: f32,
pub memory_efficiency: f32,
pub simplicity: f32,
pub novelty: f32,
pub hardware_compatibility: f32,
pub pareto_rank: usize,
pub crowding_distance: f32,
}
impl Default for FitnessScores {
fn default() -> Self {
Self {
overall_fitness: 0.0,
accuracy: 0.0,
efficiency: 0.0,
memory_efficiency: 0.0,
simplicity: 0.0,
novelty: 0.0,
hardware_compatibility: 0.0,
pareto_rank: 0,
crowding_distance: 0.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PerformanceMetrics {
pub training_accuracy: f32,
pub validation_accuracy: f32,
pub test_accuracy: Option<f32>,
pub training_time: f64,
pub inference_time_ms: f32,
pub memory_usage_mb: f32,
pub energy_consumption: Option<f32>,
pub model_size: usize,
pub flops: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HardwareMetrics {
pub gpu_utilization: f32,
pub memory_utilization: f32,
pub throughput: f32,
pub power_consumption: f32,
pub efficiency_score: f32,
}
impl Default for HardwareMetrics {
fn default() -> Self {
Self {
gpu_utilization: 0.0,
memory_utilization: 0.0,
throughput: 0.0,
power_consumption: 0.0,
efficiency_score: 0.0,
}
}
}
pub struct EvolutionHistory {
generation_stats: Vec<GenerationStatistics>,
hall_of_fame: VecDeque<ArchitectureCandidate>,
innovation_tracker: InnovationTracker,
convergence_metrics: ConvergenceMetrics,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationStatistics {
pub generation: usize,
pub best_fitness: f32,
pub average_fitness: f32,
pub fitness_std: f32,
pub diversity_score: f32,
pub new_innovations: usize,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
pub struct InnovationTracker {
next_innovation: usize,
innovation_history: HashMap<String, usize>,
innovation_fitness: HashMap<usize, f32>,
}
impl InnovationTracker {
pub fn new() -> Self {
Self {
next_innovation: 1,
innovation_history: HashMap::new(),
innovation_fitness: HashMap::new(),
}
}
pub fn get_innovation_number(&mut self, innovation_key: &str) -> usize {
if let Some(&innovation) = self.innovation_history.get(innovation_key) {
innovation
} else {
let innovation = self.next_innovation;
self.next_innovation += 1;
self.innovation_history.insert(innovation_key.to_string(), innovation);
innovation
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvergenceMetrics {
pub improvement_rate: f32,
pub stagnation_count: usize,
pub diversity_trend: Vec<f32>,
pub convergence_probability: f32,
}
pub struct FitnessEvaluator {
datasets: HashMap<String, EvaluationDataset>,
hardware_profiler: HardwareProfiler,
evaluation_cache: Arc<RwLock<HashMap<String, PerformanceMetrics>>>,
}
#[derive(Debug, Clone)]
pub struct EvaluationDataset {
pub name: String,
pub train_triples: Vec<(String, String, String)>,
pub val_triples: Vec<(String, String, String)>,
pub test_triples: Option<Vec<(String, String, String)>>,
pub entity_vocab: HashSet<String>,
pub relation_vocab: HashSet<String>,
}
pub struct HardwareProfiler {
target_hardware: HardwareTarget,
profiling_history: Vec<ProfilingResult>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProfilingResult {
pub architecture_id: Uuid,
pub hardware_metrics: HardwareMetrics,
pub timestamp: chrono::DateTime<chrono::Utc>,
pub duration: Duration,
}
pub struct GeneticOperators {
crossover_ops: Vec<Box<dyn CrossoverOperator>>,
mutation_ops: Vec<Box<dyn MutationOperator>>,
selection_ops: Vec<Box<dyn SelectionOperator>>,
}
pub trait CrossoverOperator: Send + Sync {
fn crossover(
&self,
parent1: &ArchitectureCandidate,
parent2: &ArchitectureCandidate,
innovation_tracker: &mut InnovationTracker,
) -> Result<(ArchitectureCandidate, ArchitectureCandidate)>;
}
pub trait MutationOperator: Send + Sync {
fn mutate(
&self,
candidate: &mut ArchitectureCandidate,
innovation_tracker: &mut InnovationTracker,
mutation_rate: f32,
) -> Result<()>;
}
pub trait SelectionOperator: Send + Sync {
fn select(
&self,
population: &[ArchitectureCandidate],
selection_size: usize,
) -> Vec<usize>;
}
pub struct DiversityManager {
diversity_metrics: DiversityMetrics,
novelty_archive: Vec<ArchitectureCandidate>,
strategies: Vec<Box<dyn DiversityStrategy>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DiversityMetrics {
pub genotypic_diversity: f32,
pub phenotypic_diversity: f32,
pub novelty_distribution: Vec<f32>,
pub population_entropy: f32,
}
pub trait DiversityStrategy: Send + Sync {
fn maintain_diversity(
&self,
population: &mut Vec<ArchitectureCandidate>,
diversity_target: f32,
) -> Result<()>;
}
pub struct HardwareOptimizer {
target_hardware: HardwareTarget,
optimization_strategies: Vec<Box<dyn HardwareOptimizationStrategy>>,
performance_models: HashMap<String, Box<dyn PerformanceModel>>,
}
pub trait HardwareOptimizationStrategy: Send + Sync {
fn optimize_for_hardware(
&self,
genome: &mut ArchitectureGenome,
target_hardware: &HardwareTarget,
) -> Result<OptimizationResult>;
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptimizationResult {
pub performance_improvement: f32,
pub efficiency_gain: f32,
pub confidence: f32,
pub modifications: Vec<String>,
}
pub trait PerformanceModel: Send + Sync {
fn predict_performance(
&self,
genome: &ArchitectureGenome,
hardware: &HardwareTarget,
) -> Result<PerformanceMetrics>;
}
impl EvolutionaryNAS {
pub fn new(config: EvolutionaryConfig) -> Result<Self> {
let population = Arc::new(RwLock::new(Vec::new()));
let performance_cache = Arc::new(RwLock::new(HashMap::new()));
let evolution_history = EvolutionHistory {
generation_stats: Vec::new(),
hall_of_fame: VecDeque::new(),
innovation_tracker: InnovationTracker::new(),
convergence_metrics: ConvergenceMetrics {
improvement_rate: 0.0,
stagnation_count: 0,
diversity_trend: Vec::new(),
convergence_probability: 0.0,
},
};
let fitness_evaluator = FitnessEvaluator {
datasets: HashMap::new(),
hardware_profiler: HardwareProfiler {
target_hardware: config.target_hardware.clone(),
profiling_history: Vec::new(),
},
evaluation_cache: performance_cache.clone(),
};
let genetic_operators = GeneticOperators {
crossover_ops: Vec::new(),
mutation_ops: Vec::new(),
selection_ops: Vec::new(),
};
let diversity_manager = DiversityManager {
diversity_metrics: DiversityMetrics {
genotypic_diversity: 0.0,
phenotypic_diversity: 0.0,
novelty_distribution: Vec::new(),
population_entropy: 0.0,
},
novelty_archive: Vec::new(),
strategies: Vec::new(),
};
let hardware_optimizer = HardwareOptimizer {
target_hardware: config.target_hardware.clone(),
optimization_strategies: Vec::new(),
performance_models: HashMap::new(),
};
Ok(Self {
config,
population,
evolution_history,
fitness_evaluator,
genetic_operators,
diversity_manager,
hardware_optimizer,
performance_cache,
})
}
pub async fn initialize_population(&mut self) -> Result<()> {
info!("Initializing population with {} candidates", self.config.population_size);
let mut population = self.population.write().await;
population.clear();
for i in 0..self.config.population_size {
let candidate = self.generate_random_candidate(i)?;
population.push(candidate);
}
info!("Population initialized successfully");
Ok(())
}
fn generate_random_candidate(&mut self, index: usize) -> Result<ArchitectureCandidate> {
let mut random = Random::default();
let base_complexity = self.config.progressive_config.start_complexity;
let complexity_variance = 2;
let num_nodes = base_complexity + random.random_range(0..complexity_variance);
let mut nodes = Vec::new();
let mut connections = Vec::new();
for i in 0..num_nodes {
let operation = self.generate_random_operation(&mut random)?;
let node = NodeGene {
id: i,
operation,
parameters: self.generate_random_parameters(&mut random),
active: true,
innovation_number: self.evolution_history.innovation_tracker
.get_innovation_number(&format!("node_{}", i)),
};
nodes.push(node);
}
let num_connections = random.random_range(num_nodes..num_nodes * 2);
for i in 0..num_connections {
if nodes.len() >= 2 {
let from_node = random.random_range(0..nodes.len() - 1);
let to_node = random.random_range(from_node + 1..nodes.len());
let connection = ConnectionGene {
from_node,
to_node,
weight: random.random_range(-1.0..1.0),
active: true,
innovation_number: self.evolution_history.innovation_tracker
.get_innovation_number(&format!("conn_{}_{}", from_node, to_node)),
};
connections.push(connection);
}
}
let genome = ArchitectureGenome {
nodes,
connections,
global_params: GlobalParameters::default(),
modules: Vec::new(),
};
Ok(ArchitectureCandidate {
id: Uuid::new_v4(),
genome,
fitness: FitnessScores::default(),
performance: None,
generation: 0,
parents: Vec::new(),
novelty_score: 0.0,
hardware_metrics: HardwareMetrics::default(),
})
}
fn generate_random_operation(&self, random: &mut Random) -> Result<OperationType> {
let operations = vec![
OperationType::Linear { input_dim: 128, output_dim: 128 },
OperationType::GraphConv { channels: 64, aggregation: "mean".to_string() },
OperationType::Attention { heads: 8, embed_dim: 128 },
OperationType::Activation { function: "relu".to_string() },
OperationType::Normalization { method: "batch_norm".to_string() },
OperationType::Dropout { rate: 0.1 },
OperationType::SkipConnection,
];
Ok(operations.choose(random).expect("operations should not be empty").clone())
}
fn generate_random_parameters(&self, random: &mut Random) -> HashMap<String, f32> {
let mut params = HashMap::new();
params.insert("learning_rate".to_string(), random.random_range(0.0001..0.01));
params.insert("dropout_rate".to_string(), random.random_range(0.0..0.5));
params.insert("weight_decay".to_string(), random.random_range(0.0..0.01));
params
}
pub async fn evolve(&mut self) -> Result<ArchitectureCandidate> {
info!("Starting evolutionary optimization for {} generations", self.config.max_generations);
if self.population.read().await.is_empty() {
self.initialize_population().await?;
}
let mut best_candidate: Option<ArchitectureCandidate> = None;
for generation in 0..self.config.max_generations {
info!("Generation {}/{}", generation + 1, self.config.max_generations);
self.evaluate_population().await?;
let gen_stats = self.calculate_generation_statistics(generation).await?;
self.evolution_history.generation_stats.push(gen_stats);
let current_best = self.get_best_candidate().await?;
if best_candidate.is_none() ||
current_best.fitness.overall_fitness > best_candidate.as_ref().expect("best_candidate should be set").fitness.overall_fitness {
best_candidate = Some(current_best);
info!("New best fitness: {:.4}", best_candidate.as_ref().expect("best_candidate should be set").fitness.overall_fitness);
}
if self.check_convergence(generation).await? {
info!("Convergence detected at generation {}", generation);
break;
}
self.evolve_next_generation().await?;
self.maintain_population_diversity().await?;
if self.config.progressive_config.enable_modular_building {
self.apply_progressive_complexification(generation).await?;
}
}
info!("Evolution completed");
best_candidate.ok_or_else(|| anyhow!("No best candidate found"))
}
async fn evaluate_population(&mut self) -> Result<()> {
let mut population = self.population.write().await;
for candidate in population.iter_mut() {
let genome_hash = self.calculate_genome_hash(&candidate.genome);
if let Some(cached_performance) = self.performance_cache.read().await.get(&genome_hash) {
candidate.performance = Some(cached_performance.clone());
} else {
let performance = self.evaluate_candidate_performance(candidate).await?;
candidate.performance = Some(performance.clone());
self.performance_cache.write().await.insert(genome_hash, performance);
}
candidate.fitness = self.calculate_fitness_scores(candidate)?;
}
self.calculate_pareto_ranking(&mut population)?;
Ok(())
}
fn calculate_genome_hash(&self, genome: &ArchitectureGenome) -> String {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
genome.nodes.len().hash(&mut hasher);
genome.connections.len().hash(&mut hasher);
format!("{:x}", hasher.finish())
}
async fn evaluate_candidate_performance(&self, candidate: &ArchitectureCandidate) -> Result<PerformanceMetrics> {
let mut random = Random::default();
Ok(PerformanceMetrics {
training_accuracy: random.random_range(0.7..0.95),
validation_accuracy: random.random_range(0.65..0.9),
test_accuracy: None,
training_time: random.random_range(100.0..1000.0),
inference_time_ms: random.random_range(0.1..10.0),
memory_usage_mb: random.random_range(100.0..2000.0),
energy_consumption: Some(random.random_range(10.0..100.0)),
model_size: random.random_range(1000000..50000000),
flops: random.random_range(1000000..100000000),
})
}
fn calculate_fitness_scores(&self, candidate: &ArchitectureCandidate) -> Result<FitnessScores> {
let performance = candidate.performance.as_ref()
.ok_or_else(|| anyhow!("No performance metrics available"))?;
let weights = &self.config.objective_weights;
let accuracy = performance.validation_accuracy;
let efficiency = 1.0 / (performance.inference_time_ms + 1.0); let memory_efficiency = 1.0 / (performance.memory_usage_mb / 1000.0 + 1.0); let simplicity = 1.0 / (candidate.genome.nodes.len() as f32 / 10.0 + 1.0); let novelty = candidate.novelty_score;
let hardware_compatibility = candidate.hardware_metrics.efficiency_score;
let overall_fitness =
weights.accuracy_weight * accuracy +
weights.efficiency_weight * efficiency +
weights.memory_weight * memory_efficiency +
weights.simplicity_weight * simplicity +
weights.novelty_weight * novelty;
Ok(FitnessScores {
overall_fitness,
accuracy,
efficiency,
memory_efficiency,
simplicity,
novelty,
hardware_compatibility,
pareto_rank: 0, crowding_distance: 0.0, })
}
fn calculate_pareto_ranking(&self, population: &mut [ArchitectureCandidate]) -> Result<()> {
let n = population.len();
let mut domination_count = vec![0; n];
let mut dominated_solutions = vec![Vec::new(); n];
let mut fronts = Vec::new();
let mut current_front = Vec::new();
for i in 0..n {
for j in 0..n {
if i != j {
if self.dominates(&population[i], &population[j]) {
dominated_solutions[i].push(j);
} else if self.dominates(&population[j], &population[i]) {
domination_count[i] += 1;
}
}
}
if domination_count[i] == 0 {
population[i].fitness.pareto_rank = 0;
current_front.push(i);
}
}
let mut front_number = 0;
while !current_front.is_empty() {
fronts.push(current_front.clone());
let mut next_front = Vec::new();
for &i in ¤t_front {
for &j in &dominated_solutions[i] {
domination_count[j] -= 1;
if domination_count[j] == 0 {
population[j].fitness.pareto_rank = front_number + 1;
next_front.push(j);
}
}
}
front_number += 1;
current_front = next_front;
}
for front in fronts {
self.calculate_crowding_distance(population, &front)?;
}
Ok(())
}
fn dominates(&self, a: &ArchitectureCandidate, b: &ArchitectureCandidate) -> bool {
let a_better =
a.fitness.accuracy >= b.fitness.accuracy &&
a.fitness.efficiency >= b.fitness.efficiency &&
a.fitness.memory_efficiency >= b.fitness.memory_efficiency &&
a.fitness.simplicity >= b.fitness.simplicity;
let a_strictly_better =
a.fitness.accuracy > b.fitness.accuracy ||
a.fitness.efficiency > b.fitness.efficiency ||
a.fitness.memory_efficiency > b.fitness.memory_efficiency ||
a.fitness.simplicity > b.fitness.simplicity;
a_better && a_strictly_better
}
fn calculate_crowding_distance(&self, population: &mut [ArchitectureCandidate], front: &[usize]) -> Result<()> {
let front_size = front.len();
if front_size <= 2 {
for &i in front {
population[i].fitness.crowding_distance = f32::INFINITY;
}
return Ok(());
}
for &i in front {
population[i].fitness.crowding_distance = 0.0;
}
let objectives = ["accuracy", "efficiency", "memory_efficiency", "simplicity"];
for objective in objectives {
let mut sorted_indices = front.to_vec();
sorted_indices.sort_by(|&a, &b| {
let val_a = self.get_objective_value(&population[a], objective);
let val_b = self.get_objective_value(&population[b], objective);
val_a.partial_cmp(&val_b).unwrap_or(std::cmp::Ordering::Equal)
});
population[sorted_indices[0]].fitness.crowding_distance = f32::INFINITY;
population[sorted_indices[front_size - 1]].fitness.crowding_distance = f32::INFINITY;
let obj_min = self.get_objective_value(&population[sorted_indices[0]], objective);
let obj_max = self.get_objective_value(&population[sorted_indices[front_size - 1]], objective);
let obj_range = obj_max - obj_min;
if obj_range > 0.0 {
for i in 1..front_size - 1 {
let next_obj = self.get_objective_value(&population[sorted_indices[i + 1]], objective);
let prev_obj = self.get_objective_value(&population[sorted_indices[i - 1]], objective);
population[sorted_indices[i]].fitness.crowding_distance +=
(next_obj - prev_obj) / obj_range;
}
}
}
Ok(())
}
fn get_objective_value(&self, candidate: &ArchitectureCandidate, objective: &str) -> f32 {
match objective {
"accuracy" => candidate.fitness.accuracy,
"efficiency" => candidate.fitness.efficiency,
"memory_efficiency" => candidate.fitness.memory_efficiency,
"simplicity" => candidate.fitness.simplicity,
_ => 0.0,
}
}
async fn calculate_generation_statistics(&self, generation: usize) -> Result<GenerationStatistics> {
let population = self.population.read().await;
let fitness_values: Vec<f32> = population.iter()
.map(|c| c.fitness.overall_fitness)
.collect();
let best_fitness = fitness_values.iter().fold(0.0f32, |a, &b| a.max(b));
let average_fitness = fitness_values.iter().sum::<f32>() / fitness_values.len() as f32;
let variance = fitness_values.iter()
.map(|&f| (f - average_fitness).powi(2))
.sum::<f32>() / fitness_values.len() as f32;
let fitness_std = variance.sqrt();
let diversity_score = self.calculate_population_diversity(&population)?;
Ok(GenerationStatistics {
generation,
best_fitness,
average_fitness,
fitness_std,
diversity_score,
new_innovations: 0, timestamp: chrono::Utc::now(),
})
}
fn calculate_population_diversity(&self, population: &[ArchitectureCandidate]) -> Result<f32> {
if population.len() < 2 {
return Ok(0.0);
}
let mut total_distance = 0.0;
let mut comparisons = 0;
for i in 0..population.len() {
for j in i + 1..population.len() {
let distance = self.calculate_genome_distance(&population[i].genome, &population[j].genome)?;
total_distance += distance;
comparisons += 1;
}
}
Ok(total_distance / comparisons as f32)
}
fn calculate_genome_distance(&self, genome1: &ArchitectureGenome, genome2: &ArchitectureGenome) -> Result<f32> {
let node_diff = (genome1.nodes.len() as f32 - genome2.nodes.len() as f32).abs();
let conn_diff = (genome1.connections.len() as f32 - genome2.connections.len() as f32).abs();
Ok((node_diff + conn_diff) / 10.0) }
async fn get_best_candidate(&self) -> Result<ArchitectureCandidate> {
let population = self.population.read().await;
population.iter()
.max_by(|a, b| a.fitness.overall_fitness.partial_cmp(&b.fitness.overall_fitness).unwrap_or(std::cmp::Ordering::Equal))
.cloned()
.ok_or_else(|| anyhow!("Empty population"))
}
async fn check_convergence(&self, generation: usize) -> Result<bool> {
if generation < 10 {
return Ok(false); }
let recent_stats = &self.evolution_history.generation_stats;
if recent_stats.len() < 10 {
return Ok(false);
}
let recent_best: Vec<f32> = recent_stats.iter()
.rev()
.take(10)
.map(|s| s.best_fitness)
.collect();
let improvement = recent_best[0] - recent_best[9];
Ok(improvement < 0.001) }
async fn evolve_next_generation(&mut self) -> Result<()> {
let mut current_population = self.population.write().await;
let mut new_population = Vec::new();
let elite_count = (current_population.len() as f32 * self.config.elite_percentage) as usize;
current_population.sort_by(|a, b|
b.fitness.overall_fitness.partial_cmp(&a.fitness.overall_fitness).unwrap_or(std::cmp::Ordering::Equal)
);
for i in 0..elite_count {
new_population.push(current_population[i].clone());
}
while new_population.len() < self.config.population_size {
let parent1_idx = self.tournament_selection(¤t_population)?;
let parent2_idx = self.tournament_selection(¤t_population)?;
let parent1 = ¤t_population[parent1_idx];
let parent2 = ¤t_population[parent2_idx];
let mut random = Random::default();
if random.random::<f32>() < self.config.crossover_probability {
let (mut child1, mut child2) = self.crossover(parent1, parent2)?;
if random.random::<f32>() < self.config.mutation_probability {
self.mutate(&mut child1)?;
}
if random.random::<f32>() < self.config.mutation_probability {
self.mutate(&mut child2)?;
}
new_population.push(child1);
if new_population.len() < self.config.population_size {
new_population.push(child2);
}
} else {
let mut child = parent1.clone();
child.id = Uuid::new_v4();
child.parents = vec![parent1.id];
if random.random::<f32>() < self.config.mutation_probability {
self.mutate(&mut child)?;
}
new_population.push(child);
}
}
*current_population = new_population;
Ok(())
}
fn tournament_selection(&self, population: &[ArchitectureCandidate]) -> Result<usize> {
let mut random = Random::default();
let mut best_idx = random.random_range(0..population.len());
let mut best_fitness = population[best_idx].fitness.overall_fitness;
for _ in 1..self.config.tournament_size {
let idx = random.random_range(0..population.len());
if population[idx].fitness.overall_fitness > best_fitness {
best_idx = idx;
best_fitness = population[idx].fitness.overall_fitness;
}
}
Ok(best_idx)
}
fn crossover(
&mut self,
parent1: &ArchitectureCandidate,
parent2: &ArchitectureCandidate,
) -> Result<(ArchitectureCandidate, ArchitectureCandidate)> {
let mut child1 = parent1.clone();
let mut child2 = parent2.clone();
child1.id = Uuid::new_v4();
child2.id = Uuid::new_v4();
child1.parents = vec![parent1.id, parent2.id];
child2.parents = vec![parent1.id, parent2.id];
let mut random = Random::default();
let crossover_point = random.random_range(1..parent1.genome.nodes.len().min(parent2.genome.nodes.len()));
for i in crossover_point..child1.genome.nodes.len().min(child2.genome.nodes.len()) {
std::mem::swap(&mut child1.genome.nodes[i], &mut child2.genome.nodes[i]);
}
child1.fitness = FitnessScores::default();
child2.fitness = FitnessScores::default();
child1.performance = None;
child2.performance = None;
Ok((child1, child2))
}
fn mutate(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
let mut random = Random::default();
for node in &mut candidate.genome.nodes {
if random.random::<f32>() < self.config.mutation_probability {
for (_, value) in node.parameters.iter_mut() {
*value *= random.random_range(0.8..1.2); }
}
}
for connection in &mut candidate.genome.connections {
if random.random::<f32>() < self.config.mutation_probability {
connection.weight += random.random_range(-0.1..0.1);
connection.weight = connection.weight.clamp(-2.0, 2.0);
}
}
if random.random::<f32>() < 0.05 { self.structural_mutation(candidate)?;
}
candidate.fitness = FitnessScores::default();
candidate.performance = None;
Ok(())
}
fn structural_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
let mut random = Random::default();
match random.random_range(0..4) {
0 => self.add_node_mutation(candidate)?,
1 => self.add_connection_mutation(candidate)?,
2 => self.remove_node_mutation(candidate)?,
3 => self.remove_connection_mutation(candidate)?,
_ => {}
}
Ok(())
}
fn add_node_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
let mut random = Random::default();
let new_id = candidate.genome.nodes.len();
let operation = self.generate_random_operation(&mut random)?;
let node = NodeGene {
id: new_id,
operation,
parameters: self.generate_random_parameters(&mut random),
active: true,
innovation_number: self.evolution_history.innovation_tracker
.get_innovation_number(&format!("node_{}", new_id)),
};
candidate.genome.nodes.push(node);
Ok(())
}
fn add_connection_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
let mut random = Random::default();
let num_nodes = candidate.genome.nodes.len();
if num_nodes >= 2 {
let from_node = random.random_range(0..num_nodes);
let to_node = random.random_range(0..num_nodes);
if from_node != to_node {
let connection = ConnectionGene {
from_node,
to_node,
weight: random.random_range(-1.0..1.0),
active: true,
innovation_number: self.evolution_history.innovation_tracker
.get_innovation_number(&format!("conn_{}_{}", from_node, to_node)),
};
candidate.genome.connections.push(connection);
}
}
Ok(())
}
fn remove_node_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
if candidate.genome.nodes.len() > 3 { let mut random = Random::default();
let remove_idx = random.random_range(0..candidate.genome.nodes.len());
candidate.genome.nodes.remove(remove_idx);
candidate.genome.connections.retain(|conn|
conn.from_node != remove_idx && conn.to_node != remove_idx
);
}
Ok(())
}
fn remove_connection_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
if !candidate.genome.connections.is_empty() {
let mut random = Random::default();
let remove_idx = random.random_range(0..candidate.genome.connections.len());
candidate.genome.connections.remove(remove_idx);
}
Ok(())
}
async fn maintain_population_diversity(&mut self) -> Result<()> {
let mut population = self.population.write().await;
for candidate in population.iter_mut() {
candidate.novelty_score = self.calculate_novelty_score(candidate, &population)?;
}
let mut to_remove = Vec::new();
for i in 0..population.len() {
for j in i + 1..population.len() {
let distance = self.calculate_genome_distance(
&population[i].genome,
&population[j].genome,
)?;
if distance < 0.1 { if population[i].fitness.overall_fitness < population[j].fitness.overall_fitness {
to_remove.push(i);
} else {
to_remove.push(j);
}
}
}
}
to_remove.sort();
to_remove.dedup();
to_remove.reverse();
for idx in to_remove {
if population.len() > self.config.population_size / 2 { population.remove(idx);
}
}
Ok(())
}
fn calculate_novelty_score(
&self,
candidate: &ArchitectureCandidate,
population: &[ArchitectureCandidate],
) -> Result<f32> {
let k = 15; let mut distances = Vec::new();
for other in population {
if other.id != candidate.id {
let distance = self.calculate_genome_distance(&candidate.genome, &other.genome)?;
distances.push(distance);
}
}
distances.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let novelty = if distances.len() >= k {
distances.iter().take(k).sum::<f32>() / k as f32
} else {
distances.iter().sum::<f32>() / distances.len().max(1) as f32
};
Ok(novelty)
}
async fn apply_progressive_complexification(&mut self, generation: usize) -> Result<()> {
let complexity_increase = self.config.progressive_config.complexity_increase_rate * generation as f32;
let max_nodes = (self.config.progressive_config.start_complexity as f32 + complexity_increase) as usize;
let max_nodes = max_nodes.min(self.config.progressive_config.max_complexity);
let mut population = self.population.write().await;
for candidate in population.iter_mut() {
let mut random = Random::default();
if candidate.genome.nodes.len() < max_nodes && random.random::<f32>() < 0.1 {
self.add_node_mutation(candidate)?;
}
}
Ok(())
}
pub fn get_evolution_statistics(&self) -> &[GenerationStatistics] {
&self.evolution_history.generation_stats
}
pub async fn export_best_architectures(&self, count: usize) -> Result<Vec<ArchitectureCandidate>> {
let mut population = self.population.read().await.clone();
population.sort_by(|a, b|
b.fitness.overall_fitness.partial_cmp(&a.fitness.overall_fitness).unwrap_or(std::cmp::Ordering::Equal)
);
Ok(population.into_iter().take(count).collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_evolutionary_nas_creation() {
let config = EvolutionaryConfig::default();
let nas = EvolutionaryNAS::new(config);
assert!(nas.is_ok());
}
#[tokio::test]
async fn test_population_initialization() {
let config = EvolutionaryConfig {
population_size: 10,
..Default::default()
};
let mut nas = EvolutionaryNAS::new(config).expect("should succeed");
let result = nas.initialize_population().await;
assert!(result.is_ok());
let population = nas.population.read().await;
assert_eq!(population.len(), 10);
}
#[tokio::test]
async fn test_genome_distance_calculation() {
let config = EvolutionaryConfig::default();
let mut nas = EvolutionaryNAS::new(config).expect("should succeed");
let candidate1 = nas.generate_random_candidate(0).expect("should succeed");
let candidate2 = nas.generate_random_candidate(1).expect("should succeed");
let distance = nas.calculate_genome_distance(&candidate1.genome, &candidate2.genome);
assert!(distance.is_ok());
assert!(distance.expect("should succeed") >= 0.0);
}
#[tokio::test]
async fn test_fitness_calculation() {
let config = EvolutionaryConfig::default();
let nas = EvolutionaryNAS::new(config).expect("should succeed");
let mut candidate = ArchitectureCandidate {
id: Uuid::new_v4(),
genome: ArchitectureGenome {
nodes: Vec::new(),
connections: Vec::new(),
global_params: GlobalParameters::default(),
modules: Vec::new(),
},
fitness: FitnessScores::default(),
performance: Some(PerformanceMetrics {
training_accuracy: 0.85,
validation_accuracy: 0.82,
test_accuracy: None,
training_time: 300.0,
inference_time_ms: 2.5,
memory_usage_mb: 500.0,
energy_consumption: Some(50.0),
model_size: 1000000,
flops: 5000000,
}),
generation: 0,
parents: Vec::new(),
novelty_score: 0.5,
hardware_metrics: HardwareMetrics::default(),
};
let fitness = nas.calculate_fitness_scores(&candidate);
assert!(fitness.is_ok());
assert!(fitness.expect("should succeed").overall_fitness > 0.0);
}
#[tokio::test]
async fn test_tournament_selection() {
let config = EvolutionaryConfig::default();
let nas = EvolutionaryNAS::new(config).expect("should succeed");
let mut population = Vec::new();
for i in 0..10 {
let mut candidate = ArchitectureCandidate {
id: Uuid::new_v4(),
genome: ArchitectureGenome {
nodes: Vec::new(),
connections: Vec::new(),
global_params: GlobalParameters::default(),
modules: Vec::new(),
},
fitness: FitnessScores {
overall_fitness: i as f32 * 0.1,
..Default::default()
},
performance: None,
generation: 0,
parents: Vec::new(),
novelty_score: 0.0,
hardware_metrics: HardwareMetrics::default(),
};
population.push(candidate);
}
let selected = nas.tournament_selection(&population);
assert!(selected.is_ok());
assert!(selected.expect("should succeed") < population.len());
}
}