1use rand::Rng;
20use rand_distr::{Distribution, Normal};
21
22use crate::error::GenomeError;
23use crate::genome::bounds::MultiBounds;
24use crate::genome::dynamic_real_vector::DynamicRealVector;
25use crate::genome::traits::{EvolutionaryGenome, RealValuedGenome};
26
27fn repair_length(mut genes: Vec<f64>, min_length: usize, max_length: usize) -> Vec<f64> {
33 if genes.len() > max_length {
34 genes.truncate(max_length);
35 }
36 while genes.len() < min_length {
37 let fill = genes.last().copied().unwrap_or(0.0);
38 genes.push(fill);
39 }
40 genes
41}
42
43pub fn cut_and_splice<R: Rng>(
54 parent1: &DynamicRealVector,
55 parent2: &DynamicRealVector,
56 rng: &mut R,
57) -> Result<(DynamicRealVector, DynamicRealVector), GenomeError> {
58 let min_length = parent1.min_length().max(parent2.min_length());
59 let max_length = parent1.max_length().min(parent2.max_length());
60 if min_length > max_length {
61 return Err(GenomeError::InvalidStructure(format!(
62 "cut_and_splice: parents have incompatible length constraints \
63 (combined min_length {min_length} > max_length {max_length})"
64 )));
65 }
66
67 let g1 = parent1.genes();
68 let g2 = parent2.genes();
69 let cut1 = rng.gen_range(0..=g1.len());
71 let cut2 = rng.gen_range(0..=g2.len());
72
73 let mut child1: Vec<f64> = g1[..cut1].to_vec();
74 child1.extend_from_slice(&g2[cut2..]);
75 let mut child2: Vec<f64> = g2[..cut2].to_vec();
76 child2.extend_from_slice(&g1[cut1..]);
77
78 let child1 = repair_length(child1, min_length, max_length);
79 let child2 = repair_length(child2, min_length, max_length);
80
81 Ok((
82 DynamicRealVector::new(child1, min_length, max_length)?,
83 DynamicRealVector::new(child2, min_length, max_length)?,
84 ))
85}
86
87#[derive(Clone, Copy, Debug)]
101pub struct DynamicGaussianMutation {
102 pub sigma: f64,
104 pub gene_mutation_prob: f64,
106 pub grow_prob: f64,
108 pub shrink_prob: f64,
110}
111
112impl DynamicGaussianMutation {
113 pub fn new(sigma: f64, gene_mutation_prob: f64, grow_prob: f64, shrink_prob: f64) -> Self {
118 assert!(sigma >= 0.0, "sigma must be non-negative");
119 for (name, p) in [
120 ("gene_mutation_prob", gene_mutation_prob),
121 ("grow_prob", grow_prob),
122 ("shrink_prob", shrink_prob),
123 ] {
124 assert!(
125 (0.0..=1.0).contains(&p),
126 "{name} must be in [0, 1], got {p}"
127 );
128 }
129 Self {
130 sigma,
131 gene_mutation_prob,
132 grow_prob,
133 shrink_prob,
134 }
135 }
136
137 pub fn mutate<R: Rng>(
139 &self,
140 genome: &mut DynamicRealVector,
141 bounds: &MultiBounds,
142 rng: &mut R,
143 ) {
144 if self.sigma > 0.0 && self.gene_mutation_prob > 0.0 {
146 let normal = Normal::new(0.0, self.sigma).expect("sigma > 0");
147 for gene in genome.genes_mut().iter_mut() {
148 if rng.gen::<f64>() < self.gene_mutation_prob {
149 *gene += normal.sample(rng);
150 }
151 }
152 genome.apply_bounds(bounds);
153 }
154
155 if self.grow_prob > 0.0 && rng.gen::<f64>() < self.grow_prob && genome.can_grow() {
157 let len = genome.dimension();
158 let index = rng.gen_range(0..=len);
159 let value = match bounds.get(index).or_else(|| bounds.get(0)) {
160 Some(b) => rng.gen_range(b.min..=b.max),
161 None => 0.0,
162 };
163 let _ = genome.insert(index, value);
165 }
166
167 if self.shrink_prob > 0.0 && rng.gen::<f64>() < self.shrink_prob && genome.can_shrink() {
169 let len = genome.dimension();
170 if len > 0 {
171 let index = rng.gen_range(0..len);
172 let _ = genome.remove(index);
173 }
174 }
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181 use proptest::prelude::*;
182
183 proptest! {
184 #[test]
185 fn cut_and_splice_lengths_and_values_within_range(
186 g1 in prop::collection::vec(-5.0..5.0f64, 1..12),
187 g2 in prop::collection::vec(-5.0..5.0f64, 1..12),
188 ) {
189 let min_length = 1;
193 let max_length = 16;
194 let p1 = DynamicRealVector::new(g1, min_length, max_length).unwrap();
195 let p2 = DynamicRealVector::new(g2, min_length, max_length).unwrap();
196 let mut rng = rand::thread_rng();
197
198 let (c1, c2) = cut_and_splice(&p1, &p2, &mut rng).unwrap();
199
200 prop_assert!(c1.dimension() >= min_length && c1.dimension() <= max_length);
201 prop_assert!(c2.dimension() >= min_length && c2.dimension() <= max_length);
202 for &v in c1.genes() {
203 prop_assert!((-5.0..=5.0).contains(&v));
204 }
205 for &v in c2.genes() {
206 prop_assert!((-5.0..=5.0).contains(&v));
207 }
208 }
209
210 #[test]
211 fn mutation_preserves_length_window_and_bounds(
212 genes in prop::collection::vec(-4.0..4.0f64, 2..10),
213 ) {
214 let min_length = 1;
217 let max_length = 12;
218 let mut genome = DynamicRealVector::new(genes, min_length, max_length).unwrap();
219 let bounds = MultiBounds::symmetric(5.0, max_length);
220 let op = DynamicGaussianMutation::new(0.75, 0.5, 0.5, 0.5);
221 let mut rng = rand::thread_rng();
222
223 for _ in 0..64 {
224 op.mutate(&mut genome, &bounds, &mut rng);
225 prop_assert!(
226 genome.dimension() >= min_length && genome.dimension() <= max_length
227 );
228 for &v in genome.genes() {
229 prop_assert!((-5.0..=5.0).contains(&v));
230 }
231 }
232 }
233 }
234
235 #[test]
236 fn cut_and_splice_incompatible_constraints_errors() {
237 let p1 = DynamicRealVector::new(vec![1.0, 2.0, 3.0], 2, 4).unwrap();
239 let p2 = DynamicRealVector::new(vec![9.0], 1, 1).unwrap();
240 let mut rng = rand::thread_rng();
241 assert!(cut_and_splice(&p1, &p2, &mut rng).is_err());
242 }
243
244 #[test]
245 fn cut_and_splice_is_usable_for_evolution() {
246 let p1 = DynamicRealVector::new(vec![1.0, 2.0, 3.0, 4.0], 1, 8).unwrap();
248 let p2 = DynamicRealVector::new(vec![-1.0, -2.0], 1, 8).unwrap();
249 let mut rng = rand::thread_rng();
250 let (c1, c2) = cut_and_splice(&p1, &p2, &mut rng).unwrap();
251 assert!(c1.dimension() >= 1 && c1.dimension() <= 8);
252 assert!(c2.dimension() >= 1 && c2.dimension() <= 8);
253 }
254
255 #[test]
256 fn mutation_can_grow_and_shrink() {
257 let bounds = MultiBounds::symmetric(5.0, 8);
260 let mut rng = rand::thread_rng();
261
262 let grow = DynamicGaussianMutation::new(0.1, 0.0, 1.0, 0.0);
263 let mut g = DynamicRealVector::new(vec![0.0, 0.0], 2, 8).unwrap();
264 for _ in 0..50 {
265 grow.mutate(&mut g, &bounds, &mut rng);
266 }
267 assert_eq!(g.dimension(), 8);
268
269 let shrink = DynamicGaussianMutation::new(0.1, 0.0, 0.0, 1.0);
270 let mut s = DynamicRealVector::new(vec![0.0, 0.0, 0.0, 0.0, 0.0, 0.0], 2, 8).unwrap();
271 for _ in 0..50 {
272 shrink.mutate(&mut s, &bounds, &mut rng);
273 }
274 assert_eq!(s.dimension(), 2);
275 }
276}