Skip to main content

genetic_rs_common/
lib.rs

1#![warn(missing_docs)]
2#![allow(clippy::needless_doctest_main)]
3#![cfg_attr(docsrs, feature(doc_cfg))]
4
5//! The crate containing the core traits and structs of genetic-rs.
6
7/// Built-in nextgen functions and traits to go with them.
8#[cfg(feature = "builtin")]
9pub mod builtin;
10
11/// Used to quickly import everything this crate has to offer.
12/// Simply add `use genetic_rs::prelude::*` to begin using this crate.
13pub mod prelude;
14
15#[cfg(feature = "rayon")]
16use rayon::prelude::*;
17
18/// Tests and eliminates the unfit from the simulation.
19pub trait Eliminator<G> {
20    /// Tests and eliminates the unfit from the simulation.
21    fn eliminate(&mut self, genomes: Vec<G>) -> Vec<G>;
22}
23
24/// Refills the population of the simulation based on survivors.
25pub trait Repopulator<G> {
26    /// Replaces the genomes in the simulation.
27    fn repopulate(&mut self, genomes: &mut Vec<G>, target_size: usize);
28}
29
30#[doc(hidden)]
31#[cfg(not(feature = "rayon"))]
32pub trait FeatureBoundedEliminator<G>: Eliminator<G> {}
33#[cfg(not(feature = "rayon"))]
34impl<G, T: Eliminator<G>> FeatureBoundedEliminator<G> for T {}
35
36#[doc(hidden)]
37#[cfg(feature = "rayon")]
38pub trait FeatureBoundedEliminator<G>: Eliminator<G> + Send + Sync {}
39#[cfg(feature = "rayon")]
40impl<G, T: Eliminator<G> + Send + Sync> FeatureBoundedEliminator<G> for T {}
41
42#[doc(hidden)]
43#[cfg(not(feature = "rayon"))]
44pub trait FeatureBoundedRepopulator<G>: Repopulator<G> {}
45#[cfg(not(feature = "rayon"))]
46impl<G, T: Repopulator<G>> FeatureBoundedRepopulator<G> for T {}
47
48#[doc(hidden)]
49#[cfg(feature = "rayon")]
50pub trait FeatureBoundedRepopulator<G>: Repopulator<G> + Send + Sync {}
51#[cfg(feature = "rayon")]
52impl<G, T: Repopulator<G> + Send + Sync> FeatureBoundedRepopulator<G> for T {}
53
54#[doc(hidden)]
55#[cfg(not(feature = "rayon"))]
56pub trait FeatureBoundedGenome {}
57#[cfg(not(feature = "rayon"))]
58impl<T> FeatureBoundedGenome for T {}
59
60#[doc(hidden)]
61#[cfg(feature = "rayon")]
62pub trait FeatureBoundedGenome: Sized + Send + Sync {}
63#[cfg(feature = "rayon")]
64impl<T: Sized + Send + Sync> FeatureBoundedGenome for T {}
65
66/// This struct is the main entry point for the simulation. It handles the state and evolution of the genomes
67/// based on what eliminator and repopulator it receives.
68pub struct GeneticSim<G: Sized, E: FeatureBoundedEliminator<G>, R: FeatureBoundedRepopulator<G>> {
69    /// The current population of genomes
70    pub genomes: Vec<G>,
71
72    /// The eliminator used to eliminate unfit genomes
73    pub eliminator: E,
74
75    /// The repopulator used to refill the population
76    pub repopulator: R,
77}
78
79impl<G, E, R> GeneticSim<G, E, R>
80where
81    G: Sized,
82    E: FeatureBoundedEliminator<G>,
83    R: FeatureBoundedRepopulator<G>,
84{
85    /// Creates a [`GeneticSim`] with a given population of `starting_genomes` (the size of which will be retained),
86    /// a given fitness function, and a given nextgen function.
87    pub fn new(starting_genomes: Vec<G>, eliminator: E, repopulator: R) -> Self {
88        Self {
89            genomes: starting_genomes,
90            eliminator,
91            repopulator,
92        }
93    }
94
95    /// Uses the [`Eliminator`] and [`Repopulator`] provided in [`GeneticSim::new`] to create the next generation of genomes.
96    pub fn next_generation(&mut self) {
97        let genomes = std::mem::take(&mut self.genomes);
98
99        let target_size = genomes.len();
100        self.genomes = self.eliminator.eliminate(genomes);
101        self.repopulator.repopulate(&mut self.genomes, target_size);
102    }
103
104    /// Calls [`next_generation`][GeneticSim::next_generation] `count` number of times.
105    pub fn perform_generations(&mut self, count: usize) {
106        for _ in 0..count {
107            self.next_generation();
108        }
109    }
110}
111
112/// Helper trait used in the generation of random starting populations
113#[cfg(feature = "genrand")]
114pub trait GenerateRandom {
115    /// Create a completely random instance of the genome
116    fn gen_random(rng: &mut impl rand::Rng) -> Self;
117}
118
119/// Blanket trait used on collections that contain objects implementing [`GenerateRandom`]
120#[cfg(feature = "genrand")]
121pub trait GenerateRandomCollection<T>
122where
123    T: GenerateRandom,
124{
125    /// Generate a random collection of the inner objects with a given amount
126    fn gen_random(rng: &mut impl rand::Rng, amount: usize) -> Self;
127}
128
129/// Rayon version of the [`GenerateRandomCollection`] trait
130#[cfg(all(feature = "genrand", feature = "rayon"))]
131pub trait GenerateRandomCollectionParallel<T>
132where
133    T: GenerateRandom + Send,
134{
135    /// Generate a random collection of the inner objects with the given amount. Does not pass in rng like the sync counterpart.
136    fn par_gen_random(amount: usize) -> Self;
137}
138
139impl<C, T> GenerateRandomCollection<T> for C
140where
141    C: FromIterator<T>,
142    T: GenerateRandom,
143{
144    fn gen_random(rng: &mut impl rand::Rng, amount: usize) -> Self {
145        (0..amount).map(|_| T::gen_random(rng)).collect()
146    }
147}
148
149#[cfg(feature = "rayon")]
150impl<C, T> GenerateRandomCollectionParallel<T> for C
151where
152    C: FromParallelIterator<T>,
153    T: GenerateRandom + Send,
154{
155    fn par_gen_random(amount: usize) -> Self {
156        (0..amount)
157            .into_par_iter()
158            .map(|_| T::gen_random(&mut rand::rng()))
159            .collect()
160    }
161}