use crate::dna::NeuralDNA;
use crate::fitness::{FitnessFunction, FitnessScore};
use crate::mutation::{mutate, crossover, MutationPolicy, MutationType};
use crate::traits::TraitProfile;
use rand::{Rng, thread_rng, seq::SliceRandom};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Individual {
pub dna: NeuralDNA,
pub fitness: Option<FitnessScore>,
pub traits: TraitProfile,
}
impl Individual {
pub fn new(dna: NeuralDNA) -> Self {
Self {
dna,
fitness: None,
traits: TraitProfile::default(),
}
}
pub fn with_traits(dna: NeuralDNA, traits: TraitProfile) -> Self {
Self {
dna,
fitness: None,
traits,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EvolutionConfig {
pub population_size: usize,
pub elite_count: usize,
pub mutation_policy: MutationPolicy,
pub crossover_rate: f32,
pub max_generations: usize,
pub fitness_threshold: f64,
}
impl Default for EvolutionConfig {
fn default() -> Self {
Self {
population_size: 100,
elite_count: 10,
mutation_policy: MutationPolicy::default(),
crossover_rate: 0.7,
max_generations: 1000,
fitness_threshold: 0.95,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GenerationStats {
pub generation: usize,
pub best_fitness: f64,
pub average_fitness: f64,
pub diversity: f64,
pub population_size: usize,
}
pub struct EvolutionEngine {
pub config: EvolutionConfig,
pub population: Vec<Individual>,
pub generation: usize,
pub best_fitness_history: Vec<f32>,
pub diversity_history: Vec<f32>,
pub stats_history: Vec<GenerationStats>,
}
impl EvolutionEngine {
pub fn new(config: EvolutionConfig, topology: Vec<usize>, activation: &str) -> Self {
let mut population = Vec::with_capacity(config.population_size);
for _ in 0..config.population_size {
let dna = NeuralDNA::random(topology.clone(), activation);
population.push(Individual::new(dna));
}
Self {
config,
population,
generation: 0,
best_fitness_history: Vec::new(),
diversity_history: Vec::new(),
stats_history: Vec::new(),
}
}
pub fn evolve_generation<F>(&mut self, fitness_fn: &F, inputs: &[Vec<f32>], targets: &[Vec<f32>])
where
F: FitnessFunction,
{
for individual in &mut self.population {
individual.fitness = Some(fitness_fn.evaluate(&individual.dna));
}
self.population.sort_by(|a, b| {
let fitness_a = a.fitness.as_ref().unwrap().overall;
let fitness_b = b.fitness.as_ref().unwrap().overall;
fitness_b.partial_cmp(&fitness_a).unwrap()
});
let stats = self.calculate_stats();
self.stats_history.push(stats.clone());
self.best_fitness_history.push(stats.best_fitness as f32);
self.diversity_history.push(stats.diversity as f32);
let mut next_generation = Vec::with_capacity(self.config.population_size);
for i in 0..self.config.elite_count.min(self.population.len()) {
next_generation.push(self.population[i].clone());
}
let mut rng = thread_rng();
while next_generation.len() < self.config.population_size {
if rng.gen::<f32>() < self.config.crossover_rate && self.population.len() >= 2 {
let parent1 = self.tournament_select(&mut rng);
let parent2 = self.tournament_select(&mut rng);
if let Ok(child_dna) = crossover(&parent1.dna, &parent2.dna) {
let mut child = Individual::new(child_dna);
mutate(&mut child.dna, &self.config.mutation_policy, &MutationType::All);
next_generation.push(child);
}
} else {
let parent = self.tournament_select(&mut rng);
let mut child = parent.clone();
mutate(&mut child.dna, &self.config.mutation_policy, &MutationType::All);
child.fitness = None;
next_generation.push(child);
}
}
self.population = next_generation;
self.generation += 1;
}
fn tournament_select(&self, rng: &mut impl Rng) -> &Individual {
let tournament_size = 3;
let mut best = &self.population[rng.gen_range(0..self.population.len())];
for _ in 1..tournament_size {
let candidate = &self.population[rng.gen_range(0..self.population.len())];
if let (Some(candidate_fitness), Some(best_fitness)) = (&candidate.fitness, &best.fitness) {
if candidate_fitness.overall > best_fitness.overall {
best = candidate;
}
}
}
best
}
fn calculate_stats(&self) -> GenerationStats {
let fitnesses: Vec<f64> = self.population
.iter()
.filter_map(|ind| ind.fitness.as_ref().map(|f| f.overall))
.collect();
let best_fitness = fitnesses.iter().cloned().fold(0.0, f64::max);
let average_fitness = if fitnesses.is_empty() {
0.0
} else {
fitnesses.iter().sum::<f64>() / fitnesses.len() as f64
};
let diversity = if fitnesses.len() > 1 {
let variance = fitnesses.iter()
.map(|f| (f - average_fitness).powi(2))
.sum::<f64>() / fitnesses.len() as f64;
variance.sqrt()
} else {
0.0
};
GenerationStats {
generation: self.generation,
best_fitness,
average_fitness,
diversity,
population_size: self.population.len(),
}
}
pub fn get_best_individual(&self) -> Option<&Individual> {
self.population.first()
}
pub fn get_statistics(&self) -> Option<&GenerationStats> {
self.stats_history.last()
}
pub fn should_stop(&self) -> bool {
if self.generation >= self.config.max_generations {
return true;
}
if let Some(best) = self.get_best_individual() {
if let Some(fitness) = &best.fitness {
return fitness.overall >= self.config.fitness_threshold;
}
}
false
}
pub fn run<F>(&mut self, fitness_fn: &F, inputs: &[Vec<f32>], targets: &[Vec<f32>]) -> Result<&Individual, String>
where
F: FitnessFunction,
{
while !self.should_stop() {
self.evolve_generation(fitness_fn, inputs, targets);
}
self.get_best_individual()
.ok_or_else(|| "No individuals in population".to_string())
}
}