memega 0.1.0

genetic algorithm library
Documentation
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)
    }
}