#![cfg(feature = "ga")]
pub mod builder;
pub mod individual;
pub mod operators;
pub mod population;
pub mod probe;
use crate::ga::operators::fitness::Fitness;
pub use builder::*;
pub use individual::Individual;
pub use probe::CsvProbe;
pub use probe::JsonProbe;
pub use probe::Probe;
pub use probe::StdoutProbe;
use std::marker::PhantomData;
use self::individual::IndividualTrait;
use self::{
operators::{
crossover::CrossoverOperator, mutation::MutationOperator, replacement::ReplacementOperator,
selection::SelectionOperator,
},
population::PopulationGenerator,
};
pub struct GAParams {
pub selection_rate: f64,
pub mutation_rate: f64,
pub population_size: usize,
pub generation_limit: usize,
pub max_duration: std::time::Duration,
}
pub struct GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
pub params: GAParams,
pub fitness_fn: FitnessT,
pub mutation_operator: MutOpT,
pub crossover_operator: CrossOpT,
pub selection_operator: SelOpT,
pub replacement_operator: ReplOpT,
pub population_factory: PopGenT,
pub probe: ProbeT,
_phantom: PhantomData<IndividualT::ChromosomeT>,
}
#[derive(Default)]
pub struct GAMetadata {
pub start_time: Option<std::time::Instant>,
pub duration: Option<std::time::Duration>,
pub generation: usize,
}
impl GAMetadata {
pub fn new(
start_time: Option<std::time::Instant>,
duration: Option<std::time::Duration>,
generation: usize,
) -> Self {
GAMetadata {
start_time,
duration,
generation,
}
}
}
pub struct GeneticSolver<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
config: GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>,
metadata: GAMetadata,
}
impl<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
GeneticSolver<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>
where
IndividualT: IndividualTrait,
MutOpT: MutationOperator<IndividualT>,
CrossOpT: CrossoverOperator<IndividualT>,
SelOpT: SelectionOperator<IndividualT>,
ReplOpT: ReplacementOperator<IndividualT>,
PopGenT: PopulationGenerator<IndividualT>,
FitnessT: Fitness<IndividualT>,
ProbeT: Probe<IndividualT>,
{
pub fn new(
config: GAConfig<IndividualT, MutOpT, CrossOpT, SelOpT, ReplOpT, PopGenT, FitnessT, ProbeT>,
) -> Self {
assert_eq!(config.params.population_size % 2, 0); GeneticSolver {
config,
metadata: GAMetadata::new(None, None, 0),
}
}
#[inline]
fn find_best_individual(population: &[IndividualT]) -> &IndividualT {
population.iter().min().unwrap()
}
#[inline]
fn eval_pop(&mut self, population: &mut [IndividualT]) {
population
.iter_mut()
.filter(|idv| idv.requires_evaluation())
.for_each(|idv| *idv.fitness_mut() = (self.config.fitness_fn).apply(idv));
}
#[inline(always)]
fn gen_pop(&mut self) -> Vec<IndividualT> {
self.config
.population_factory
.generate(self.config.params.population_size)
}
pub fn run(&mut self) -> Option<IndividualT> {
self.metadata.start_time = Some(std::time::Instant::now());
self.config.probe.on_start(&self.metadata);
let mut population = self.gen_pop();
self.eval_pop(&mut population);
self.config.probe.on_initial_population_created(&population);
let mut best_individual_all_time = Self::find_best_individual(&population).clone();
self.metadata.duration = Some(self.metadata.start_time.unwrap().elapsed());
self.config
.probe
.on_new_best(&self.metadata, &best_individual_all_time);
for generation_no in 1..=self.config.params.generation_limit {
self.metadata.generation = generation_no;
self.metadata.duration = Some(self.metadata.start_time.unwrap().elapsed());
self.config.probe.on_iteration_start(&self.metadata);
self.eval_pop(&mut population);
let mating_pool: Vec<&IndividualT> =
self.config
.selection_operator
.apply(&self.metadata, &population, population.len());
let mut children: Vec<IndividualT> = Vec::with_capacity(self.config.params.population_size);
for parents in mating_pool.chunks(2) {
let crt_children = self.config.crossover_operator.apply(parents[0], parents[1]);
children.push(crt_children.0);
children.push(crt_children.1);
}
children.iter_mut().for_each(|child| {
self.config
.mutation_operator
.apply(child, self.config.params.mutation_rate)
});
if self.config.replacement_operator.requires_children_fitness() {
self.eval_pop(&mut children);
}
population = self.config.replacement_operator.apply(population, children);
self.eval_pop(&mut population);
self.config.probe.on_new_generation(&self.metadata, &population);
let best_individual = Self::find_best_individual(&population);
self.config
.probe
.on_best_fit_in_generation(&self.metadata, best_individual);
if *best_individual < best_individual_all_time {
best_individual_all_time = best_individual.clone();
self.config
.probe
.on_new_best(&self.metadata, &best_individual_all_time);
}
self.config.probe.on_iteration_end(&self.metadata);
if self.metadata.start_time.unwrap().elapsed() >= self.config.params.max_duration {
break;
}
}
self.config
.probe
.on_end(&self.metadata, &population, &best_individual_all_time);
Some(best_individual_all_time)
}
}
#[cfg(test)]
mod tests {
use super::GAMetadata;
#[test]
fn gametadata_can_be_constructed_with_new_fn() {
GAMetadata::new(None, None, 0);
}
}