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