use crate::{Evaluator, Evolver, Genotype, Phenotype};
use rand::prelude::{IndexedRandom, SeedableRng};
use rand_pcg::Pcg64;
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[cfg(feature = "parallel")]
use rayon::prelude::*;
fn cmp_f32_nan_last(a: f32, b: f32) -> Ordering {
match (a.is_nan(), b.is_nan()) {
(true, true) => Ordering::Equal,
(true, false) => Ordering::Less,
(false, true) => Ordering::Greater,
(false, false) => a.partial_cmp(&b).unwrap_or(Ordering::Equal),
}
}
#[derive(Serialize, Deserialize)]
#[serde(bound = "G: Genotype")]
struct SimpleGAData<G: Genotype> {
population: Vec<Phenotype<G>>,
pop_size: usize,
mutation_rate: f32,
elitism: usize,
rng: Pcg64,
}
pub struct SimpleGA<G: Genotype> {
population: Vec<Phenotype<G>>,
pop_size: usize,
mutation_rate: f32,
elitism: usize,
rng: Pcg64,
}
impl<G: Genotype> Serialize for SimpleGA<G> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("SimpleGA", 5)?;
state.serialize_field("population", &self.population)?;
state.serialize_field("pop_size", &self.pop_size)?;
state.serialize_field("mutation_rate", &self.mutation_rate)?;
state.serialize_field("elitism", &self.elitism)?;
state.serialize_field("rng", &self.rng)?;
state.end()
}
}
impl<'de, G: Genotype> Deserialize<'de> for SimpleGA<G> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::Error;
let data = SimpleGAData::<G>::deserialize(deserializer)?;
if data.pop_size != data.population.len() {
return Err(D::Error::custom(format!(
"pop_size ({}) does not match population length ({})",
data.pop_size,
data.population.len()
)));
}
if data.mutation_rate.is_nan() || data.mutation_rate.is_infinite() {
return Err(D::Error::custom("mutation_rate must be a finite number"));
}
Ok(Self {
population: data.population,
pop_size: data.pop_size,
mutation_rate: data.mutation_rate,
elitism: data.elitism,
rng: data.rng,
})
}
}
impl<G: Genotype> SimpleGA<G> {
pub fn new(initial_pop: Vec<G>, mutation_rate: f32, elitism: usize, seed: u64) -> Self {
let pop_size = initial_pop.len();
if elitism >= pop_size && pop_size > 0 {
eprintln!(
"Warning: SimpleGA elitism ({}) >= pop_size ({}). \
Elitism will be clamped to {} to ensure evolution progresses.",
elitism,
pop_size,
pop_size.saturating_sub(1)
);
}
let population = initial_pop
.into_iter()
.map(|g| Phenotype {
genotype: g,
fitness: 0.0,
objectives: vec![],
descriptor: vec![],
})
.collect();
Self {
population,
pop_size,
mutation_rate,
elitism,
rng: Pcg64::seed_from_u64(seed),
}
}
pub fn pop_size(&self) -> usize {
self.pop_size
}
pub fn mutation_rate(&self) -> f32 {
self.mutation_rate
}
pub fn elitism(&self) -> usize {
self.elitism.min(self.pop_size.saturating_sub(1))
}
pub fn elitism_configured(&self) -> usize {
self.elitism
}
}
impl<G: Genotype> Evolver<G> for SimpleGA<G> {
fn step<E: Evaluator<G>>(&mut self, evaluator: &E) {
if self.population.is_empty() {
return;
}
#[cfg(feature = "parallel")]
self.population.par_iter_mut().for_each(|p| {
let (f, obj, desc) = evaluator.evaluate(&p.genotype);
p.fitness = f;
p.objectives = obj;
p.descriptor = desc;
});
#[cfg(not(feature = "parallel"))]
for p in &mut self.population {
let (f, obj, desc) = evaluator.evaluate(&p.genotype);
p.fitness = f;
p.objectives = obj;
p.descriptor = desc;
}
self.population
.sort_by(|a, b| cmp_f32_nan_last(b.fitness, a.fitness));
let effective_elitism = self.elitism.min(self.pop_size.saturating_sub(1));
let mut next_gen = self.population[..effective_elitism].to_vec();
let tournament_size = 3.min(self.population.len());
while next_gen.len() < self.pop_size {
let p_a = self
.population
.choose_multiple(&mut self.rng, tournament_size)
.max_by(|a, b| cmp_f32_nan_last(a.fitness, b.fitness))
.unwrap();
let p_b = self
.population
.choose_multiple(&mut self.rng, tournament_size)
.max_by(|a, b| cmp_f32_nan_last(a.fitness, b.fitness))
.unwrap();
let mut child_dna = p_a.genotype.crossover(&p_b.genotype, &mut self.rng);
child_dna.mutate(&mut self.rng, self.mutation_rate);
next_gen.push(Phenotype {
genotype: child_dna,
fitness: 0.0,
objectives: vec![],
descriptor: vec![],
});
}
self.population = next_gen;
}
fn population(&mut self) -> &[Phenotype<G>] {
&self.population
}
}