Skip to main content

oxirs_embed/
evolutionary_nas_evolution.rs

1//! Evolutionary NAS — Evolution
2//!
3//! Evolutionary loop: selection (tournament/roulette), crossover, mutation, and population
4//! management.
5
6use crate::evolutionary_nas_types::{
7    ArchitectureCandidate, ArchitectureGenome, ConnectionGene, ConvergenceMetrics,
8    DiversityMetrics, EvolutionaryConfig, FitnessScores, GenerationStatistics, GlobalParameters,
9    HardwareMetrics, HardwareTarget, InnovationTracker, NodeGene, OperationType,
10    PerformanceMetrics,
11};
12use anyhow::{anyhow, Result};
13use scirs2_core::random::{Random, RngExt};
14use std::collections::{HashMap, VecDeque};
15use std::sync::Arc;
16use tokio::sync::RwLock;
17use tracing::info;
18use uuid::Uuid;
19
20// ── Traits ────────────────────────────────────────────────────────────────────
21
22/// Trait for crossover operators
23pub trait CrossoverOperator: Send + Sync {
24    fn crossover(
25        &self,
26        parent1: &ArchitectureCandidate,
27        parent2: &ArchitectureCandidate,
28        innovation_tracker: &mut InnovationTracker,
29    ) -> Result<(ArchitectureCandidate, ArchitectureCandidate)>;
30}
31
32/// Trait for mutation operators
33pub trait MutationOperator: Send + Sync {
34    fn mutate(
35        &self,
36        candidate: &mut ArchitectureCandidate,
37        innovation_tracker: &mut InnovationTracker,
38        mutation_rate: f32,
39    ) -> Result<()>;
40}
41
42/// Trait for selection operators
43pub trait SelectionOperator: Send + Sync {
44    fn select(&self, population: &[ArchitectureCandidate], selection_size: usize) -> Vec<usize>;
45}
46
47/// Trait for diversity maintenance strategies
48pub trait DiversityStrategy: Send + Sync {
49    fn maintain_diversity(
50        &self,
51        population: &mut Vec<ArchitectureCandidate>,
52        diversity_target: f32,
53    ) -> Result<()>;
54}
55
56/// Trait for hardware optimization strategies
57pub trait HardwareOptimizationStrategy: Send + Sync {
58    fn optimize_for_hardware(
59        &self,
60        genome: &mut ArchitectureGenome,
61        target_hardware: &HardwareTarget,
62    ) -> Result<crate::evolutionary_nas_types::OptimizationResult>;
63}
64
65/// Trait for hardware performance models
66pub trait PerformanceModel: Send + Sync {
67    fn predict_performance(
68        &self,
69        genome: &ArchitectureGenome,
70        hardware: &HardwareTarget,
71    ) -> Result<PerformanceMetrics>;
72}
73
74// ── Internal sub-structures ───────────────────────────────────────────────────
75
76/// Evolution history tracking
77pub(crate) struct EvolutionHistory {
78    pub generation_stats: Vec<GenerationStatistics>,
79    pub hall_of_fame: VecDeque<ArchitectureCandidate>,
80    pub innovation_tracker: InnovationTracker,
81    pub convergence_metrics: ConvergenceMetrics,
82}
83
84/// Genetic operators container
85pub(crate) struct GeneticOperators {
86    pub crossover_ops: Vec<Box<dyn CrossoverOperator>>,
87    pub mutation_ops: Vec<Box<dyn MutationOperator>>,
88    pub selection_ops: Vec<Box<dyn SelectionOperator>>,
89}
90
91/// Population diversity manager
92pub(crate) struct DiversityManager {
93    pub diversity_metrics: DiversityMetrics,
94    pub novelty_archive: Vec<ArchitectureCandidate>,
95    pub strategies: Vec<Box<dyn DiversityStrategy>>,
96}
97
98/// Hardware-aware optimizer
99pub(crate) struct HardwareOptimizer {
100    pub target_hardware: HardwareTarget,
101    pub optimization_strategies: Vec<Box<dyn HardwareOptimizationStrategy>>,
102    pub performance_models: HashMap<String, Box<dyn PerformanceModel>>,
103}
104
105// ── EvolutionaryNAS ───────────────────────────────────────────────────────────
106
107/// Evolutionary Neural Architecture Search Engine
108pub struct EvolutionaryNAS {
109    pub(crate) config: EvolutionaryConfig,
110    pub population: Arc<RwLock<Vec<ArchitectureCandidate>>>,
111    pub(crate) evolution_history: EvolutionHistory,
112    pub(crate) fitness_evaluator: crate::evolutionary_nas_eval::FitnessEvaluator,
113    pub(crate) _genetic_operators: GeneticOperators,
114    pub(crate) _diversity_manager: DiversityManager,
115    pub(crate) _hardware_optimizer: HardwareOptimizer,
116    pub(crate) performance_cache: Arc<RwLock<HashMap<String, PerformanceMetrics>>>,
117}
118
119impl EvolutionaryNAS {
120    /// Create a new evolutionary NAS engine
121    pub fn new(config: EvolutionaryConfig) -> Result<Self> {
122        let population = Arc::new(RwLock::new(Vec::new()));
123        let performance_cache = Arc::new(RwLock::new(HashMap::new()));
124
125        let evolution_history = EvolutionHistory {
126            generation_stats: Vec::new(),
127            hall_of_fame: VecDeque::new(),
128            innovation_tracker: InnovationTracker::new(),
129            convergence_metrics: ConvergenceMetrics {
130                improvement_rate: 0.0,
131                stagnation_count: 0,
132                diversity_trend: Vec::new(),
133                convergence_probability: 0.0,
134            },
135        };
136
137        let fitness_evaluator = crate::evolutionary_nas_eval::FitnessEvaluator {
138            datasets: HashMap::new(),
139            hardware_profiler: crate::evolutionary_nas_eval::HardwareProfiler {
140                target_hardware: config.target_hardware.clone(),
141                profiling_history: Vec::new(),
142            },
143            evaluation_cache: performance_cache.clone(),
144        };
145
146        let _genetic_operators = GeneticOperators {
147            crossover_ops: Vec::new(),
148            mutation_ops: Vec::new(),
149            selection_ops: Vec::new(),
150        };
151
152        let _diversity_manager = DiversityManager {
153            diversity_metrics: DiversityMetrics {
154                genotypic_diversity: 0.0,
155                phenotypic_diversity: 0.0,
156                novelty_distribution: Vec::new(),
157                population_entropy: 0.0,
158            },
159            novelty_archive: Vec::new(),
160            strategies: Vec::new(),
161        };
162
163        let _hardware_optimizer = HardwareOptimizer {
164            target_hardware: config.target_hardware.clone(),
165            optimization_strategies: Vec::new(),
166            performance_models: HashMap::new(),
167        };
168
169        Ok(Self {
170            config,
171            population,
172            evolution_history,
173            fitness_evaluator,
174            _genetic_operators,
175            _diversity_manager,
176            _hardware_optimizer,
177            performance_cache,
178        })
179    }
180
181    /// Initialize the population with random architectures
182    pub async fn initialize_population(&mut self) -> Result<()> {
183        info!(
184            "Initializing population with {} candidates",
185            self.config.population_size
186        );
187        let mut candidates = Vec::with_capacity(self.config.population_size);
188        for i in 0..self.config.population_size {
189            candidates.push(self.generate_random_candidate(i)?);
190        }
191        let mut population = self.population.write().await;
192        population.clear();
193        population.extend(candidates);
194        info!("Population initialized successfully");
195        Ok(())
196    }
197
198    /// Generate a random architecture candidate
199    pub(crate) fn generate_random_candidate(
200        &mut self,
201        _index: usize,
202    ) -> Result<ArchitectureCandidate> {
203        let mut random = Random::default();
204
205        let base_complexity = self.config.progressive_config.start_complexity;
206        let num_nodes = base_complexity + random.random_range(0..2usize);
207
208        let mut nodes = Vec::new();
209        let mut connections = Vec::new();
210
211        for i in 0..num_nodes {
212            let operation = self.generate_random_operation(&mut random)?;
213            let node = NodeGene {
214                id: i,
215                operation,
216                parameters: self.generate_random_parameters(&mut random),
217                active: true,
218                innovation_number: self
219                    .evolution_history
220                    .innovation_tracker
221                    .get_innovation_number(&format!("node_{i}")),
222            };
223            nodes.push(node);
224        }
225
226        let num_connections = random.random_range(num_nodes..num_nodes * 2);
227        for _ in 0..num_connections {
228            if nodes.len() >= 2 {
229                let from_node = random.random_range(0..nodes.len() - 1);
230                let to_node = random.random_range(from_node + 1..nodes.len());
231                let connection = ConnectionGene {
232                    from_node,
233                    to_node,
234                    weight: random.random_range(-1.0f32..1.0f32),
235                    active: true,
236                    innovation_number: self
237                        .evolution_history
238                        .innovation_tracker
239                        .get_innovation_number(&format!("conn_{from_node}_{to_node}")),
240                };
241                connections.push(connection);
242            }
243        }
244
245        let genome = ArchitectureGenome {
246            nodes,
247            connections,
248            global_params: GlobalParameters::default(),
249            modules: Vec::new(),
250        };
251
252        Ok(ArchitectureCandidate {
253            id: Uuid::new_v4(),
254            genome,
255            fitness: FitnessScores::default(),
256            performance: None,
257            generation: 0,
258            parents: Vec::new(),
259            novelty_score: 0.0,
260            hardware_metrics: HardwareMetrics::default(),
261        })
262    }
263
264    /// Generate a random operation type
265    pub(crate) fn generate_random_operation(&self, random: &mut Random) -> Result<OperationType> {
266        let operation_count: usize = 7;
267        Ok(match random.random_range(0..operation_count) {
268            0 => OperationType::Linear {
269                input_dim: 128,
270                output_dim: 128,
271            },
272            1 => OperationType::GraphConv {
273                channels: 64,
274                aggregation: "mean".to_string(),
275            },
276            2 => OperationType::Attention {
277                heads: 8,
278                embed_dim: 128,
279            },
280            3 => OperationType::Activation {
281                function: "relu".to_string(),
282            },
283            4 => OperationType::Normalization {
284                method: "batch_norm".to_string(),
285            },
286            5 => OperationType::Dropout { rate: 0.1 },
287            _ => OperationType::SkipConnection,
288        })
289    }
290
291    /// Generate random parameters for an operation
292    pub(crate) fn generate_random_parameters(&self, random: &mut Random) -> HashMap<String, f32> {
293        let mut params = HashMap::new();
294        params.insert(
295            "learning_rate".to_string(),
296            random.random_range(0.0001f32..0.01f32),
297        );
298        params.insert(
299            "dropout_rate".to_string(),
300            random.random_range(0.0f32..0.5f32),
301        );
302        params.insert(
303            "weight_decay".to_string(),
304            random.random_range(0.0f32..0.01f32),
305        );
306        params
307    }
308
309    /// Run the evolutionary optimization process
310    pub async fn evolve(&mut self) -> Result<ArchitectureCandidate> {
311        info!(
312            "Starting evolutionary optimization for {} generations",
313            self.config.max_generations
314        );
315
316        if self.population.read().await.is_empty() {
317            self.initialize_population().await?;
318        }
319
320        let mut best_candidate: Option<ArchitectureCandidate> = None;
321
322        for generation in 0..self.config.max_generations {
323            self.evaluate_population().await?;
324
325            let gen_stats = self.calculate_generation_statistics(generation).await?;
326            self.evolution_history.generation_stats.push(gen_stats);
327
328            let current_best = self.get_best_candidate().await?;
329            if best_candidate.is_none()
330                || current_best.fitness.overall_fitness
331                    > best_candidate
332                        .as_ref()
333                        .expect("best_candidate should be set")
334                        .fitness
335                        .overall_fitness
336            {
337                best_candidate = Some(current_best);
338            }
339
340            if self.check_convergence(generation).await? {
341                info!("Convergence detected at generation {}", generation);
342                break;
343            }
344
345            self.evolve_next_generation().await?;
346            self.maintain_population_diversity().await?;
347
348            if self.config.progressive_config.enable_modular_building {
349                self.apply_progressive_complexification(generation).await?;
350            }
351        }
352
353        info!("Evolution completed");
354        best_candidate.ok_or_else(|| anyhow!("No best candidate found"))
355    }
356
357    async fn evaluate_population(&mut self) -> Result<()> {
358        let mut population = self.population.write().await;
359        for candidate in population.iter_mut() {
360            let genome_hash = Self::calculate_genome_hash_static(&candidate.genome);
361            if let Some(cached_performance) = self.performance_cache.read().await.get(&genome_hash)
362            {
363                candidate.performance = Some(cached_performance.clone());
364            } else {
365                let performance = self
366                    .fitness_evaluator
367                    .evaluate_candidate_performance(candidate)
368                    .await?;
369                candidate.performance = Some(performance.clone());
370                self.performance_cache
371                    .write()
372                    .await
373                    .insert(genome_hash, performance);
374            }
375            candidate.fitness = self.calculate_fitness_scores(candidate)?;
376        }
377        self.calculate_pareto_ranking(&mut population)?;
378        Ok(())
379    }
380
381    fn calculate_genome_hash_static(genome: &ArchitectureGenome) -> String {
382        use std::collections::hash_map::DefaultHasher;
383        use std::hash::{Hash, Hasher};
384        let mut hasher = DefaultHasher::new();
385        genome.nodes.len().hash(&mut hasher);
386        genome.connections.len().hash(&mut hasher);
387        format!("{:x}", hasher.finish())
388    }
389
390    pub(crate) fn calculate_fitness_scores(
391        &self,
392        candidate: &ArchitectureCandidate,
393    ) -> Result<FitnessScores> {
394        let performance = candidate
395            .performance
396            .as_ref()
397            .ok_or_else(|| anyhow!("No performance metrics available"))?;
398
399        let weights = &self.config.objective_weights;
400        let accuracy = performance.validation_accuracy;
401        let efficiency = 1.0 / (performance.inference_time_ms + 1.0);
402        let memory_efficiency = 1.0 / (performance.memory_usage_mb / 1000.0 + 1.0);
403        let simplicity = 1.0 / (candidate.genome.nodes.len() as f32 / 10.0 + 1.0);
404        let novelty = candidate.novelty_score;
405        let hardware_compatibility = candidate.hardware_metrics.efficiency_score;
406
407        let overall_fitness = weights.accuracy_weight * accuracy
408            + weights.efficiency_weight * efficiency
409            + weights.memory_weight * memory_efficiency
410            + weights.simplicity_weight * simplicity
411            + weights.novelty_weight * novelty;
412
413        Ok(FitnessScores {
414            overall_fitness,
415            accuracy,
416            efficiency,
417            memory_efficiency,
418            simplicity,
419            novelty,
420            hardware_compatibility,
421            pareto_rank: 0,
422            crowding_distance: 0.0,
423        })
424    }
425
426    fn calculate_pareto_ranking(&self, population: &mut [ArchitectureCandidate]) -> Result<()> {
427        let n = population.len();
428        let mut domination_count = vec![0usize; n];
429        let mut dominated_solutions: Vec<Vec<usize>> = vec![Vec::new(); n];
430        let mut fronts: Vec<Vec<usize>> = Vec::new();
431        let mut current_front: Vec<usize> = Vec::new();
432
433        for i in 0..n {
434            for j in 0..n {
435                if i != j {
436                    if self.dominates(&population[i], &population[j]) {
437                        dominated_solutions[i].push(j);
438                    } else if self.dominates(&population[j], &population[i]) {
439                        domination_count[i] += 1;
440                    }
441                }
442            }
443            if domination_count[i] == 0 {
444                population[i].fitness.pareto_rank = 0;
445                current_front.push(i);
446            }
447        }
448
449        let mut front_number = 0usize;
450        while !current_front.is_empty() {
451            fronts.push(current_front.clone());
452            let mut next_front = Vec::new();
453            for &i in &current_front {
454                for &j in &dominated_solutions[i] {
455                    domination_count[j] -= 1;
456                    if domination_count[j] == 0 {
457                        population[j].fitness.pareto_rank = front_number + 1;
458                        next_front.push(j);
459                    }
460                }
461            }
462            front_number += 1;
463            current_front = next_front;
464        }
465
466        for front in fronts {
467            self.calculate_crowding_distance(population, &front)?;
468        }
469        Ok(())
470    }
471
472    fn dominates(&self, a: &ArchitectureCandidate, b: &ArchitectureCandidate) -> bool {
473        let a_better = a.fitness.accuracy >= b.fitness.accuracy
474            && a.fitness.efficiency >= b.fitness.efficiency
475            && a.fitness.memory_efficiency >= b.fitness.memory_efficiency
476            && a.fitness.simplicity >= b.fitness.simplicity;
477        let a_strictly_better = a.fitness.accuracy > b.fitness.accuracy
478            || a.fitness.efficiency > b.fitness.efficiency
479            || a.fitness.memory_efficiency > b.fitness.memory_efficiency
480            || a.fitness.simplicity > b.fitness.simplicity;
481        a_better && a_strictly_better
482    }
483
484    fn calculate_crowding_distance(
485        &self,
486        population: &mut [ArchitectureCandidate],
487        front: &[usize],
488    ) -> Result<()> {
489        let front_size = front.len();
490        if front_size <= 2 {
491            for &i in front {
492                population[i].fitness.crowding_distance = f32::INFINITY;
493            }
494            return Ok(());
495        }
496        for &i in front {
497            population[i].fitness.crowding_distance = 0.0;
498        }
499        let objectives = ["accuracy", "efficiency", "memory_efficiency", "simplicity"];
500        for objective in objectives {
501            let mut sorted_indices = front.to_vec();
502            sorted_indices.sort_by(|&a, &b| {
503                let va = self.get_objective_value(&population[a], objective);
504                let vb = self.get_objective_value(&population[b], objective);
505                va.partial_cmp(&vb).unwrap_or(std::cmp::Ordering::Equal)
506            });
507            population[sorted_indices[0]].fitness.crowding_distance = f32::INFINITY;
508            population[sorted_indices[front_size - 1]]
509                .fitness
510                .crowding_distance = f32::INFINITY;
511            let obj_min = self.get_objective_value(&population[sorted_indices[0]], objective);
512            let obj_max =
513                self.get_objective_value(&population[sorted_indices[front_size - 1]], objective);
514            let obj_range = obj_max - obj_min;
515            if obj_range > 0.0 {
516                for i in 1..front_size - 1 {
517                    let next_obj =
518                        self.get_objective_value(&population[sorted_indices[i + 1]], objective);
519                    let prev_obj =
520                        self.get_objective_value(&population[sorted_indices[i - 1]], objective);
521                    population[sorted_indices[i]].fitness.crowding_distance +=
522                        (next_obj - prev_obj) / obj_range;
523                }
524            }
525        }
526        Ok(())
527    }
528
529    fn get_objective_value(&self, candidate: &ArchitectureCandidate, objective: &str) -> f32 {
530        match objective {
531            "accuracy" => candidate.fitness.accuracy,
532            "efficiency" => candidate.fitness.efficiency,
533            "memory_efficiency" => candidate.fitness.memory_efficiency,
534            "simplicity" => candidate.fitness.simplicity,
535            _ => 0.0,
536        }
537    }
538
539    async fn calculate_generation_statistics(
540        &self,
541        generation: usize,
542    ) -> Result<GenerationStatistics> {
543        let population = self.population.read().await;
544        let fitness_values: Vec<f32> = population
545            .iter()
546            .map(|c| c.fitness.overall_fitness)
547            .collect();
548        let best_fitness = fitness_values.iter().fold(0.0f32, |a, &b| a.max(b));
549        let average_fitness = fitness_values.iter().sum::<f32>() / fitness_values.len() as f32;
550        let variance = fitness_values
551            .iter()
552            .map(|&f| (f - average_fitness).powi(2))
553            .sum::<f32>()
554            / fitness_values.len() as f32;
555        let fitness_std = variance.sqrt();
556        let diversity_score = self.calculate_population_diversity(&population)?;
557        Ok(GenerationStatistics {
558            generation,
559            best_fitness,
560            average_fitness,
561            fitness_std,
562            diversity_score,
563            new_innovations: 0,
564            timestamp: chrono::Utc::now(),
565        })
566    }
567
568    pub(crate) fn calculate_population_diversity(
569        &self,
570        population: &[ArchitectureCandidate],
571    ) -> Result<f32> {
572        if population.len() < 2 {
573            return Ok(0.0);
574        }
575        let mut total_distance = 0.0f32;
576        let mut comparisons = 0usize;
577        for i in 0..population.len() {
578            for j in i + 1..population.len() {
579                let distance =
580                    self.calculate_genome_distance(&population[i].genome, &population[j].genome)?;
581                total_distance += distance;
582                comparisons += 1;
583            }
584        }
585        Ok(total_distance / comparisons as f32)
586    }
587
588    pub(crate) fn calculate_genome_distance(
589        &self,
590        genome1: &ArchitectureGenome,
591        genome2: &ArchitectureGenome,
592    ) -> Result<f32> {
593        let node_diff = (genome1.nodes.len() as f32 - genome2.nodes.len() as f32).abs();
594        let conn_diff = (genome1.connections.len() as f32 - genome2.connections.len() as f32).abs();
595        Ok((node_diff + conn_diff) / 10.0)
596    }
597
598    async fn get_best_candidate(&self) -> Result<ArchitectureCandidate> {
599        let population = self.population.read().await;
600        population
601            .iter()
602            .max_by(|a, b| {
603                a.fitness
604                    .overall_fitness
605                    .partial_cmp(&b.fitness.overall_fitness)
606                    .unwrap_or(std::cmp::Ordering::Equal)
607            })
608            .cloned()
609            .ok_or_else(|| anyhow!("Empty population"))
610    }
611
612    async fn check_convergence(&self, generation: usize) -> Result<bool> {
613        if generation < 10 {
614            return Ok(false);
615        }
616        let recent_stats = &self.evolution_history.generation_stats;
617        if recent_stats.len() < 10 {
618            return Ok(false);
619        }
620        let recent_best: Vec<f32> = recent_stats
621            .iter()
622            .rev()
623            .take(10)
624            .map(|s| s.best_fitness)
625            .collect();
626        let improvement = recent_best[0] - recent_best[9];
627        Ok(improvement < 0.001)
628    }
629
630    async fn evolve_next_generation(&mut self) -> Result<()> {
631        // Clone the current population to release the Arc lock before calling &mut self methods
632        let mut current_population: Vec<ArchitectureCandidate> =
633            self.population.read().await.clone();
634        let mut new_population = Vec::new();
635
636        let elite_count = (current_population.len() as f32 * self.config.elite_percentage) as usize;
637        current_population.sort_by(|a, b| {
638            b.fitness
639                .overall_fitness
640                .partial_cmp(&a.fitness.overall_fitness)
641                .unwrap_or(std::cmp::Ordering::Equal)
642        });
643        new_population.extend(current_population.iter().take(elite_count).cloned());
644
645        while new_population.len() < self.config.population_size {
646            let parent1_idx = self.tournament_selection(&current_population)?;
647            let parent2_idx = self.tournament_selection(&current_population)?;
648            let parent1 = current_population[parent1_idx].clone();
649            let parent2 = current_population[parent2_idx].clone();
650
651            let mut random = Random::default();
652            if random.random::<f32>() < self.config.crossover_probability {
653                let (mut child1, mut child2) = self.crossover(&parent1, &parent2)?;
654                if random.random::<f32>() < self.config.mutation_probability {
655                    self.mutate(&mut child1)?;
656                }
657                if random.random::<f32>() < self.config.mutation_probability {
658                    self.mutate(&mut child2)?;
659                }
660                new_population.push(child1);
661                if new_population.len() < self.config.population_size {
662                    new_population.push(child2);
663                }
664            } else {
665                let mut child = parent1.clone();
666                child.id = Uuid::new_v4();
667                child.parents = vec![parent1.id];
668                if random.random::<f32>() < self.config.mutation_probability {
669                    self.mutate(&mut child)?;
670                }
671                new_population.push(child);
672            }
673        }
674        *self.population.write().await = new_population;
675        Ok(())
676    }
677
678    /// Tournament selection
679    pub(crate) fn tournament_selection(
680        &self,
681        population: &[ArchitectureCandidate],
682    ) -> Result<usize> {
683        let mut random = Random::default();
684        let mut best_idx = random.random_range(0..population.len());
685        let mut best_fitness = population[best_idx].fitness.overall_fitness;
686        for _ in 1..self.config.tournament_size {
687            let idx = random.random_range(0..population.len());
688            if population[idx].fitness.overall_fitness > best_fitness {
689                best_idx = idx;
690                best_fitness = population[idx].fitness.overall_fitness;
691            }
692        }
693        Ok(best_idx)
694    }
695
696    /// Crossover operation
697    fn crossover(
698        &mut self,
699        parent1: &ArchitectureCandidate,
700        parent2: &ArchitectureCandidate,
701    ) -> Result<(ArchitectureCandidate, ArchitectureCandidate)> {
702        let mut child1 = parent1.clone();
703        let mut child2 = parent2.clone();
704        child1.id = Uuid::new_v4();
705        child2.id = Uuid::new_v4();
706        child1.parents = vec![parent1.id, parent2.id];
707        child2.parents = vec![parent1.id, parent2.id];
708
709        let mut random = Random::default();
710        let crossover_point =
711            random.random_range(1..parent1.genome.nodes.len().min(parent2.genome.nodes.len()));
712        for i in crossover_point..child1.genome.nodes.len().min(child2.genome.nodes.len()) {
713            std::mem::swap(&mut child1.genome.nodes[i], &mut child2.genome.nodes[i]);
714        }
715        child1.fitness = FitnessScores::default();
716        child2.fitness = FitnessScores::default();
717        child1.performance = None;
718        child2.performance = None;
719        Ok((child1, child2))
720    }
721
722    /// Mutation operation
723    fn mutate(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
724        let mut random = Random::default();
725        for node in &mut candidate.genome.nodes {
726            if random.random::<f32>() < self.config.mutation_probability {
727                for (_, value) in node.parameters.iter_mut() {
728                    *value *= random.random_range(0.8f32..1.2f32);
729                }
730            }
731        }
732        for connection in &mut candidate.genome.connections {
733            if random.random::<f32>() < self.config.mutation_probability {
734                connection.weight += random.random_range(-0.1f32..0.1f32);
735                connection.weight = connection.weight.clamp(-2.0, 2.0);
736            }
737        }
738        if random.random::<f32>() < 0.05 {
739            self.structural_mutation(candidate)?;
740        }
741        candidate.fitness = FitnessScores::default();
742        candidate.performance = None;
743        Ok(())
744    }
745
746    fn structural_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
747        let mut random = Random::default();
748        match random.random_range(0..4usize) {
749            0 => self.add_node_mutation(candidate)?,
750            1 => self.add_connection_mutation(candidate)?,
751            2 => self.remove_node_mutation(candidate)?,
752            3 => self.remove_connection_mutation(candidate)?,
753            _ => {}
754        }
755        Ok(())
756    }
757
758    fn add_node_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
759        let mut random = Random::default();
760        let new_id = candidate.genome.nodes.len();
761        let operation = self.generate_random_operation(&mut random)?;
762        let node = NodeGene {
763            id: new_id,
764            operation,
765            parameters: self.generate_random_parameters(&mut random),
766            active: true,
767            innovation_number: self
768                .evolution_history
769                .innovation_tracker
770                .get_innovation_number(&format!("node_{new_id}")),
771        };
772        candidate.genome.nodes.push(node);
773        Ok(())
774    }
775
776    fn add_connection_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
777        let mut random = Random::default();
778        let num_nodes = candidate.genome.nodes.len();
779        if num_nodes >= 2 {
780            let from_node = random.random_range(0..num_nodes);
781            let to_node = random.random_range(0..num_nodes);
782            if from_node != to_node {
783                let connection = ConnectionGene {
784                    from_node,
785                    to_node,
786                    weight: random.random_range(-1.0f32..1.0f32),
787                    active: true,
788                    innovation_number: self
789                        .evolution_history
790                        .innovation_tracker
791                        .get_innovation_number(&format!("conn_{from_node}_{to_node}")),
792                };
793                candidate.genome.connections.push(connection);
794            }
795        }
796        Ok(())
797    }
798
799    fn remove_node_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
800        if candidate.genome.nodes.len() > 3 {
801            let mut random = Random::default();
802            let remove_idx = random.random_range(0..candidate.genome.nodes.len());
803            candidate.genome.nodes.remove(remove_idx);
804            candidate
805                .genome
806                .connections
807                .retain(|conn| conn.from_node != remove_idx && conn.to_node != remove_idx);
808        }
809        Ok(())
810    }
811
812    fn remove_connection_mutation(&mut self, candidate: &mut ArchitectureCandidate) -> Result<()> {
813        if !candidate.genome.connections.is_empty() {
814            let mut random = Random::default();
815            let remove_idx = random.random_range(0..candidate.genome.connections.len());
816            candidate.genome.connections.remove(remove_idx);
817        }
818        Ok(())
819    }
820
821    async fn maintain_population_diversity(&mut self) -> Result<()> {
822        let mut population = self.population.write().await;
823        let novelty_scores: Vec<f32> = population
824            .iter()
825            .map(|candidate| {
826                self.calculate_novelty_score(candidate, &population)
827                    .unwrap_or(0.0)
828            })
829            .collect();
830        for (candidate, score) in population.iter_mut().zip(novelty_scores) {
831            candidate.novelty_score = score;
832        }
833
834        let mut to_remove = Vec::new();
835        for i in 0..population.len() {
836            for j in i + 1..population.len() {
837                let distance =
838                    self.calculate_genome_distance(&population[i].genome, &population[j].genome)?;
839                if distance < 0.1 {
840                    if population[i].fitness.overall_fitness < population[j].fitness.overall_fitness
841                    {
842                        to_remove.push(i);
843                    } else {
844                        to_remove.push(j);
845                    }
846                }
847            }
848        }
849        to_remove.sort();
850        to_remove.dedup();
851        to_remove.reverse();
852        for idx in to_remove {
853            if population.len() > self.config.population_size / 2 {
854                population.remove(idx);
855            }
856        }
857        Ok(())
858    }
859
860    fn calculate_novelty_score(
861        &self,
862        candidate: &ArchitectureCandidate,
863        population: &[ArchitectureCandidate],
864    ) -> Result<f32> {
865        let k = 15;
866        let mut distances = Vec::new();
867        for other in population {
868            if other.id != candidate.id {
869                let distance = self.calculate_genome_distance(&candidate.genome, &other.genome)?;
870                distances.push(distance);
871            }
872        }
873        distances.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
874        let novelty = if distances.len() >= k {
875            distances.iter().take(k).sum::<f32>() / k as f32
876        } else {
877            distances.iter().sum::<f32>() / distances.len().max(1) as f32
878        };
879        Ok(novelty)
880    }
881
882    async fn apply_progressive_complexification(&mut self, generation: usize) -> Result<()> {
883        let complexity_increase =
884            self.config.progressive_config.complexity_increase_rate * generation as f32;
885        let max_nodes =
886            (self.config.progressive_config.start_complexity as f32 + complexity_increase) as usize;
887        let max_nodes = max_nodes.min(self.config.progressive_config.max_complexity);
888
889        // Clone population so the Arc<RwLock> is not held while we call &mut self methods
890        let mut snapshot = self.population.read().await.clone();
891        for candidate in snapshot.iter_mut() {
892            let mut random = Random::default();
893            if candidate.genome.nodes.len() < max_nodes && random.random::<f32>() < 0.1 {
894                self.add_node_mutation(candidate)?;
895            }
896        }
897        *self.population.write().await = snapshot;
898        Ok(())
899    }
900
901    /// Get evolution statistics
902    pub fn get_evolution_statistics(&self) -> &[GenerationStatistics] {
903        &self.evolution_history.generation_stats
904    }
905
906    /// Export the best architectures
907    pub async fn export_best_architectures(
908        &self,
909        count: usize,
910    ) -> Result<Vec<ArchitectureCandidate>> {
911        let mut population = self.population.read().await.clone();
912        population.sort_by(|a, b| {
913            b.fitness
914                .overall_fitness
915                .partial_cmp(&a.fitness.overall_fitness)
916                .unwrap_or(std::cmp::Ordering::Equal)
917        });
918        Ok(population.into_iter().take(count).collect())
919    }
920}