1use 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
20pub 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
32pub 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
42pub trait SelectionOperator: Send + Sync {
44 fn select(&self, population: &[ArchitectureCandidate], selection_size: usize) -> Vec<usize>;
45}
46
47pub trait DiversityStrategy: Send + Sync {
49 fn maintain_diversity(
50 &self,
51 population: &mut Vec<ArchitectureCandidate>,
52 diversity_target: f32,
53 ) -> Result<()>;
54}
55
56pub 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
65pub trait PerformanceModel: Send + Sync {
67 fn predict_performance(
68 &self,
69 genome: &ArchitectureGenome,
70 hardware: &HardwareTarget,
71 ) -> Result<PerformanceMetrics>;
72}
73
74pub(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
84pub(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
91pub(crate) struct DiversityManager {
93 pub diversity_metrics: DiversityMetrics,
94 pub novelty_archive: Vec<ArchitectureCandidate>,
95 pub strategies: Vec<Box<dyn DiversityStrategy>>,
96}
97
98pub(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
105pub 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 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 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 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 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 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 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 ¤t_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 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(¤t_population)?;
647 let parent2_idx = self.tournament_selection(¤t_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 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 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 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 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 pub fn get_evolution_statistics(&self) -> &[GenerationStatistics] {
903 &self.evolution_history.generation_stats
904 }
905
906 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}