mod builder;
pub mod prelude;
mod reporter;
pub use self::builder::{
Builder as EvolveBuilder, TryFromBuilderError as TryFromEvolveBuilderError,
};
use super::{
Strategy, StrategyAction, StrategyConfig, StrategyReporter, StrategyReporterNoop,
StrategyState, StrategyVariant,
};
use crate::chromosome::{Chromosome, Genes};
use crate::crossover::Crossover;
use crate::extension::{Extension, ExtensionNoop};
use crate::fitness::{Fitness, FitnessCache, FitnessOrdering, FitnessValue};
use crate::genotype::EvolveGenotype;
use crate::mutate::Mutate;
use crate::population::Population;
use crate::select::Select;
use rand::rngs::SmallRng;
use std::cell::RefCell;
use std::collections::HashMap;
use std::fmt;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use thread_local::ThreadLocal;
pub use self::reporter::Simple as EvolveReporterSimple;
pub use crate::strategy::reporter::Duration as EvolveReporterDuration;
pub use crate::strategy::reporter::Noop as EvolveReporterNoop;
#[derive(Copy, Clone, Debug, Default)]
pub enum EvolveVariant {
#[default]
Standard,
}
pub struct Evolve<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
E: Extension<Genotype = G>,
SR: StrategyReporter<Genotype = G>,
> {
pub genotype: G,
pub fitness: F,
pub plugins: EvolvePlugins<M, S, C, E>,
pub config: EvolveConfig,
pub state: EvolveState<G>,
pub reporter: SR,
pub rng: SmallRng,
}
pub struct EvolvePlugins<M: Mutate, S: Crossover, C: Select, E: Extension> {
pub mutate: M,
pub crossover: S,
pub select: C,
pub extension: E,
}
pub struct EvolveConfig {
pub variant: EvolveVariant,
pub fitness_ordering: FitnessOrdering,
pub par_fitness: bool,
pub replace_on_equal_fitness: bool,
pub target_fitness_score: Option<FitnessValue>,
pub max_stale_generations: Option<usize>,
pub max_generations: Option<usize>,
pub valid_fitness_score: Option<FitnessValue>,
pub fitness_cache: Option<FitnessCache>,
pub target_population_size: usize,
pub max_chromosome_age: Option<usize>,
pub abort_flag: Option<Arc<AtomicBool>>,
}
#[derive(Clone)]
pub struct EvolveState<G: EvolveGenotype> {
pub current_iteration: usize,
pub current_generation: usize,
pub stale_generations: usize,
pub scale_generation: usize,
pub best_generation: usize,
pub best_fitness_score: Option<FitnessValue>,
pub best_chromosome: Option<Chromosome<G::Allele>>,
pub chromosome: Option<Chromosome<G::Allele>>,
pub population: Population<G::Allele>,
pub durations: HashMap<StrategyAction, Duration>,
pub population_cardinality: Option<usize>,
}
impl<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
E: Extension<Genotype = G>,
SR: StrategyReporter<Genotype = G>,
> Strategy<G> for Evolve<G, M, F, S, C, E, SR>
{
fn call(&mut self) {
let now = Instant::now();
self.reporter
.on_enter(&self.genotype, &self.state, &self.config);
let mut fitness_thread_local: Option<ThreadLocal<RefCell<F>>> = None;
if self.config.par_fitness {
fitness_thread_local = Some(ThreadLocal::new());
}
self.setup(fitness_thread_local.as_ref());
self.reporter
.on_start(&self.genotype, &self.state, &self.config);
while !self.is_finished() {
self.state.increment_generation();
self.plugins
.select
.before(&self.genotype, &mut self.state, &self.config);
self.plugins.select.call(
&self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.plugins
.select
.after(&self.genotype, &mut self.state, &self.config);
self.reporter
.on_selection_complete(&self.genotype, &self.state, &self.config);
self.plugins.extension.after_selection_complete(
&mut self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.state.population.increment_age();
self.plugins
.crossover
.before(&self.genotype, &mut self.state, &self.config);
self.plugins.crossover.call(
&self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.plugins
.crossover
.after(&self.genotype, &mut self.state, &self.config);
self.reporter
.on_crossover_complete(&self.genotype, &self.state, &self.config);
self.plugins.extension.after_crossover_complete(
&mut self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.plugins
.mutate
.before(&self.genotype, &mut self.state, &self.config);
self.plugins.mutate.call(
&self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.plugins
.mutate
.after(&self.genotype, &mut self.state, &self.config);
self.reporter
.on_mutation_complete(&self.genotype, &self.state, &self.config);
self.plugins.extension.after_mutation_complete(
&mut self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.fitness.call_for_state_population(
&self.genotype,
&mut self.state,
&self.config,
fitness_thread_local.as_ref(),
);
self.state.update_best_chromosome_and_report(
&self.genotype,
&self.config,
&mut self.reporter,
);
self.reporter
.on_generation_complete(&self.genotype, &self.state, &self.config);
self.plugins.extension.after_generation_complete(
&mut self.genotype,
&mut self.state,
&self.config,
&mut self.reporter,
&mut self.rng,
);
self.state.scale(&mut self.genotype, &self.config);
}
self.reporter
.on_finish(&self.genotype, &self.state, &self.config);
self.cleanup(fitness_thread_local.as_mut());
self.state.close_duration(now.elapsed());
self.reporter
.on_exit(&self.genotype, &self.state, &self.config);
}
fn best_generation(&self) -> usize {
self.state.best_generation
}
fn best_fitness_score(&self) -> Option<FitnessValue> {
self.state.best_fitness_score()
}
fn best_genes(&self) -> Option<Genes<G::Allele>> {
self.state
.best_chromosome
.as_ref()
.map(|c| c.genes().clone())
}
fn flush_reporter(&mut self, output: &mut Vec<u8>) {
self.reporter.flush(output);
}
}
impl<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
E: Extension<Genotype = G>,
SR: StrategyReporter<Genotype = G>,
> Evolve<G, M, F, S, C, E, SR>
{
pub fn best_chromosome(&self) -> Option<Chromosome<G::Allele>> {
if let Some(best_genes) = self.best_genes() {
let mut chromosome = Chromosome::new(best_genes);
chromosome.set_fitness_score(self.best_fitness_score());
Some(chromosome)
} else {
None
}
}
}
impl<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
> Evolve<G, M, F, S, C, ExtensionNoop<G>, StrategyReporterNoop<G>>
{
pub fn builder() -> EvolveBuilder<G, M, F, S, C, ExtensionNoop<G>, StrategyReporterNoop<G>> {
EvolveBuilder::new()
}
}
impl<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
E: Extension<Genotype = G>,
SR: StrategyReporter<Genotype = G>,
> Evolve<G, M, F, S, C, E, SR>
{
pub fn setup(&mut self, fitness_thread_local: Option<&ThreadLocal<RefCell<F>>>) {
let now = Instant::now();
self.state.population = self
.genotype
.population_constructor(self.config.target_population_size, &mut self.rng);
self.state
.add_duration(StrategyAction::SetupAndCleanup, now.elapsed());
self.fitness.call_for_state_population(
&self.genotype,
&mut self.state,
&self.config,
fitness_thread_local,
);
self.state.update_best_chromosome_and_report(
&self.genotype,
&self.config,
&mut self.reporter,
);
if self.state.best_fitness_score().is_none() {
let chromosome = &self.state.population.chromosomes[0];
self.state.best_generation = self.state.current_generation;
self.state.best_chromosome = Some(chromosome.clone());
self.reporter
.on_new_best_chromosome(&self.genotype, &self.state, &self.config);
self.state.reset_stale_generations();
}
}
pub fn cleanup(&mut self, fitness_thread_local: Option<&mut ThreadLocal<RefCell<F>>>) {
let now = Instant::now();
self.state.chromosome.take();
self.state.population.chromosomes.clear();
if let Some(thread_local) = fitness_thread_local {
thread_local.clear();
}
self.state
.add_duration(StrategyAction::SetupAndCleanup, now.elapsed());
}
fn is_finished(&self) -> bool {
self.is_conclusive() || self.is_exhausted()
}
fn is_conclusive(&self) -> bool {
if self
.config
.abort_flag
.as_ref()
.is_some_and(|flag| flag.load(Ordering::Relaxed))
{
return true;
}
if let Some(target_fitness_score) = self.config.target_fitness_score {
if let Some(fitness_score) = self.best_fitness_score() {
return match self.config.fitness_ordering {
FitnessOrdering::Maximize => fitness_score >= target_fitness_score,
FitnessOrdering::Minimize => fitness_score <= target_fitness_score,
};
}
}
false
}
fn is_exhausted(&self) -> bool {
if let Some(valid_fitness_score) = self.config.valid_fitness_score {
if let Some(fitness_score) = self.best_fitness_score() {
let valid = match self.config.fitness_ordering {
FitnessOrdering::Maximize => fitness_score >= valid_fitness_score,
FitnessOrdering::Minimize => fitness_score <= valid_fitness_score,
};
if !valid {
return false;
}
}
}
if let Some(max_stale_generations) = self.config.max_stale_generations {
if self.state.stale_generations >= max_stale_generations {
return true;
}
}
if let Some(max_generations) = self.config.max_generations {
if self.state.scale_generation >= max_generations {
return true;
}
}
false
}
}
impl StrategyConfig for EvolveConfig {
fn fitness_ordering(&self) -> FitnessOrdering {
self.fitness_ordering
}
fn fitness_cache(&self) -> Option<&FitnessCache> {
self.fitness_cache.as_ref()
}
fn par_fitness(&self) -> bool {
self.par_fitness
}
fn replace_on_equal_fitness(&self) -> bool {
self.replace_on_equal_fitness
}
fn variant(&self) -> StrategyVariant {
StrategyVariant::Evolve(self.variant)
}
}
impl<G: EvolveGenotype> StrategyState<G> for EvolveState<G> {
fn chromosome_as_ref(&self) -> &Option<Chromosome<G::Allele>> {
&self.chromosome
}
fn population_as_ref(&self) -> &Population<G::Allele> {
&self.population
}
fn chromosome_as_mut(&mut self) -> &mut Option<Chromosome<G::Allele>> {
&mut self.chromosome
}
fn population_as_mut(&mut self) -> &mut Population<G::Allele> {
&mut self.population
}
fn best_generation(&self) -> usize {
self.best_generation
}
fn best_fitness_score(&self) -> Option<FitnessValue> {
self.best_fitness_score
}
fn current_generation(&self) -> usize {
self.current_generation
}
fn current_iteration(&self) -> usize {
self.current_iteration
}
fn increment_generation(&mut self) {
self.current_generation += 1;
self.scale_generation += 1;
}
fn stale_generations(&self) -> usize {
self.stale_generations
}
fn increment_stale_generations(&mut self) {
self.stale_generations += 1;
}
fn reset_stale_generations(&mut self) {
self.stale_generations = 0;
}
fn scale_generation(&self) -> usize {
self.scale_generation
}
fn reset_scale_generation(&mut self) {
self.scale_generation = 0;
}
fn population_cardinality(&self) -> Option<usize> {
self.population_cardinality
}
fn durations(&self) -> &HashMap<StrategyAction, Duration> {
&self.durations
}
fn add_duration(&mut self, action: StrategyAction, duration: Duration) {
*self.durations.entry(action).or_default() += duration;
}
fn total_duration(&self) -> Duration {
self.durations.values().sum()
}
fn best_genes(&self) -> Option<Genes<G::Allele>> {
self.best_chromosome.as_ref().map(|c| c.genes().clone())
}
}
impl<G: EvolveGenotype> EvolveState<G> {
fn update_best_chromosome_and_report<SR: StrategyReporter<Genotype = G>>(
&mut self,
genotype: &G,
config: &EvolveConfig,
reporter: &mut SR,
) {
let now = Instant::now();
if let Some(contending_chromosome) =
self.population.best_chromosome(config.fitness_ordering)
{
match self.is_better_chromosome(
contending_chromosome,
&config.fitness_ordering,
config.replace_on_equal_fitness,
) {
(true, true) => {
self.best_generation = self.current_generation;
self.best_fitness_score = contending_chromosome.fitness_score();
self.best_chromosome = Some(contending_chromosome.clone());
reporter.on_new_best_chromosome(genotype, self, config);
self.reset_stale_generations();
}
(true, false) => {
self.best_chromosome = Some(contending_chromosome.clone());
reporter.on_new_best_chromosome_equal_fitness(genotype, self, config);
self.increment_stale_generations();
}
_ => self.increment_stale_generations(),
}
} else {
self.increment_stale_generations();
}
self.add_duration(StrategyAction::UpdateBestChromosome, now.elapsed());
}
fn scale(&mut self, genotype: &mut G, config: &EvolveConfig) {
if let Some(max_generations) = config.max_generations {
if self.scale_generation >= max_generations && genotype.increment_scale_index() {
self.reset_scale_generation();
self.reset_stale_generations();
}
}
if let Some(max_stale_generations) = config.max_stale_generations {
if self.stale_generations >= max_stale_generations && genotype.increment_scale_index() {
self.reset_scale_generation();
self.reset_stale_generations();
}
}
}
pub fn update_population_cardinality(&mut self, genotype: &G, _config: &EvolveConfig) {
self.population_cardinality = if genotype.genes_hashing() {
self.population.genes_cardinality()
} else {
self.population.fitness_score_cardinality()
}
}
}
impl<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
E: Extension<Genotype = G>,
SR: StrategyReporter<Genotype = G>,
> TryFrom<EvolveBuilder<G, M, F, S, C, E, SR>> for Evolve<G, M, F, S, C, E, SR>
{
type Error = TryFromEvolveBuilderError;
fn try_from(builder: EvolveBuilder<G, M, F, S, C, E, SR>) -> Result<Self, Self::Error> {
if builder.genotype.is_none() {
Err(TryFromEvolveBuilderError(
"Evolve requires a EvolveGenotype",
))
} else if builder.fitness.is_none() {
Err(TryFromEvolveBuilderError("Evolve requires a Fitness"))
} else if builder.mutate.is_none() {
Err(TryFromEvolveBuilderError(
"Evolve requires a Mutate strategy",
))
} else if builder.crossover.is_none() {
Err(TryFromEvolveBuilderError(
"Evolve requires a Crossover strategy",
))
} else if builder.select.is_none() {
Err(TryFromEvolveBuilderError(
"Evolve requires a Select strategy",
))
} else if builder.max_stale_generations.is_none()
&& builder.max_generations.is_none()
&& builder.target_fitness_score.is_none()
{
Err(TryFromEvolveBuilderError(
"Evolve requires at least a max_stale_generations, max_generations or target_fitness_score ending condition",
))
} else {
let rng = builder.rng();
let genotype = builder.genotype.unwrap();
let state = EvolveState::new(&genotype);
let target_population_size = builder.target_population_size;
Ok(Self {
genotype,
fitness: builder.fitness.unwrap(),
plugins: EvolvePlugins {
mutate: builder.mutate.unwrap(),
crossover: builder.crossover.unwrap(),
select: builder.select.unwrap(),
extension: builder.extension,
},
config: EvolveConfig {
target_population_size,
max_stale_generations: builder.max_stale_generations,
max_generations: builder.max_generations,
max_chromosome_age: builder.max_chromosome_age,
target_fitness_score: builder.target_fitness_score,
valid_fitness_score: builder.valid_fitness_score,
fitness_ordering: builder.fitness_ordering,
fitness_cache: builder.fitness_cache,
par_fitness: builder.par_fitness,
replace_on_equal_fitness: builder.replace_on_equal_fitness,
abort_flag: builder.abort_flag,
..Default::default()
},
state,
reporter: builder.reporter,
rng,
})
}
}
}
impl Default for EvolveConfig {
fn default() -> Self {
Self {
variant: Default::default(),
target_population_size: 100,
max_stale_generations: None,
max_generations: None,
max_chromosome_age: None,
target_fitness_score: None,
valid_fitness_score: None,
fitness_ordering: FitnessOrdering::Maximize,
fitness_cache: None,
par_fitness: false,
replace_on_equal_fitness: true,
abort_flag: None,
}
}
}
impl EvolveConfig {
pub fn new() -> Self {
Self::default()
}
}
impl<G: EvolveGenotype> EvolveState<G> {
pub fn new(genotype: &G) -> Self {
Self {
current_iteration: 0,
current_generation: 0,
stale_generations: 0,
scale_generation: 0,
best_generation: 0,
best_fitness_score: None,
best_chromosome: None,
chromosome: None,
population: Population::new_empty(genotype.chromosome_recycling()),
population_cardinality: None,
durations: HashMap::new(),
}
}
}
impl<
G: EvolveGenotype,
M: Mutate<Genotype = G>,
F: Fitness<Genotype = G>,
S: Crossover<Genotype = G>,
C: Select<Genotype = G>,
E: Extension<Genotype = G>,
SR: StrategyReporter<Genotype = G>,
> fmt::Display for Evolve<G, M, F, S, C, E, SR>
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "evolve:")?;
writeln!(f, " fitness: {:?}", self.fitness)?;
writeln!(f)?;
writeln!(f, "{}", self.plugins)?;
writeln!(f, "{}", self.config)?;
writeln!(f, "{}", self.state)?;
writeln!(f, "{}", self.genotype)
}
}
impl<M: Mutate, S: Crossover, C: Select, E: Extension> fmt::Display for EvolvePlugins<M, S, C, E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "evolve_plugins:")?;
writeln!(f, " mutate: {:?}", self.mutate)?;
writeln!(f, " crossover: {:?}", self.crossover)?;
writeln!(f, " select: {:?}", self.select)?;
writeln!(f, " extension: {:?}", self.extension)
}
}
impl fmt::Display for EvolveConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "evolve_config:")?;
writeln!(
f,
" target_population_size: {}",
self.target_population_size
)?;
writeln!(
f,
" max_stale_generations: {:?}",
self.max_stale_generations
)?;
writeln!(f, " max_generations: {:?}", self.max_generations)?;
writeln!(f, " max_chromosome_age: {:?}", self.max_chromosome_age)?;
writeln!(f, " valid_fitness_score: {:?}", self.valid_fitness_score)?;
writeln!(f, " target_fitness_score: {:?}", self.target_fitness_score)?;
writeln!(f, " fitness_ordering: {:?}", self.fitness_ordering)?;
writeln!(f, " par_fitness: {:?}", self.par_fitness)
}
}
impl<G: EvolveGenotype> fmt::Display for EvolveState<G> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "evolve_state:")?;
writeln!(f, " current iteration: {:?}", self.current_iteration)?;
writeln!(f, " current generation: {:?}", self.current_generation)?;
writeln!(f, " stale generations: {:?}", self.stale_generations)?;
writeln!(
f,
" population cardinality: {:?}",
self.population_cardinality
)?;
writeln!(f, " best fitness score: {:?}", self.best_fitness_score())
}
}