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_attr(docsrs, doc(cfg(feature = "builtin")))]
9#[cfg(feature = "builtin")]
10pub mod builtin;
11
12/// Used to quickly import everything this crate has to offer.
13/// Simply add `use genetic_rs::prelude::*` to begin using this crate.
14pub mod prelude;
15
16#[cfg(feature = "rayon")]
17use rayon::prelude::*;
18
19#[cfg(feature = "tracing")]
20use tracing::*;
21
22#[cfg(feature = "tracing")]
23#[allow(missing_docs)]
24pub trait Rng: rand::Rng + std::fmt::Debug {}
25
26#[cfg(feature = "tracing")]
27impl<T: rand::Rng + std::fmt::Debug> Rng for T {}
28
29#[cfg(not(feature = "tracing"))]
30#[allow(missing_docs)]
31pub trait Rng: rand::Rng {}
32
33#[cfg(not(feature = "tracing"))]
34impl<T: rand::Rng> Rng for T {}
35
36/// Tests and eliminates the unfit from the simulation.
37pub trait Eliminator<G> {
38    /// Tests and eliminates the unfit from the simulation.
39    fn eliminate(&self, genomes: Vec<G>) -> Vec<G>;
40}
41
42/// Refills the population of the simulation based on survivors.
43pub trait Repopulator<G> {
44    /// Replaces the genomes in the simulation.
45    fn repopulate(&self, genomes: &mut Vec<G>, target_size: usize);
46}
47
48/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
49/// It is blanket implemented, so you should never have to reference this directly.
50#[cfg(not(feature = "rayon"))]
51pub trait FeatureBoundedEliminator<G>: Eliminator<G> {}
52#[cfg(not(feature = "rayon"))]
53impl<G, T: Eliminator<G>> FeatureBoundedEliminator<G> for T {}
54
55/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
56/// It is blanket implemented, so you should never have to reference this directly.
57#[cfg(feature = "rayon")]
58pub trait FeatureBoundedEliminator<G>: Eliminator<G> + Send + Sync {}
59#[cfg(feature = "rayon")]
60impl<G, T: Eliminator<G> + Send + Sync> FeatureBoundedEliminator<G> for T {}
61
62/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
63/// It is blanket implemented, so you should never have to reference this directly.
64#[cfg(not(feature = "rayon"))]
65pub trait FeatureBoundedRepopulator<G>: Repopulator<G> {}
66#[cfg(not(feature = "rayon"))]
67impl<G, T: Repopulator<G>> FeatureBoundedRepopulator<G> for T {}
68
69/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
70/// It is blanket implemented, so you should never have to reference this directly.
71#[cfg(feature = "rayon")]
72pub trait FeatureBoundedRepopulator<G>: Repopulator<G> + Send + Sync {}
73#[cfg(feature = "rayon")]
74impl<G, T: Repopulator<G> + Send + Sync> FeatureBoundedRepopulator<G> for T {}
75
76/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
77/// It is blanket implemented, so you should never have to reference this directly.
78#[cfg(not(feature = "rayon"))]
79pub trait FeatureBoundedGenome {}
80#[cfg(not(feature = "rayon"))]
81impl<T> FeatureBoundedGenome for T {}
82
83/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
84/// It is blanket implemented, so you should never have to reference this directly.
85#[cfg(feature = "rayon")]
86pub trait FeatureBoundedGenome: Sized + Send + Sync {}
87#[cfg(feature = "rayon")]
88impl<T: Sized + Send + Sync> FeatureBoundedGenome for T {}
89
90/// This struct is the main entry point for the simulation. It handles the state and evolution of the genomes
91/// based on what eliminator and repopulator it receives.
92pub struct GeneticSim<G: Sized, E: FeatureBoundedEliminator<G>, R: FeatureBoundedRepopulator<G>> {
93    /// The current population of genomes
94    pub genomes: Vec<G>,
95
96    /// The eliminator used to eliminate unfit genomes
97    pub eliminator: E,
98
99    /// The repopulator used to refill the population
100    pub repopulator: R,
101}
102
103impl<G, E, R> GeneticSim<G, E, R>
104where
105    G: Sized,
106    E: FeatureBoundedEliminator<G>,
107    R: FeatureBoundedRepopulator<G>,
108{
109    /// Creates a [`GeneticSim`] with a given population of `starting_genomes` (the size of which will be retained),
110    /// a given fitness function, and a given nextgen function.
111    pub fn new(starting_genomes: Vec<G>, eliminator: E, repopulator: R) -> Self {
112        Self {
113            genomes: starting_genomes,
114            eliminator,
115            repopulator,
116        }
117    }
118
119    /// Uses the [`Eliminator`] and [`Repopulator`] provided in [`GeneticSim::new`] to create the next generation of genomes.
120    pub fn next_generation(&mut self) {
121        #[cfg(feature = "tracing")]
122        let span = span!(Level::TRACE, "next_generation");
123
124        #[cfg(feature = "tracing")]
125        let enter = span.enter();
126
127        let genomes = std::mem::take(&mut self.genomes);
128
129        let target_size = genomes.len();
130        self.genomes = self.eliminator.eliminate(genomes);
131        self.repopulator.repopulate(&mut self.genomes, target_size);
132
133        #[cfg(feature = "tracing")]
134        drop(enter);
135    }
136
137    /// Calls [`next_generation`][GeneticSim::next_generation] `count` number of times.
138    pub fn perform_generations(&mut self, count: usize) {
139        for _ in 0..count {
140            self.next_generation();
141        }
142    }
143}
144
145/// Helper trait used in the generation of random starting populations
146#[cfg(feature = "genrand")]
147#[cfg_attr(docsrs, doc(cfg(feature = "genrand")))]
148pub trait GenerateRandom {
149    /// Create a completely random instance of the genome
150    fn gen_random(rng: &mut impl Rng) -> Self;
151}
152
153/// Blanket trait used on collections that contain objects implementing [`GenerateRandom`]
154#[cfg(all(feature = "genrand", not(feature = "rayon")))]
155#[cfg_attr(docsrs, doc(cfg(feature = "genrand")))]
156pub trait GenerateRandomCollection<T>
157where
158    T: GenerateRandom,
159{
160    /// Generate a random collection of the inner objects with a given amount
161    fn gen_random(rng: &mut impl Rng, amount: usize) -> Self;
162}
163
164/// Rayon version of the [`GenerateRandomCollection`] trait
165#[cfg(all(feature = "genrand", feature = "rayon"))]
166pub trait GenerateRandomCollection<T>
167where
168    T: GenerateRandom + Send,
169{
170    /// Generate a random collection of the inner objects with the given amount. Does not pass in rng like the sync counterpart.
171    fn gen_random(amount: usize) -> Self;
172}
173
174#[cfg(not(feature = "rayon"))]
175impl<C, T> GenerateRandomCollection<T> for C
176where
177    C: FromIterator<T>,
178    T: GenerateRandom,
179{
180    #[cfg_attr(feature = "tracing", instrument)]
181    fn gen_random(rng: &mut impl Rng, amount: usize) -> Self {
182        (0..amount).map(|_| T::gen_random(rng)).collect()
183    }
184}
185
186#[cfg(feature = "rayon")]
187impl<C, T> GenerateRandomCollection<T> for C
188where
189    C: FromParallelIterator<T>,
190    T: GenerateRandom + Send,
191{
192    #[cfg_attr(feature = "tracing", instrument)]
193    fn gen_random(amount: usize) -> Self {
194        (0..amount)
195            .into_par_iter()
196            .map(|_| T::gen_random(&mut rand::rng()))
197            .collect()
198    }
199}