genetic_rs_common/builtin/
repopulator.rs

1use rand::Rng as RandRng;
2
3use crate::{Repopulator, Rng};
4
5#[cfg(feature = "tracing")]
6use tracing::*;
7
8/// Used in other traits to randomly mutate genomes a given amount
9pub trait RandomlyMutable {
10    /// Mutate the genome with a given mutation rate (0..1)
11    fn mutate(&mut self, rate: f32, rng: &mut impl Rng);
12}
13
14/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
15/// It is blanket implemented, so you should never have to reference this directly.
16#[cfg(not(feature = "tracing"))]
17pub trait FeatureBoundedRandomlyMutable: RandomlyMutable {}
18#[cfg(not(feature = "tracing"))]
19impl<T: RandomlyMutable> FeatureBoundedRandomlyMutable for T {}
20
21/// Internal trait that simply deals with the trait bounds of features to avoid duplicate code.
22/// It is blanket implemented, so you should never have to reference this directly.
23#[cfg(feature = "tracing")]
24pub trait FeatureBoundedRandomlyMutable: RandomlyMutable + std::fmt::Debug {}
25#[cfg(feature = "tracing")]
26impl<T: RandomlyMutable + std::fmt::Debug> FeatureBoundedRandomlyMutable for T {}
27
28/// Used in dividually-reproducing [`Repopulator`]s
29pub trait Mitosis: Clone + FeatureBoundedRandomlyMutable {
30    /// Create a new child with mutation. Similar to [RandomlyMutable::mutate], but returns a new instance instead of modifying the original.
31    fn divide(&self, rate: f32, rng: &mut impl Rng) -> Self {
32        let mut child = self.clone();
33        child.mutate(rate, rng);
34        child
35    }
36}
37
38/// Used in crossover-reproducing [`Repopulator`]s
39#[cfg(all(feature = "crossover", not(feature = "tracing")))]
40#[cfg_attr(docsrs, doc(cfg(feature = "crossover")))]
41pub trait Crossover: Clone + PartialEq {
42    /// Use crossover reproduction to create a new genome.
43    fn crossover(&self, other: &Self, rate: f32, rng: &mut impl Rng) -> Self;
44}
45
46/// Used in crossover-reproducing [`next_gen`]s
47#[cfg(all(feature = "crossover", feature = "tracing"))]
48#[cfg_attr(docsrs, doc(cfg(feature = "crossover")))]
49pub trait Crossover: Clone + std::fmt::Debug {
50    /// Use crossover reproduction to create a new genome.
51    fn crossover(&self, other: &Self, rate: f32, rng: &mut impl Rng) -> Self;
52}
53
54/// Used in speciated crossover nextgens. Allows for genomes to avoid crossover with ones that are too different.
55#[cfg(feature = "speciation")]
56#[cfg_attr(docsrs, doc(cfg(feature = "speciation")))]
57pub trait Speciated: Sized {
58    /// Calculates whether two genomes are similar enough to be considered part of the same species.
59    fn is_same_species(&self, other: &Self) -> bool;
60
61    /// Filters a list of genomes based on whether they are of the same species.
62    fn filter_same_species<'a>(&'a self, genomes: &'a [Self]) -> Vec<&'a Self> {
63        genomes.iter().filter(|g| self.is_same_species(g)).collect()
64    }
65}
66
67/// Repopulator that uses division reproduction to create new genomes.
68pub struct MitosisRepopulator<G: Mitosis> {
69    /// The mutation rate to use when mutating genomes. 0.0 - 1.0
70    pub mutation_rate: f32,
71    _marker: std::marker::PhantomData<G>,
72}
73
74impl<G: Mitosis> MitosisRepopulator<G> {
75    /// Creates a new [`MitosisRepopulator`].
76    pub fn new(mutation_rate: f32) -> Self {
77        Self {
78            mutation_rate,
79            _marker: std::marker::PhantomData,
80        }
81    }
82}
83
84impl<G> Repopulator<G> for MitosisRepopulator<G>
85where
86    G: Mitosis,
87{
88    fn repopulate(&self, genomes: &mut Vec<G>, target_size: usize) {
89        let mut rng = rand::rng();
90        let champions = genomes.clone();
91        let mut champs_cycle = champions.iter().cycle();
92
93        // TODO maybe rayonify
94        while genomes.len() < target_size {
95            let parent = champs_cycle.next().unwrap();
96            let child = parent.divide(self.mutation_rate, &mut rng);
97            genomes.push(child);
98        }
99    }
100}
101
102/// Repopulator that uses crossover reproduction to create new genomes.
103pub struct CrossoverRepopulator<G: Crossover> {
104    /// The mutation rate to use when mutating genomes. 0.0 - 1.0
105    pub mutation_rate: f32,
106    _marker: std::marker::PhantomData<G>,
107}
108
109impl<G: Crossover> CrossoverRepopulator<G> {
110    /// Creates a new [`CrossoverRepopulator`].
111    pub fn new(mutation_rate: f32) -> Self {
112        Self {
113            mutation_rate,
114            _marker: std::marker::PhantomData,
115        }
116    }
117}
118
119impl<G> Repopulator<G> for CrossoverRepopulator<G>
120where
121    G: Crossover,
122{
123    fn repopulate(&self, genomes: &mut Vec<G>, target_size: usize) {
124        let mut rng = rand::rng();
125        let champions = genomes.clone();
126        let mut champs_cycle = champions.iter().enumerate().cycle();
127
128        // TODO maybe rayonify
129        while genomes.len() < target_size {
130            let (i, parent1) = champs_cycle.next().unwrap();
131            let mut j = rng.random_range(1..champions.len());
132            if i == j {
133                j = 0;
134            }
135            let parent2 = &genomes[j];
136
137            #[cfg(feature = "tracing")]
138            let span = span!(
139                Level::DEBUG,
140                "crossover",
141                a = tracing::field::debug(parent1),
142                b = tracing::field::debug(parent2)
143            );
144            #[cfg(feature = "tracing")]
145            let enter = span.enter();
146
147            let child = parent1.crossover(parent2, self.mutation_rate, &mut rng);
148
149            #[cfg(feature = "tracing")]
150            drop(enter);
151
152            genomes.push(child);
153        }
154    }
155}
156
157/// Repopulator that uses crossover reproduction to create new genomes, but only between genomes of the same species.
158#[cfg(feature = "speciation")]
159pub struct SpeciatedCrossoverRepopulator<G: Crossover + Speciated + PartialEq> {
160    /// The mutation rate to use when mutating genomes. 0.0 - 1.0
161    pub mutation_rate: f32,
162    _marker: std::marker::PhantomData<G>,
163}
164
165#[cfg(feature = "speciation")]
166impl<G: Crossover + Speciated + PartialEq> SpeciatedCrossoverRepopulator<G> {
167    /// Creates a new [`SpeciatedCrossoverRepopulator`].
168    pub fn new(mutation_rate: f32) -> Self {
169        Self {
170            mutation_rate,
171            _marker: std::marker::PhantomData,
172        }
173    }
174}
175
176#[cfg(feature = "speciation")]
177impl<G> Repopulator<G> for SpeciatedCrossoverRepopulator<G>
178where
179    G: Crossover + Speciated + PartialEq,
180{
181    fn repopulate(&self, genomes: &mut Vec<G>, target_size: usize) {
182        let mut rng = rand::rng();
183        let champions = genomes.clone();
184        let mut champs_cycle = champions.iter().cycle();
185
186        // TODO maybe rayonify
187        while genomes.len() < target_size {
188            let parent1 = champs_cycle.next().unwrap();
189            let mut parent2 = &champions[rng.random_range(0..champions.len() - 1)];
190
191            while parent1 == parent2 || !parent1.is_same_species(parent2) {
192                // TODO panic or eliminate if this parent cannot find another survivor in the same species
193                parent2 = &champions[rng.random_range(0..champions.len() - 1)];
194            }
195
196            #[cfg(feature = "tracing")]
197            let span = span!(
198                Level::DEBUG,
199                "crossover",
200                a = tracing::field::debug(parent1),
201                b = tracing::field::debug(parent2)
202            );
203            #[cfg(feature = "tracing")]
204            let enter = span.enter();
205
206            let child = parent1.crossover(parent2, self.mutation_rate, &mut rng);
207
208            #[cfg(feature = "tracing")]
209            drop(enter);
210
211            genomes.push(child);
212        }
213    }
214}