use core::marker::PhantomData;
use rand::seq::IteratorRandom;
use rand::Rng;
use crate::environment::Environment;
use crate::fitness::Fitness;
use crate::genotype::{Distance, Genotype};
use crate::operators::mutation::Mutation;
use crate::operators::recombination::Recombination;
use crate::operators::selection::Selection;
use crate::phenotype::FromGenotype;
use crate::phenotype::Phenotype;
use crate::random::Random;
use crate::species::{
Bare, Evaluated as EvaluatedMember, EvaluatedPopulation, EvaluatedSpecies, Parents, Population, Species,
};
use crate::termination::Termination;
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct Unconfigured;
#[derive(Debug)]
pub struct WithRNG<RNG> {
rng: RNG,
}
#[derive(Debug)]
pub struct WithoutOperators<RNG, ENVIRONMENT> {
rng: RNG,
environment: ENVIRONMENT,
}
#[derive(Debug)]
pub struct WithSelection<RNG, ENVIRONMENT, SELECTION> {
rng: RNG,
environment: ENVIRONMENT,
selection: SELECTION,
}
#[derive(Debug)]
pub struct WithRecombination<RNG, ENVIRONMENT, SELECTION, RECOMBINATION> {
rng: RNG,
environment: ENVIRONMENT,
selection: SELECTION,
recombination: RECOMBINATION,
}
#[derive(Debug)]
pub struct WithoutTermination<RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION> {
rng: RNG,
environment: ENVIRONMENT,
selection: SELECTION,
recombination: RECOMBINATION,
mutation: MUTATION,
}
#[derive(Debug)]
pub struct Configuration<STATE> {
state: STATE,
}
impl Configuration<Unconfigured> {
#[must_use]
pub const fn new() -> Configuration<Unconfigured> {
Configuration { state: Unconfigured {} }
}
#[allow(clippy::missing_const_for_fn)]
pub fn with_rng<RNG>(self, rng: RNG) -> Configuration<WithRNG<RNG>>
where
RNG: Rng,
{
Configuration { state: WithRNG { rng } }
}
}
impl<RNG> Configuration<WithRNG<RNG>> {
#[allow(clippy::missing_const_for_fn)]
pub fn with_environment<ENVIRONMENT>(
self,
environment: ENVIRONMENT,
) -> Configuration<WithoutOperators<RNG, ENVIRONMENT>>
where
ENVIRONMENT: Environment,
{
Configuration {
state: WithoutOperators {
rng: self.state.rng,
environment,
},
}
}
}
impl<RNG, ENVIRONMENT> Configuration<WithoutOperators<RNG, ENVIRONMENT>> {
#[allow(clippy::missing_const_for_fn)]
pub fn with_selection<SELECTION>(
self,
selection: SELECTION,
) -> Configuration<WithSelection<RNG, ENVIRONMENT, SELECTION>> {
Configuration {
state: WithSelection {
rng: self.state.rng,
environment: self.state.environment,
selection,
},
}
}
}
impl<RNG, ENVIRONMENT, SELECTION> Configuration<WithSelection<RNG, ENVIRONMENT, SELECTION>> {
#[allow(clippy::missing_const_for_fn)]
pub fn with_recombination<RECOMBINATION>(
self,
recombination: RECOMBINATION,
) -> Configuration<WithRecombination<RNG, ENVIRONMENT, SELECTION, RECOMBINATION>> {
Configuration {
state: WithRecombination {
rng: self.state.rng,
environment: self.state.environment,
selection: self.state.selection,
recombination,
},
}
}
}
impl<RNG, ENVIRONMENT, SELECTION, RECOMBINATION>
Configuration<WithRecombination<RNG, ENVIRONMENT, SELECTION, RECOMBINATION>>
{
#[allow(clippy::missing_const_for_fn)]
pub fn with_mutation<MUTATION>(
self,
mutation: MUTATION,
) -> Configuration<WithoutTermination<RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION>> {
Configuration {
state: WithoutTermination {
rng: self.state.rng,
environment: self.state.environment,
selection: self.state.selection,
recombination: self.state.recombination,
mutation,
},
}
}
}
impl<RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION>
Configuration<WithoutTermination<RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION>>
{
#[allow(clippy::missing_const_for_fn)]
pub fn with_termination<TERMINATION, GENOTYPE>(
self,
termination: TERMINATION,
) -> Classic<Configured, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION> {
Classic {
state: Configured {},
genotype: PhantomData,
rng: self.state.rng,
environment: self.state.environment,
selection: self.state.selection,
recombination: self.state.recombination,
mutation: self.state.mutation,
termination,
max_population: 0,
elites: 0,
elite_members: vec![],
speciation_threshold: 0.0,
interspecies_breeding_chance: 0.0,
statistics: Statistics { iteration: 0 },
}
}
}
#[derive(Copy, Clone, Debug)]
#[non_exhaustive]
pub struct Configured;
#[derive(Debug)]
pub struct Unevaluated<GENOTYPE> {
species: Vec<Species<GENOTYPE>>,
}
impl<GENOTYPE> Unevaluated<GENOTYPE> {
#[must_use]
pub const fn species(&self) -> &Vec<Species<GENOTYPE>> {
&self.species
}
}
#[derive(Debug, PartialEq)]
pub struct Evaluated<PHENOTYPE> {
species: Vec<EvaluatedSpecies<PHENOTYPE>>,
}
impl<PHENOTYPE> Evaluated<PHENOTYPE> {
#[must_use]
pub const fn species(&self) -> &Vec<EvaluatedSpecies<PHENOTYPE>> {
&self.species
}
}
#[derive(Debug)]
pub struct Selected<PHENOTYPE> {
selected: Vec<Parents<PHENOTYPE>>,
}
impl<PHENOTYPE> Selected<PHENOTYPE> {
#[must_use]
pub const fn selected(&self) -> &Vec<Parents<PHENOTYPE>> {
&self.selected
}
}
#[derive(Debug)]
pub struct Recombined<GENOTYPE> {
species: Vec<Bare<GENOTYPE>>,
new_members: Population<GENOTYPE>,
}
impl<GENOTYPE> Recombined<GENOTYPE> {
#[must_use]
pub const fn species(&self) -> &Vec<Bare<GENOTYPE>> {
&self.species
}
}
#[derive(Debug)]
pub struct Mutated<GENOTYPE> {
species: Vec<Bare<GENOTYPE>>,
new_members: Population<GENOTYPE>,
}
#[derive(Debug)]
pub struct Terminated<PHENOTYPE> {
species: Vec<EvaluatedSpecies<PHENOTYPE>>,
}
impl<PHENOTYPE> Terminated<PHENOTYPE> {
#[must_use]
pub const fn species(&self) -> &Vec<EvaluatedSpecies<PHENOTYPE>> {
&self.species
}
}
#[derive(Debug, PartialEq)]
pub struct Classic<STATE, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION> {
state: STATE,
genotype: PhantomData<GENOTYPE>,
rng: RNG,
environment: ENVIRONMENT,
selection: SELECTION,
recombination: RECOMBINATION,
mutation: MUTATION,
termination: TERMINATION,
max_population: usize,
elites: usize,
elite_members: Vec<GENOTYPE>,
speciation_threshold: f32,
interspecies_breeding_chance: f64,
statistics: Statistics,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Statistics {
pub iteration: usize,
}
impl<STATE, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<STATE, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
pub const fn state(&self) -> &STATE {
&self.state
}
pub const fn statistics(&self) -> &Statistics {
&self.statistics
}
}
impl<GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Unconfigured, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
#[must_use]
pub const fn configure() -> Configuration<Unconfigured> {
Configuration { state: Unconfigured {} }
}
}
impl<GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Configured, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
pub fn with_population(
mut self,
population: Population<GENOTYPE>,
) -> Classic<Unevaluated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
GENOTYPE: Distance + Clone,
{
self.max_population = population.members.len();
let species = self.species_from_initial_population(population);
let state = Unevaluated { species };
Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
}
}
pub fn with_random_population(
mut self,
size: usize,
) -> Classic<Unevaluated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
GENOTYPE: Random + Distance + Clone,
RNG: Rng,
{
let members = (0..size).map(|_| GENOTYPE::random(&mut self.rng)).collect();
let population = Population { members };
self.with_population(population)
}
#[allow(clippy::todo, clippy::needless_pass_by_value, clippy::missing_panics_doc)]
pub fn with_seeded_population(
_seed: impl FromIterator<GENOTYPE>,
_size: usize,
) -> Classic<Unevaluated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
todo!()
}
#[must_use]
pub const fn with_elitism(mut self, elites: usize) -> Self {
self.elites = elites;
self
}
#[must_use]
pub fn with_speciation_threshold(mut self, threshold: f32) -> Self {
assert!(
(0.0..=1.0_f32).contains(&threshold),
"speciation threshold has to be in the range [0.0, 1.0], value is: {}",
threshold
);
self.speciation_threshold = threshold;
self
}
#[must_use]
pub fn with_interspecies_breeding_chance(mut self, chance: f64) -> Self {
assert!(
(0.0_f64..=1.0_f64).contains(&chance),
"interbreeding chance has to be in the range [0.0, 1.0], value is: {}",
chance
);
self.interspecies_breeding_chance = chance;
self
}
fn species_from_initial_population(&self, population: Population<GENOTYPE>) -> Vec<Species<GENOTYPE>>
where
GENOTYPE: Distance + Clone,
{
sort_population_to_species(1, Vec::new(), population, self.speciation_threshold)
}
}
impl<GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Unevaluated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
GENOTYPE: Genotype,
RNG: Rng,
ENVIRONMENT: Environment,
RECOMBINATION: Recombination<GENOTYPE>,
{
#[allow(clippy::type_complexity)]
pub fn run<PHENOTYPE>(
mut self,
) -> Result<
Self,
Classic<Terminated<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>,
>
where
PHENOTYPE: Phenotype<Genotype = GENOTYPE> + FromGenotype<GENOTYPE, ENVIRONMENT> + Fitness<ENVIRONMENT> + Clone,
GENOTYPE: Random + Distance + Clone + PartialEq,
SELECTION: Selection<PHENOTYPE>,
MUTATION: Mutation<GENOTYPE, RNG>,
TERMINATION: Termination<PHENOTYPE>,
[(); RECOMBINATION::OFFSPRING]:,
[(); RECOMBINATION::PARENTS]:,
{
loop {
self = self.iteration()?;
}
}
#[allow(clippy::type_complexity)]
pub fn iteration<PHENOTYPE>(
self,
) -> Result<
Self,
Classic<Terminated<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>,
>
where
PHENOTYPE: Phenotype<Genotype = GENOTYPE> + FromGenotype<GENOTYPE, ENVIRONMENT> + Fitness<ENVIRONMENT> + Clone,
GENOTYPE: Random + Distance + Clone + PartialEq,
SELECTION: Selection<PHENOTYPE>,
MUTATION: Mutation<GENOTYPE, RNG>,
TERMINATION: Termination<PHENOTYPE>,
[(); RECOMBINATION::OFFSPRING]:,
[(); RECOMBINATION::PARENTS]:,
{
Ok(self
.evaluate()
.check_termination()?
.select()
.recombine()
.mutate()
.reinsert())
}
pub fn evaluate<PHENOTYPE>(
mut self,
) -> Classic<Evaluated<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
PHENOTYPE: Phenotype<Genotype = GENOTYPE> + FromGenotype<GENOTYPE, ENVIRONMENT> + Fitness<ENVIRONMENT> + Clone,
GENOTYPE: Clone + PartialEq,
{
let Unevaluated { species } = self.state;
#[allow(clippy::shadow_reuse, clippy::shadow_unrelated, clippy::float_arithmetic)]
let species = species
.into_iter()
.map(|species| {
#[allow(clippy::cast_precision_loss, clippy::as_conversions)]
let species_size = species.population.members.len() as f64;
let phenotypes = species
.population
.members
.into_iter()
.map(|genotype| PHENOTYPE::from_genotype(&mut self.rng, genotype, &self.environment));
let evaluated = phenotypes
.map(|phenotype| {
let fitness = phenotype.fitness(&self.environment);
let shared_fitness = fitness / species_size;
EvaluatedMember {
phenotype,
fitness,
adjusted_fitness: shared_fitness,
}
})
.collect::<Vec<_>>();
let species_total_adjusted_fitness = evaluated
.iter()
.map(|e| e.adjusted_fitness)
.fold(0.0_f64, |sum, fitness| sum + fitness);
EvaluatedSpecies {
identifier: species.identifier,
sum_adjusted_fitness: species_total_adjusted_fitness,
population: EvaluatedPopulation { members: evaluated },
}
})
.collect();
self.statistics.iteration = self.statistics.iteration.saturating_add(1);
let state = Evaluated { species };
Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
}
}
}
impl<PHENOTYPE, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Evaluated<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
#[allow(clippy::type_complexity)]
pub fn check_termination(
mut self,
) -> Result<
Self,
Classic<Terminated<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>,
>
where
TERMINATION: Termination<PHENOTYPE>,
{
if self.termination.terminate(&self.statistics, &self.state.species) {
let state = Terminated {
species: self.state.species,
};
Err(Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
})
} else {
Ok(self)
}
}
#[allow(clippy::too_many_lines)]
pub fn select(
mut self,
) -> Classic<Selected<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
PHENOTYPE: Phenotype<Genotype = GENOTYPE> + Clone,
RNG: Rng,
SELECTION: Selection<PHENOTYPE>,
{
#[allow(clippy::float_arithmetic)]
let total_adjusted_fitness = self
.state
.species
.iter()
.map(|s| s.sum_adjusted_fitness)
.fold(0.0_f64, |sum, fitness| sum + fitness);
let selected: Vec<Parents<PHENOTYPE>> = if total_adjusted_fitness == 0.0 {
self.state
.species
.into_iter()
.map(|species| {
#[allow(clippy::expect_used)]
let representative = species
.population
.members
.iter()
.choose(&mut self.rng)
.expect("selection should not be empty")
.phenotype
.clone();
Parents {
identifier: species.identifier,
max_size: species.population.members.len(),
representative,
population: species.population,
}
})
.collect()
} else {
self.state
.species
.into_iter()
.filter_map(|species| {
#[allow(
clippy::cast_precision_loss,
clippy::float_arithmetic,
clippy::cast_possible_truncation
)]
#[allow(clippy::cast_sign_loss, clippy::as_conversions)]
let offspring =
((species.sum_adjusted_fitness / total_adjusted_fitness) * self.max_population as f64) as usize;
if offspring == 0 {
return None;
}
let mut final_selected = Vec::with_capacity(offspring);
let mut available = species.population.members;
if self.elites > 0 {
available.sort_unstable_by(|e1, e2| e2.fitness.total_cmp(&e1.fitness));
let elites = available
.iter()
.filter(|e| e.fitness > 0.0_f64)
.take(self.elites)
.cloned()
.map(|e| e.phenotype.to_genotype())
.collect::<Vec<_>>();
self.elite_members.extend(elites);
}
while final_selected.len() < offspring {
let missing = offspring.saturating_sub(final_selected.len());
let (mut selected, non_selected) = self.selection.select(&mut self.rng, available);
debug_assert!(!selected.is_empty());
debug_assert!(!non_selected.is_empty());
if missing >= selected.len() {
final_selected.append(&mut selected);
} else {
final_selected.extend(selected.into_iter().take(missing));
}
available = non_selected;
}
#[allow(clippy::expect_used)]
let representative = final_selected
.iter()
.choose(&mut self.rng)
.expect("selection should not be empty")
.phenotype
.clone();
Some(Parents {
identifier: species.identifier,
max_size: offspring,
representative,
population: EvaluatedPopulation {
members: final_selected,
},
})
})
.collect()
};
let state = Selected { selected };
Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
}
}
}
impl<PHENOTYPE, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Selected<PHENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
#[allow(clippy::indexing_slicing, clippy::too_many_lines)]
pub fn recombine(
mut self,
) -> Classic<Recombined<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
PHENOTYPE: Phenotype<Genotype = GENOTYPE>,
GENOTYPE: Genotype + Random,
RNG: Rng,
RECOMBINATION: Recombination<GENOTYPE>,
[(); RECOMBINATION::OFFSPRING]:,
[(); RECOMBINATION::PARENTS]:,
{
let mut populations = Vec::with_capacity(self.state.selected.len());
let mut empty_species: Vec<Bare<GENOTYPE>> = Vec::with_capacity(self.state.selected.len());
let offspring_size = empty_species.iter().map(|s| s.max_size).sum();
let mut offspring = Vec::with_capacity(offspring_size);
for species in self.state.selected {
let Parents {
identifier,
max_size,
representative,
population,
} = species;
empty_species.push(Bare {
identifier,
max_size,
representative: representative.to_genotype(),
});
let members = population
.members
.into_iter()
.map(|m| m.phenotype.to_genotype())
.collect::<Vec<_>>();
populations.push(members);
}
while !populations.is_empty() {
let parent_index = self.rng.gen_range(0..populations[0].len());
let parent_1 = populations[0].remove(parent_index);
let crossbreed = self.rng.gen_bool(self.interspecies_breeding_chance);
if populations[0].is_empty() {
let _empty_population = populations.remove(0);
if !crossbreed {
offspring.push(parent_1);
continue;
}
}
if crossbreed && populations.is_empty() {
offspring.push(parent_1);
break;
}
if crossbreed {
let sum_of_crossbreedable_members =
populations.get(1..).map_or(0, |pops| pops.iter().map(Vec::len).sum());
if sum_of_crossbreedable_members < RECOMBINATION::PARENTS.saturating_sub(1) {
let remaining_members = populations.into_iter().flatten();
offspring.extend(remaining_members);
break;
}
let mut parent_one = Some(parent_1);
let parents = core::array::from_fn(|i| {
if i == 0 {
#[allow(clippy::expect_used)]
return parent_one.take().expect("option can't be None");
}
let population = self.rng.gen_range(1..populations.len());
let member = self.rng.gen_range(0..populations[population].len());
let parent = populations[population].remove(member);
if populations[population].is_empty() {
let _empty_population = populations.remove(population);
}
parent
});
offspring.extend(self.recombination.crossover(&mut self.rng, parents));
continue;
}
if populations[0].len() >= RECOMBINATION::PARENTS.saturating_sub(1) {
let mut parent_one = Some(parent_1);
let parents = core::array::from_fn(|i| {
if i == 0 {
#[allow(clippy::expect_used)]
return parent_one.take().expect("option can't be None");
}
let remaining_parent = self.rng.gen_range(0..populations[0].len());
populations[0].remove(remaining_parent)
});
if populations[0].is_empty() {
let _empty_population = populations.remove(0);
}
offspring.extend(self.recombination.crossover(&mut self.rng, parents));
continue;
}
offspring.push(parent_1);
}
let state = Recombined {
species: empty_species,
new_members: Population { members: offspring },
};
Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
}
}
}
impl<GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Recombined<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
pub fn mutate(
mut self,
) -> Classic<Mutated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
GENOTYPE: Genotype,
RNG: Rng,
MUTATION: Mutation<GENOTYPE, RNG>,
{
let Recombined {
species,
mut new_members,
} = self.state;
for member in &mut new_members.members {
self.mutation.mutate(member, &mut self.rng);
}
let state = Mutated { species, new_members };
Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
}
}
}
impl<GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
Classic<Mutated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
{
pub fn reinsert(
mut self,
) -> Classic<Unevaluated<GENOTYPE>, GENOTYPE, RNG, ENVIRONMENT, SELECTION, RECOMBINATION, MUTATION, TERMINATION>
where
GENOTYPE: Distance + Clone + Random,
RNG: Rng,
{
let Mutated {
species,
mut new_members,
} = self.state;
new_members.members.append(&mut self.elite_members);
if new_members.members.len() < self.max_population {
let missing = self.max_population.saturating_sub(new_members.members.len());
for _ in 0..missing {
new_members.members.push(GENOTYPE::random(&mut self.rng));
}
}
let new_generation_species = species
.into_iter()
.map(|s| Species {
identifier: s.identifier,
representative: s.representative,
population: Population {
members: Vec::with_capacity(s.max_size),
},
})
.collect();
#[allow(clippy::shadow_unrelated, clippy::expect_used)]
let mut species = sort_population_to_species(
self.statistics
.iteration
.checked_add(1)
.expect("iteration counter overflowed"),
new_generation_species,
new_members,
self.speciation_threshold,
);
species.retain(|s| !s.population.members.is_empty());
let state = Unevaluated { species };
Classic {
state,
genotype: self.genotype,
rng: self.rng,
environment: self.environment,
selection: self.selection,
recombination: self.recombination,
mutation: self.mutation,
termination: self.termination,
max_population: self.max_population,
elites: self.elites,
elite_members: self.elite_members,
speciation_threshold: self.speciation_threshold,
interspecies_breeding_chance: self.interspecies_breeding_chance,
statistics: self.statistics,
}
}
}
fn sort_population_to_species<GENOTYPE>(
identification_start: usize,
mut species: Vec<Species<GENOTYPE>>,
population: Population<GENOTYPE>,
threshold: f32,
) -> Vec<Species<GENOTYPE>>
where
GENOTYPE: Distance + Clone,
{
if threshold == 0.0 {
assert!(
species.len() <= 1,
"there should be only one species when speciation is turned off, found {}",
species.len()
);
}
let mut species_this_iteration = 1;
#[allow(clippy::expect_used)]
'members: for member in population.members {
for single_species in &mut species {
if single_species.representative.distance(&member) < threshold || threshold == 0.0 {
single_species.population.members.push(member);
continue 'members;
}
}
let new_species = Species {
identifier: (identification_start, species_this_iteration),
representative: member.clone(),
population: Population { members: vec![member] },
};
species.push(new_species);
species_this_iteration = species_this_iteration
.checked_add(1)
.expect("ran out of species identifiers for iteration");
}
species
}
#[allow(dead_code)]
struct Interactive;
#[cfg(test)]
mod tests {
use core::marker::PhantomData;
use core::num::NonZeroUsize;
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
use pretty_assertions::assert_eq;
use crate::algorithm::fitness::{Classic, Evaluated, Statistics, Unevaluated};
use crate::environment::Environment;
use crate::fitness::Fitness as FitnessTrait;
use crate::operators::mutation::random::single::nudge::Nudge;
use crate::operators::recombination::single_point::SinglePoint;
use crate::operators::selection::truncate::Truncation;
use crate::phenotype::{FromGenotype, Phenotype};
use crate::species::{Evaluated as EvaluatedMember, EvaluatedPopulation, EvaluatedSpecies, Population, Species};
use crate::termination::fitness::Fitness;
const TARGET: [u8; 10] = [3, 3, 3, 3, 3, 3, 3, 3, 3, 3];
#[derive(Clone, Debug, PartialEq)]
struct TestPhenotype([u8; 10]);
impl Phenotype for TestPhenotype {
type Genotype = [u8; 10];
fn to_genotype(self) -> Self::Genotype {
self.0
}
}
#[derive(Debug, PartialEq)]
struct TestEnvironment {
target: [u8; 10],
}
impl Environment for TestEnvironment {}
impl FromGenotype<[u8; 10], TestEnvironment> for TestPhenotype {
fn from_genotype<RNG>(_rng: &mut RNG, genotype: [u8; 10], _environment: &TestEnvironment) -> Self
where
RNG: Rng,
{
TestPhenotype(genotype)
}
}
impl FitnessTrait<TestEnvironment> for TestPhenotype {
#[allow(clippy::cast_precision_loss, clippy::float_arithmetic)]
fn fitness(&self, environment: &TestEnvironment) -> f64 {
let chars = &self.0;
let target = &environment.target;
let correct = chars
.iter()
.zip(target)
.map(|(c, t)| c == t)
.filter(|result| *result)
.count();
correct as f64 / self.0.len().max(environment.target.len()) as f64
}
}
#[test]
fn evaluation() {
let algo = Classic {
state: Unevaluated {
species: vec![
Species {
identifier: (0, 0),
representative: [0, 0, 0, 0, 3, 3, 3, 3, 3, 3],
population: Population {
members: vec![
[0, 0, 0, 0, 3, 3, 3, 3, 3, 3], [0, 0, 0, 0, 0, 0, 3, 3, 3, 3], ],
},
},
Species {
identifier: (0, 1),
representative: [0, 0, 0, 0, 0, 3, 3, 3, 3, 3],
population: Population {
members: vec![
[0, 0, 0, 0, 0, 3, 3, 3, 3, 3], ],
},
},
],
},
genotype: PhantomData::<[u8; 10]>,
rng: SmallRng::seed_from_u64(0),
environment: TestEnvironment { target: TARGET },
selection: Truncation {
proportion_to_keep: 1.0,
},
recombination: SinglePoint,
mutation: Nudge {
maximum_distance: NonZeroUsize::new(1).unwrap(),
chance: 0.0,
},
termination: Fitness(1.0),
max_population: 10,
elites: 0, elite_members: vec![],
speciation_threshold: 0.0, interspecies_breeding_chance: 0.0, statistics: Statistics { iteration: 0 },
};
let result = algo.evaluate::<TestPhenotype>();
let expected = Classic {
state: Evaluated {
species: vec![
EvaluatedSpecies {
identifier: (0, 0),
sum_adjusted_fitness: 0.5,
population: EvaluatedPopulation {
members: vec![
EvaluatedMember {
phenotype: TestPhenotype([0, 0, 0, 0, 3, 3, 3, 3, 3, 3]),
fitness: 0.6,
adjusted_fitness: 0.3,
},
EvaluatedMember {
phenotype: TestPhenotype([0, 0, 0, 0, 0, 0, 3, 3, 3, 3]),
fitness: 0.4,
adjusted_fitness: 0.2,
},
],
},
},
EvaluatedSpecies {
identifier: (0, 1),
sum_adjusted_fitness: 0.5,
population: EvaluatedPopulation {
members: vec![EvaluatedMember {
phenotype: TestPhenotype([0, 0, 0, 0, 0, 3, 3, 3, 3, 3]),
fitness: 0.5,
adjusted_fitness: 0.5,
}],
},
},
],
},
genotype: PhantomData::<[u8; 10]>,
rng: SmallRng::seed_from_u64(0),
environment: TestEnvironment { target: TARGET },
selection: Truncation {
proportion_to_keep: 1.0,
},
recombination: SinglePoint,
mutation: Nudge {
maximum_distance: NonZeroUsize::new(1).unwrap(),
chance: 0.0,
},
termination: Fitness(1.0),
max_population: 10,
elites: 0,
elite_members: vec![],
speciation_threshold: 0.0,
interspecies_breeding_chance: 0.0,
statistics: Statistics { iteration: 1 },
};
assert_eq!(expected, result);
}
}