#![allow(clippy::print_stdout)] use rand::{thread_rng, Rng}; use meiosis::algorithm::fitness::Configuration; use meiosis::environment::Environment; use meiosis::fitness::Fitness; use meiosis::operators::mutation::combination::Combination; use meiosis::operators::mutation::{add::Add, random::multi::nudge::Nudge, remove::Remove, swap::Swap}; use meiosis::operators::recombination::single_point::SinglePoint; use meiosis::operators::selection::fitness_proportionate::StochasticAcceptance; use meiosis::phenotype::{FromGenotype, Phenotype}; use meiosis::termination::fitness::Fitness as FitnessTermination; const TARGET: &str = "Hello, World!"; //const TARGET: &str = "Hello, I'm apparently very slow at developing!"; #[derive(Debug, Clone, PartialEq)] struct EvolvedString(Vec); impl Phenotype for EvolvedString { type Genotype = Vec; fn to_genotype(self) -> Self::Genotype { self.0 } } struct EvolveHelloWorld { target: Vec, } impl FromGenotype, EvolveHelloWorld> for EvolvedString { fn from_genotype(_rng: &mut RNG, genotype: Vec, _environment: &EvolveHelloWorld) -> Self where RNG: Rng, { EvolvedString(genotype) } } impl Environment for EvolveHelloWorld {} /// Define how well [`EvolvedString`] does in an [`EvolveHelloWorld`] environment. impl Fitness for EvolvedString { #[allow(clippy::cast_precision_loss, clippy::float_arithmetic)] fn fitness(&self, environment: &EvolveHelloWorld) -> 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 } } fn main() { // note that all chances are set to 1 because we selectively do only one mutation per mutation let mutation = Combination::selective() .and(Add { chance: 1., max_genes: None, }) .and(Remove { chance: 1., min_genes: 5.try_into().unwrap(), }) .and(Swap { chance: 1. }) .and(Nudge { maximum_distance: 10.try_into().unwrap(), rate: 0.3, }); let config = Configuration::new() // we invisibly switch to a configuration struct used to collect basic information .with_rng(thread_rng()) .with_environment(EvolveHelloWorld { target: TARGET.as_bytes().to_vec(), }) .with_selection(StochasticAcceptance::default()) .with_recombination(SinglePoint {}) .with_mutation(mutation) // we invisibly switch back to the algorithm, configuring the final details .with_termination(FitnessTermination(1.)); // set up final runtime parameters let mut classic = config .with_elitism(2) .with_speciation_threshold(0.999) .with_interspecies_breeding_chance(0.005) .with_random_population(100); // we're manually iterating the algorithm so we can print out statistics let result = loop { classic = match classic.evaluate::().check_termination() { Ok(new_evaluated) => { let iteration = new_evaluated.statistics().iteration; if iteration % 100 == 0 { let species = new_evaluated.state().species(); let population_count = species.iter().map(|s| s.population.members.len()).sum::(); println!( "generation: {iteration}, population: {population_count}, species: {}", species.len(), ); for evaluated_species in species.iter() { #[allow(clippy::expect_used)] let fittest = evaluated_species .population .members .iter() .max_by(|a, b| a.fitness.total_cmp(&b.fitness)) .expect("iterator can not be empty"); println!( "\tSpecies: {}-{}, members: {}, fitness: {}, best member: \"{}\"", evaluated_species.identifier.0, evaluated_species.identifier.1, evaluated_species.population.members.len(), fittest.fitness, String::from_utf8_lossy(&fittest.phenotype.0), ); } } // we evaluated above, so now we need to do the rest of the algorithm, otherwise // Rust complains as we're in the wrong state of the state machine new_evaluated.select().recombine().mutate().reinsert().into() } Err(terminated) => { // a termination condition triggered here, so we may want to check the result break terminated; } }; }; let species = result.state().species(); #[allow(clippy::expect_used)] let fittest = species .iter() .flat_map(|s| &s.population.members) .max_by(|a, b| a.fitness.total_cmp(&b.fitness)) .expect("iterator can not be empty"); println!( "generation: {}, fitness: {}, best member: \"{}\"", result.statistics().iteration, fittest.fitness, String::from_utf8_lossy(&fittest.phenotype.0), ); }