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
7use replace_with::replace_with_or_abort;
8
9/// Built-in nextgen functions and traits to go with them.
10#[cfg_attr(docsrs, doc(cfg(feature = "builtin")))]
11#[cfg(feature = "builtin")]
12pub mod builtin;
13
14/// Used to quickly import everything this crate has to offer.
15/// Simply add `use genetic_rs::prelude::*` to begin using this crate.
16pub mod prelude;
17
18#[cfg(feature = "rayon")]
19use rayon::prelude::*;
20
21/// Represents a fitness function. Inputs a reference to the genome and outputs an f32.
22pub trait FitnessFn<G> {
23    /// Evaluates a genome's fitness
24    fn fitness(&self, genome: &G) -> f32;
25}
26
27impl<F: Fn(&G) -> f32, G> FitnessFn<G> for F {
28    fn fitness(&self, genome: &G) -> f32 {
29        (self)(genome)
30    }
31}
32
33/// Represents a nextgen function. Inputs genomes and rewards and produces the next generation
34pub trait NextgenFn<G> {
35    /// Creates the next generation from the current fitness values.
36    fn next_gen(&self, fitness: Vec<(G, f32)>) -> Vec<G>;
37}
38
39impl<F: Fn(Vec<(G, f32)>) -> Vec<G>, G> NextgenFn<G> for F {
40    fn next_gen(&self, fitness: Vec<(G, f32)>) -> Vec<G> {
41        (self)(fitness)
42    }
43}
44
45/// The simulation controller.
46/// ```rust
47/// use genetic_rs_common::prelude::*;
48///
49/// #[derive(Debug, Clone)]
50/// struct MyGenome {
51///     a: f32,
52///     b: f32,
53/// }
54///
55/// impl RandomlyMutable for MyGenome {
56///     fn mutate(&mut self, rate: f32, rng: &mut impl rand::Rng) {
57///         self.a += rng.gen::<f32>() * rate;
58///         self.b += rng.gen::<f32>() * rate;
59///     }
60/// }
61///
62/// impl DivisionReproduction for MyGenome {
63///     fn divide(&self, rng: &mut impl rand::Rng) -> Self {
64///         let mut child = self.clone();
65///         child.mutate(0.25, rng); // you'll generally want to use a constant mutation rate for mutating children.
66///         child
67///     }
68/// }
69///
70/// impl Prunable for MyGenome {} // if we wanted to, we could implement the `despawn` function to run any cleanup code as needed. in this example, though, we do not need it.
71///
72/// impl GenerateRandom for MyGenome {
73///     fn gen_random(rng: &mut impl rand::Rng) -> Self {
74///         Self {
75///             a: rng.gen(),
76///             b: rng.gen(),
77///         }
78///     }
79/// }
80///
81/// fn main() {
82///     let my_fitness_fn = |e: &MyGenome| {
83///         e.a * e.b // should result in genomes increasing their value
84///     };
85///
86///     let mut rng = rand::thread_rng();
87///
88///     let mut sim = GeneticSim::new(
89///         Vec::gen_random(&mut rng, 1000),
90///         my_fitness_fn,
91///         division_pruning_nextgen,
92///     );
93///
94///     for _ in 0..100 {
95///         // if this were a more complex simulation, you might test genomes in `sim.genomes` between `next_generation` calls to provide a more accurate reward.
96///         sim.next_generation();
97///     }
98///
99///     dbg!(sim.genomes);
100/// }
101/// ```
102#[cfg(not(feature = "rayon"))]
103pub struct GeneticSim<F, NG, G>
104where
105    F: FitnessFn<G>,
106    NG: NextgenFn<G>,
107    G: Sized,
108{
109    /// The current population of genomes
110    pub genomes: Vec<G>,
111    fitness: F,
112    next_gen: NG,
113}
114
115/// Rayon version of the [`GeneticSim`] struct
116#[cfg(feature = "rayon")]
117pub struct GeneticSim<F, NG, G>
118where
119    F: FitnessFn<G> + Send + Sync,
120    NG: NextgenFn<G> + Send + Sync,
121    G: Sized + Send,
122{
123    /// The current population of genomes
124    pub genomes: Vec<G>,
125    fitness: F,
126    next_gen: NG,
127}
128
129#[cfg(not(feature = "rayon"))]
130impl<F, NG, G> GeneticSim<F, NG, G>
131where
132    F: FitnessFn<G>,
133    NG: NextgenFn<G>,
134    G: Sized,
135{
136    /// Creates a [`GeneticSim`] with a given population of `starting_genomes` (the size of which will be retained),
137    /// a given fitness function, and a given nextgen function.
138    pub fn new(starting_genomes: Vec<G>, fitness: F, next_gen: NG) -> Self {
139        Self {
140            genomes: starting_genomes,
141            fitness,
142            next_gen,
143        }
144    }
145
146    /// Uses the `next_gen` provided in [`GeneticSim::new`] to create the next generation of genomes.
147    pub fn next_generation(&mut self) {
148        // TODO maybe remove unneccessary dependency, can prob use std::mem::replace
149        replace_with_or_abort(&mut self.genomes, |genomes| {
150            let rewards = genomes
151                .into_iter()
152                .map(|e| {
153                    let fitness: f32 = self.fitness.fitness(&e);
154                    (e, fitness)
155                })
156                .collect();
157
158            self.next_gen.next_gen(rewards)
159        });
160    }
161
162    /// Calls [`next_generation`][GeneticSim::next_generation] `count` number of times.
163    pub fn perform_generations(&mut self, count: usize) {
164        for _ in 0..count {
165            self.next_generation();
166        }
167    }
168}
169
170#[cfg(feature = "rayon")]
171impl<F, NG, G> GeneticSim<F, NG, G>
172where
173    F: FitnessFn<G> + Send + Sync,
174    NG: NextgenFn<G> + Send + Sync,
175    G: Sized + Send,
176{
177    /// Creates a [`GeneticSim`] with a given population of `starting_genomes` (the size of which will be retained),
178    /// a given fitness function, and a given nextgen function.
179    pub fn new(starting_genomes: Vec<G>, fitness: F, next_gen: NG) -> Self {
180        Self {
181            genomes: starting_genomes,
182            fitness,
183            next_gen,
184        }
185    }
186
187    /// Performs selection and produces the next generation within the simulation.
188    pub fn next_generation(&mut self) {
189        replace_with_or_abort(&mut self.genomes, |genomes| {
190            let rewards = genomes
191                .into_par_iter()
192                .map(|e| {
193                    let fitness: f32 = self.fitness.fitness(&e);
194                    (e, fitness)
195                })
196                .collect();
197
198            self.next_gen.next_gen(rewards)
199        });
200    }
201
202    /// Calls [`next_generation`][GeneticSim::next_generation] `count` number of times.
203    pub fn perform_generations(&mut self, count: usize) {
204        for _ in 0..count {
205            self.next_generation();
206        }
207    }
208}
209
210#[cfg(feature = "genrand")]
211use rand::prelude::*;
212
213/// Helper trait used in the generation of random starting populations
214#[cfg(feature = "genrand")]
215#[cfg_attr(docsrs, doc(cfg(feature = "genrand")))]
216pub trait GenerateRandom {
217    /// Create a completely random instance of the genome
218    fn gen_random(rng: &mut impl Rng) -> Self;
219}
220
221/// Blanket trait used on collections that contain objects implementing [`GenerateRandom`]
222#[cfg(all(feature = "genrand", not(feature = "rayon")))]
223#[cfg_attr(docsrs, doc(cfg(feature = "genrand")))]
224pub trait GenerateRandomCollection<T>
225where
226    T: GenerateRandom,
227{
228    /// Generate a random collection of the inner objects with a given amount
229    fn gen_random(rng: &mut impl Rng, amount: usize) -> Self;
230}
231
232/// Rayon version of the [`GenerateRandomCollection`] trait
233#[cfg(all(feature = "genrand", feature = "rayon"))]
234pub trait GenerateRandomCollection<T>
235where
236    T: GenerateRandom + Send,
237{
238    /// Generate a random collection of the inner objects with the given amount. Does not pass in rng like the sync counterpart.
239    fn gen_random(amount: usize) -> Self;
240}
241
242#[cfg(not(feature = "rayon"))]
243impl<C, T> GenerateRandomCollection<T> for C
244where
245    C: FromIterator<T>,
246    T: GenerateRandom,
247{
248    fn gen_random(rng: &mut impl Rng, amount: usize) -> Self {
249        (0..amount).map(|_| T::gen_random(rng)).collect()
250    }
251}
252
253#[cfg(feature = "rayon")]
254impl<C, T> GenerateRandomCollection<T> for C
255where
256    C: FromParallelIterator<T>,
257    T: GenerateRandom + Send,
258{
259    fn gen_random(amount: usize) -> Self {
260        (0..amount)
261            .into_par_iter()
262            .map(|_| T::gen_random(&mut rand::thread_rng()))
263            .collect()
264    }
265}
266
267#[cfg(test)]
268mod tests {
269    use super::prelude::*;
270
271    #[test]
272    fn send_sim() {
273        let mut sim = GeneticSim::new(vec![()], |_: &()| 0., |_: Vec<((), f32)>| vec![()]);
274
275        let h = std::thread::spawn(move || {
276            sim.next_generation();
277        });
278
279        h.join().unwrap();
280    }
281}