use rand::Rng;
use std::collections::{HashMap, HashSet};
use std::fmt;
use std::ops::{Index, IndexMut};
use crate::genome::{Genome, GenomeStats};
pub struct SpeciesStats {
pub avg_fitness: f32,
pub max_fitness: f32,
pub max_fitness_ever: f32,
pub age: usize,
pub last_improvement_age: usize,
pub genome_stats: Vec<GenomeStats>,
}
#[derive(Clone)]
pub struct Species {
pub(crate) genomes: Vec<Genome>,
pub(crate) avg_fitness: f32,
pub(crate) max_fitness: f32,
pub(crate) max_fitness_ever: f32,
pub(crate) age: usize,
pub(crate) last_improvement_age: usize,
}
impl Species {
pub(crate) fn empty() -> Self {
return Species {
genomes: vec![],
avg_fitness: 0.0,
max_fitness: 0.0,
max_fitness_ever: 0.0,
age: 0,
last_improvement_age: 0,
};
}
pub(crate) fn new(genome: Genome) -> Self {
return Species {
genomes: vec![genome],
avg_fitness: 0.0,
max_fitness: 0.0,
max_fitness_ever: 0.0,
age: 0,
last_improvement_age: 0,
};
}
pub fn stats(&self) -> SpeciesStats {
return SpeciesStats {
avg_fitness: self.avg_fitness,
max_fitness: self.max_fitness,
max_fitness_ever: self.max_fitness_ever,
age: self.age,
last_improvement_age: self.last_improvement_age,
genome_stats: self.genomes.iter().map(|g| g.stats()).collect(),
};
}
pub fn len(&self) -> usize {
return self.genomes.len();
}
pub(crate) fn add_genome(&mut self, genome: Genome) {
self.genomes.push(genome);
}
pub(crate) fn is_same_species(
&mut self,
genome: &mut Genome,
threshold: f32,
excess_coef: f32,
disjoint_coef: f32,
weight_diff_coef: f32,
) -> bool {
if self.genomes.len() == 0 {
return true;
}
return self.get_compatibility_score(genome, excess_coef, disjoint_coef, weight_diff_coef)
<= threshold;
}
pub(crate) fn get_compatibility_score(
&mut self,
genome: &mut Genome,
excess_coef: f32,
disjoint_coef: f32,
weight_diff_coef: f32,
) -> f32 {
let n = self.genomes.len();
let our_genome = &mut self.genomes[rand::thread_rng().gen_range(0..n)];
let (disjoint, excess, weight_diff) =
Species::get_disjoint_excess_and_weight_diff(our_genome, genome);
let genome_length = match [
our_genome.network.edge_weights().collect::<Vec<_>>().len(),
genome.network.edge_weights().collect::<Vec<_>>().len(),
]
.iter()
.max()
{
Some(n) => *n as f32,
None => 0.0,
};
return ((excess_coef * excess) / genome_length)
+ ((disjoint_coef * disjoint) / genome_length)
+ (weight_diff_coef * weight_diff);
}
pub(crate) fn adjust_fitness(
&mut self,
obliterate: bool,
dropoff_age: usize,
age_significance: f32,
) {
let is_stagnant = (self.age - self.last_improvement_age) >= dropoff_age;
let species_size = self.genomes.len() as u32;
for g in self.genomes.iter_mut() {
if is_stagnant || obliterate {
g.fitness = g.fitness * 0.01;
}
if self.age <= 10 {
g.fitness = g.fitness * age_significance;
}
g.fitness = g.fitness / species_size as f32;
}
}
fn get_disjoint_excess_and_weight_diff(g1: &mut Genome, g2: &mut Genome) -> (f32, f32, f32) {
let g1_data: HashMap<u64, f32> =
HashMap::from_iter(g1.network.edge_weights().map(|g| (g.innovation, g.weight)));
let g2_data: HashMap<u64, f32> =
HashMap::from_iter(g2.network.edge_weights().map(|g| (g.innovation, g.weight)));
let g1_innos: HashSet<&u64> = HashSet::from_iter(g1_data.keys());
let g2_innos: HashSet<&u64> = HashSet::from_iter(g2_data.keys());
let min: &u64 = match g1_data.keys().min() {
Some(i) => i,
None => &0,
};
let max: &u64 = match g1_data.keys().max() {
Some(i) => i,
None => &0,
};
let mut disjoint: f32 = 0.0;
let mut excess: f32 = 0.0;
for missing in g2_innos.difference(&g1_innos) {
if *missing >= min && *missing <= max {
disjoint += 1.0;
} else {
excess += 1.0;
}
}
let mut weight_diff: f32 = 0.0;
for same in g2_innos.intersection(&g1_innos) {
weight_diff = (g1_data[same] - g2_data[same]).abs();
}
return (disjoint, excess, weight_diff);
}
pub(crate) fn set_max_fitness(&mut self) {
let winner = self.genomes.iter().reduce(|g1, g2| {
if g1.max_fitness > g2.max_fitness {
return g1;
} else {
return g2;
}
});
match winner {
Some(g) => {
self.max_fitness = g.max_fitness;
if self.max_fitness > self.max_fitness_ever {
self.last_improvement_age = self.age;
self.max_fitness_ever = self.max_fitness;
}
}
None => {}
}
}
pub(crate) fn set_avg_fitness(&mut self) {
self.avg_fitness =
self.genomes.iter().map(|g| g.fitness).sum::<f32>() / self.genomes.len() as f32;
}
pub(crate) fn sort_by_fitness(&mut self) {
let debug_self = format!("{self:?}");
self.genomes
.sort_unstable_by(|g1, g2| g2.fitness.partial_cmp(&g1.fitness).expect(&debug_self));
}
pub(crate) fn reproduce(
&self,
num_babies: u32,
species: &Vec<Species>,
innovation: &mut u64,
mut_only_rate: f32,
mate_only_rate: f32,
crossover_rate: f32,
connection_mut_rate: f32,
node_mut_rate: f32,
weight_mut_rate: f32,
perturb_rate: f32,
perturb_step_size: f32,
disable_mut_rate: f32,
enable_mut_rate: f32,
) -> Vec<Genome> {
let mut babies: Vec<Genome> = vec![];
for i in 0..num_babies {
if num_babies > 5 && i == 0 {
babies.push(self.genomes[0].clone());
}
else if rand::thread_rng().gen_range(0..=100) as f32 * 0.01 < mut_only_rate {
let parent = rand::thread_rng().gen_range(0..self.genomes.len());
let mut baby = self.genomes[parent].clone();
baby.mutate(
innovation,
connection_mut_rate,
node_mut_rate,
weight_mut_rate,
perturb_rate,
perturb_step_size,
disable_mut_rate,
enable_mut_rate,
);
babies.push(baby);
}
else {
let n1 = rand::thread_rng().gen_range(0..self.genomes.len());
if rand::thread_rng().gen_range(0..=100) as f32 * 0.01 < crossover_rate {
let n2 = rand::thread_rng().gen_range(0..self.genomes.len());
let mut baby = Genome::from_parents(&self.genomes[n1], &self.genomes[n2]);
if rand::thread_rng().gen_range(0..=100) as f32 * 0.01 < mate_only_rate {
baby.mutate(
innovation,
connection_mut_rate,
node_mut_rate,
weight_mut_rate,
perturb_rate,
perturb_step_size,
disable_mut_rate,
enable_mut_rate,
);
}
babies.push(baby);
} else {
let mut other_species = self;
for _ in 0..species.len() {
let n = rand::thread_rng().gen_range(0..species.len());
if species[n].len() > 0 {
other_species = &species[n];
break;
}
}
let mut baby =
Genome::from_parents(&self.genomes[n1], &other_species.genomes[0]);
if rand::thread_rng().gen_range(0..=100) as f32 * 0.01 < mate_only_rate {
baby.mutate(
innovation,
connection_mut_rate,
node_mut_rate,
weight_mut_rate,
perturb_rate,
perturb_step_size,
disable_mut_rate,
enable_mut_rate,
);
}
babies.push(baby);
}
}
}
return babies;
}
}
impl Index<usize> for Species {
type Output = Genome;
fn index(&self, index: usize) -> &Self::Output {
return &self.genomes[index];
}
}
impl IndexMut<usize> for Species {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
return &mut self.genomes[index];
}
}
impl fmt::Debug for Species {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
return write!(
f,
"{{ Max fitness: {}, Max fitness ever: {}, Avg fitness: {}, Genomes: {}, Age: {}, Last improvement age: {} }}",
self.max_fitness,
self.max_fitness_ever,
self.avg_fitness,
self.genomes.len(),
self.age,
self.last_improvement_age,
);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn new_genome(inputs: usize, outputs: usize, inno: &mut u64) -> Genome {
return Genome::new(inputs, outputs, inno, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
}
#[test]
fn test_set_avg_fitness() {
let mut s = Species::empty();
let mut innovation = 0;
s.genomes = [10.0, 10.0, 30.0, 100.0, 100.0]
.iter()
.map(|f| {
let mut g = new_genome(1, 1, &mut innovation);
g.fitness = *f;
return g;
})
.collect();
s.set_avg_fitness();
assert_eq!(s.avg_fitness, 50.0);
}
#[test]
fn test_sort_by_fitness() {
let mut s = Species::empty();
let mut innovation = 0;
s.genomes = [50.0, 30.0, 10.0, 70.0, 40.0, 20.0, 100.0, 5.0]
.iter()
.map(|f| {
let mut g = new_genome(1, 1, &mut innovation);
g.fitness = *f;
return g;
})
.collect();
s.sort_by_fitness();
let sorted: Vec<f32> = s.genomes.iter().map(|g| g.fitness).collect();
assert_eq!(sorted, [100.0, 70.0, 50.0, 40.0, 30.0, 20.0, 10.0, 5.0]);
}
}