use std::fmt;
use std::hash::Hash;
use concurrent_lru::sharded::LruCache;
use derive_more::Display;
use float_pretty_print::PrettyPrintFloat;
use crate::cfg::Cfg;
use crate::gen::species::{SpeciesId, NO_SPECIES};
use crate::gen::Params;
pub trait Genome = Clone + Send + Sync + PartialOrd + PartialEq + fmt::Debug;
pub trait FitnessFn<G: Genome> = Fn(&G, usize) -> f64 + Sync + Send + Clone;
#[derive(Clone, PartialOrd, PartialEq, Debug, Display)]
#[display(fmt = "fitness {} species {}", "PrettyPrintFloat(*base_fitness)", species)]
pub struct Mem<G: Genome> {
pub genome: G, // Actual genome.
pub params: Params, // Adaptively evolved parameters
pub species: SpeciesId, // Species index
pub base_fitness: f64, // Original fitness, generated by Evaluator fitness function.
pub selection_fitness: f64, // Potentially adjusted fitness, for selection.
}
impl<G: Genome> Mem<G> {
pub fn new<E: Evaluator>(genome: G, cfg: &Cfg) -> Self {
Self {
genome,
params: Params::new::<E>(cfg),
species: NO_SPECIES,
base_fitness: 0.0,
selection_fitness: 0.0,
}
}
}
pub trait Evaluator: Send + Sync {
type Genome: Genome;
const NUM_CROSSOVER: usize = 2; // Specify the number of crossover operators.
const NUM_MUTATION: usize = 1; // Specify the number of mutation operators.
// |idx| specifies which crossover function to use. 0 is conventionally do nothing,
// with actual crossover starting from index 1.
fn crossover(&self, s1: &mut Self::Genome, s2: &mut Self::Genome, idx: usize);
// Unlike crossover, mutation is called for every mutation operator. No need for a nop operator.
fn mutate(&self, s: &mut Self::Genome, rate: f64, idx: usize);
fn fitness(&self, s: &Self::Genome, gen: usize) -> f64;
fn distance(&self, s1: &Self::Genome, s2: &Self::Genome) -> f64;
}
// Evaluator which uses an LRU cache to cache fitness and distance values.
pub struct CachedEvaluator<E: Evaluator>
where
E::Genome: Hash + Eq,
{
eval: E,
fitness_cache: LruCache<E::Genome, f64>,
}
impl<E: Evaluator> CachedEvaluator<E>
where
E::Genome: Hash + Eq,
{
pub fn new(eval: E, cap: usize) -> Self {
Self { eval, fitness_cache: LruCache::new(cap as u64) }
}
}
impl<E: Evaluator> Evaluator for CachedEvaluator<E>
where
E::Genome: Hash + Eq,
{
type Genome = E::Genome;
const NUM_CROSSOVER: usize = E::NUM_CROSSOVER;
const NUM_MUTATION: usize = E::NUM_MUTATION;
fn crossover(&self, s1: &mut Self::Genome, s2: &mut Self::Genome, idx: usize) {
self.eval.crossover(s1, s2, idx);
}
fn mutate(&self, s: &mut Self::Genome, rate: f64, idx: usize) {
self.eval.mutate(s, rate, idx);
}
fn fitness(&self, s: &Self::Genome, gen: usize) -> f64 {
*self.fitness_cache.get_or_init(s.clone(), 1, |s| self.eval.fitness(s, gen)).value()
}
fn distance(&self, s1: &Self::Genome, s2: &Self::Genome) -> f64 {
self.eval.distance(s1, s2)
}
}