1use rand::{Rng, RngExt};
19
20use crate::function_set::{FunctionSet, Symbol};
21
22use super::alphabet::Alphabet;
23
24const MAX_TRANSPOSON_LEN: usize = 3;
26
27pub fn point_mutation<F: FunctionSet>(
34 chromosome: &mut [Symbol],
35 head_len: usize,
36 alphabet: &Alphabet<F>,
37 rate: f32,
38 rng: &mut dyn Rng,
39) {
40 for (i, locus) in chromosome.iter_mut().enumerate() {
41 if rng.random::<f32>() < rate {
45 *locus = if i < head_len {
46 alphabet.sample_head_symbol(rng)
47 } else {
48 alphabet.sample_tail_symbol(rng)
49 };
50 }
51 }
52}
53
54pub fn is_transposition(chromosome: &mut [Symbol], head_len: usize, rng: &mut dyn Rng) {
58 let genome_len = chromosome.len();
59 if head_len < 2 || genome_len == 0 {
60 return; }
62 let max_len = MAX_TRANSPOSON_LEN.min(genome_len);
63 let len = rng.random_range(1..=max_len);
64 let source_start = rng.random_range(0..=(genome_len - len));
65 let insert = rng.random_range(1..head_len); let seq: Vec<Symbol> = chromosome[source_start..source_start + len].to_vec();
68 let mut new_head: Vec<Symbol> = Vec::with_capacity(head_len + len);
69 new_head.extend_from_slice(&chromosome[0..insert]);
70 new_head.extend_from_slice(&seq);
71 new_head.extend_from_slice(&chromosome[insert..head_len]);
72 new_head.truncate(head_len);
73 chromosome[0..head_len].copy_from_slice(&new_head);
74}
75
76pub fn ris_transposition<F: FunctionSet>(
81 chromosome: &mut [Symbol],
82 head_len: usize,
83 alphabet: &Alphabet<F>,
84 rng: &mut dyn Rng,
85) {
86 if head_len == 0 {
87 return;
88 }
89 let offset = rng.random_range(0..head_len);
91 let func_pos = (0..head_len)
92 .map(|k| (offset + k) % head_len)
93 .find(|&i| alphabet.arity(chromosome[i]) >= 1);
94 let Some(func_pos) = func_pos else {
95 return; };
97
98 let max_len = MAX_TRANSPOSON_LEN.min(head_len - func_pos);
99 let len = rng.random_range(1..=max_len);
100 let seq: Vec<Symbol> = chromosome[func_pos..func_pos + len].to_vec();
101
102 let mut new_head: Vec<Symbol> = Vec::with_capacity(head_len + len);
103 new_head.extend_from_slice(&seq);
104 new_head.extend_from_slice(&chromosome[0..head_len]);
105 new_head.truncate(head_len);
106 chromosome[0..head_len].copy_from_slice(&new_head);
107}
108
109pub fn one_point_crossover(a: &mut [Symbol], b: &mut [Symbol], rng: &mut dyn Rng) {
117 let n = a.len();
118 assert_eq!(n, b.len(), "crossover parents must share genome length");
119 if n < 2 {
120 return;
121 }
122 let cut = rng.random_range(1..n);
123 a[cut..].swap_with_slice(&mut b[cut..]);
124}
125
126pub fn two_point_crossover(a: &mut [Symbol], b: &mut [Symbol], rng: &mut dyn Rng) {
134 let n = a.len();
135 assert_eq!(n, b.len(), "crossover parents must share genome length");
136 if n < 2 {
137 return;
138 }
139 let mut c1 = rng.random_range(0..n);
140 let mut c2 = rng.random_range(1..=n);
141 if c1 > c2 {
142 std::mem::swap(&mut c1, &mut c2);
143 }
144 if c1 == c2 {
145 return;
146 }
147 a[c1..c2].swap_with_slice(&mut b[c1..c2]);
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::algorithms::gep::decode::GenotypePhenotypeMap;
154 use crate::algorithms::gep::{GepConfig, GepDecoder};
155 use crate::function_set::ArithmeticFunctionSet;
156 use crate::rng::{SeedPurpose, seed_stream};
157 use proptest::prelude::{ProptestConfig, any, prop_assert, proptest};
160
161 type Fs = ArithmeticFunctionSet;
162
163 fn alphabet() -> Alphabet<Fs> {
164 Alphabet::new(ArithmeticFunctionSet, 1, vec![])
165 }
166
167 fn sample_valid(alphabet: &Alphabet<Fs>, cfg: &GepConfig, rng: &mut dyn Rng) -> Vec<Symbol> {
169 let mut g = Vec::with_capacity(cfg.genome_len());
170 for _ in 0..cfg.head_len {
171 g.push(alphabet.sample_head_symbol(rng));
172 }
173 for _ in 0..cfg.tail_len {
174 g.push(alphabet.sample_tail_symbol(rng));
175 }
176 g
177 }
178
179 fn tail_all_terminals(g: &[Symbol], head_len: usize, a: &Alphabet<Fs>) -> bool {
181 g[head_len..].iter().all(|&s| a.arity(s) == 0)
182 }
183
184 fn decodes_complete(g: &[Symbol], a: &Alphabet<Fs>) -> bool {
187 let tree = GepDecoder.decode(a, g);
188 let mut needed: i64 = 1;
189 for &s in tree.nodes() {
190 needed += i64::try_from(a.arity(s)).unwrap() - 1;
191 }
192 needed == 0 && tree.node_count() >= 1
193 }
194
195 #[test]
197 fn point_mutation_preserves_tail_invariant() {
198 let a = alphabet();
199 let cfg = GepConfig::new(7, 2, 1, 100).unwrap();
200 let mut rng = seed_stream(1, 0, SeedPurpose::Mutation);
201 for _ in 0..1000 {
202 let mut g = sample_valid(&a, &cfg, &mut rng);
203 point_mutation(&mut g, cfg.head_len, &a, 0.5, &mut rng);
205 assert!(
206 tail_all_terminals(&g, cfg.head_len, &a),
207 "tail invariant violated: {g:?}"
208 );
209 assert!(decodes_complete(&g, &a));
210 }
211 }
212
213 #[test]
216 fn test_point_mutation_rate_bounds_are_none_and_all() {
217 let a = alphabet();
218 let cfg = GepConfig::new(7, 2, 1, 100).unwrap();
219 let mut rng = seed_stream(9, 0, SeedPurpose::Mutation);
220
221 let original = sample_valid(&a, &cfg, &mut rng);
223 let mut g = original.clone();
224 point_mutation(&mut g, cfg.head_len, &a, 0.0, &mut rng);
225 assert_eq!(g, original, "rate 0.0 must leave the chromosome unchanged");
226
227 let mut g = sample_valid(&a, &cfg, &mut rng);
229 point_mutation(&mut g, cfg.head_len, &a, 1.0, &mut rng);
230 assert!(
231 tail_all_terminals(&g, cfg.head_len, &a),
232 "rate 1.0 must preserve the tail invariant: {g:?}"
233 );
234 assert!(
235 decodes_complete(&g, &a),
236 "rate 1.0 offspring must still decode completely"
237 );
238 }
239
240 #[test]
242 fn all_operators_yield_decodable_offspring() {
243 let a = alphabet();
244 let cfg = GepConfig::new(7, 2, 1, 100).unwrap();
245 let mut rng = seed_stream(2, 0, SeedPurpose::Crossover);
246
247 for _ in 0..500 {
248 let mut g = sample_valid(&a, &cfg, &mut rng);
250 is_transposition(&mut g, cfg.head_len, &mut rng);
251 assert!(tail_all_terminals(&g, cfg.head_len, &a));
252 assert!(decodes_complete(&g, &a));
253
254 let mut g = sample_valid(&a, &cfg, &mut rng);
256 ris_transposition(&mut g, cfg.head_len, &a, &mut rng);
257 assert!(tail_all_terminals(&g, cfg.head_len, &a));
258 assert!(decodes_complete(&g, &a));
259
260 let mut p1 = sample_valid(&a, &cfg, &mut rng);
262 let mut p2 = sample_valid(&a, &cfg, &mut rng);
263 one_point_crossover(&mut p1, &mut p2, &mut rng);
264 assert!(tail_all_terminals(&p1, cfg.head_len, &a));
265 assert!(tail_all_terminals(&p2, cfg.head_len, &a));
266 assert!(decodes_complete(&p1, &a));
267 assert!(decodes_complete(&p2, &a));
268
269 let mut p1 = sample_valid(&a, &cfg, &mut rng);
271 let mut p2 = sample_valid(&a, &cfg, &mut rng);
272 two_point_crossover(&mut p1, &mut p2, &mut rng);
273 assert!(tail_all_terminals(&p1, cfg.head_len, &a));
274 assert!(tail_all_terminals(&p2, cfg.head_len, &a));
275 assert!(decodes_complete(&p1, &a));
276 assert!(decodes_complete(&p2, &a));
277 }
278 }
279
280 #[test]
282 fn ris_roots_a_function() {
283 let a = alphabet();
284 let cfg = GepConfig::new(7, 2, 1, 10).unwrap();
285 let mut rng = seed_stream(3, 0, SeedPurpose::Crossover);
286 let mut rooted = 0;
287 for _ in 0..200 {
288 let mut g = sample_valid(&a, &cfg, &mut rng);
289 g[0] = Symbol::from_raw(0);
291 ris_transposition(&mut g, cfg.head_len, &a, &mut rng);
292 if a.arity(g[0]) >= 1 {
293 rooted += 1;
294 }
295 }
296 assert_eq!(rooted, 200, "RIS should always root a function");
297 }
298
299 #[test]
301 #[allow(clippy::similar_names)]
302 fn transposition_does_not_touch_tail() {
303 let a = alphabet();
304 let cfg = GepConfig::new(7, 2, 1, 10).unwrap();
305 let mut rng = seed_stream(4, 0, SeedPurpose::Crossover);
306 let g = sample_valid(&a, &cfg, &mut rng);
307 let tail_before = g[cfg.head_len..].to_vec();
308 let mut g_is = g.clone();
309 is_transposition(&mut g_is, cfg.head_len, &mut rng);
310 assert_eq!(&g_is[cfg.head_len..], &tail_before[..]);
311 let mut g_ris = g.clone();
312 ris_transposition(&mut g_ris, cfg.head_len, &a, &mut rng);
313 assert_eq!(&g_ris[cfg.head_len..], &tail_before[..]);
314 }
315
316 #[test]
319 fn point_mutation_nan_rate_is_no_op() {
320 let a = alphabet();
321 let cfg = GepConfig::new(7, 2, 1, 10).unwrap();
322 let mut rng = seed_stream(21, 0, SeedPurpose::Mutation);
323 let original = sample_valid(&a, &cfg, &mut rng);
324 let mut g = original.clone();
325 point_mutation(&mut g, cfg.head_len, &a, f32::NAN, &mut rng);
326 assert_eq!(g, original, "NaN rate must leave the chromosome unchanged");
327 }
328
329 #[test]
332 fn point_mutation_out_of_range_rate_resamples_all_but_stays_valid() {
333 let a = alphabet();
334 let cfg = GepConfig::new(7, 2, 1, 10).unwrap();
335 let mut rng = seed_stream(22, 0, SeedPurpose::Mutation);
336 let mut g = sample_valid(&a, &cfg, &mut rng);
337 point_mutation(&mut g, cfg.head_len, &a, 2.0, &mut rng);
338 assert!(tail_all_terminals(&g, cfg.head_len, &a));
339 assert!(decodes_complete(&g, &a));
340 }
341
342 #[test]
344 fn operators_do_not_panic_on_empty_slices() {
345 let a = alphabet();
346 let mut rng = seed_stream(23, 0, SeedPurpose::Crossover);
347
348 let mut empty: Vec<Symbol> = Vec::new();
349 point_mutation(&mut empty, 0, &a, 1.0, &mut rng);
350 is_transposition(&mut empty, 0, &mut rng);
351 ris_transposition(&mut empty, 0, &a, &mut rng);
352 assert!(empty.is_empty());
353
354 let mut lhs: Vec<Symbol> = Vec::new();
355 let mut rhs: Vec<Symbol> = Vec::new();
356 one_point_crossover(&mut lhs, &mut rhs, &mut rng);
357 two_point_crossover(&mut lhs, &mut rhs, &mut rng);
358 assert!(lhs.is_empty() && rhs.is_empty());
359 }
360
361 #[test]
365 fn transposition_with_head_len_one_is_safe() {
366 let a = alphabet();
367 let cfg = GepConfig::new(1, 2, 1, 10).unwrap();
368 assert_eq!(cfg.head_len, 1);
369 let mut rng = seed_stream(24, 0, SeedPurpose::Transposition);
370 for _ in 0..200 {
371 let mut g = sample_valid(&a, &cfg, &mut rng);
372 g[0] = Symbol::from_raw(0);
374 let len_before = g.len();
375 is_transposition(&mut g, cfg.head_len, &mut rng);
376 ris_transposition(&mut g, cfg.head_len, &a, &mut rng);
377 assert_eq!(g.len(), len_before);
378 assert!(tail_all_terminals(&g, cfg.head_len, &a));
379 assert!(decodes_complete(&g, &a));
380 }
381 }
382
383 #[test]
385 fn crossover_with_single_locus_is_no_op() {
386 let mut rng = seed_stream(25, 0, SeedPurpose::Crossover);
387 let a0: Vec<Symbol> = vec![Symbol::from_raw(8)];
388 let b0: Vec<Symbol> = vec![Symbol::from_raw(0)];
389
390 let mut a1 = a0.clone();
391 let mut b1 = b0.clone();
392 one_point_crossover(&mut a1, &mut b1, &mut rng);
393 two_point_crossover(&mut a1, &mut b1, &mut rng);
394 assert_eq!(a1, a0);
395 assert_eq!(b1, b0);
396 }
397
398 #[test]
401 #[should_panic(expected = "crossover parents must share genome length")]
402 fn one_point_crossover_panics_on_mismatched_lengths() {
403 let mut rng = seed_stream(26, 0, SeedPurpose::Crossover);
404 let mut a = vec![Symbol::from_raw(8); 4];
405 let mut b = vec![Symbol::from_raw(8); 5];
406 one_point_crossover(&mut a, &mut b, &mut rng);
407 }
408
409 #[test]
411 #[should_panic(expected = "crossover parents must share genome length")]
412 fn two_point_crossover_panics_on_mismatched_lengths() {
413 let mut rng = seed_stream(27, 0, SeedPurpose::Crossover);
414 let mut a = vec![Symbol::from_raw(8); 4];
415 let mut b = vec![Symbol::from_raw(8); 5];
416 two_point_crossover(&mut a, &mut b, &mut rng);
417 }
418
419 #[test]
422 fn ris_no_op_when_head_has_no_functions() {
423 let a = alphabet();
424 let cfg = GepConfig::new(7, 2, 1, 10).unwrap();
425 let mut rng = seed_stream(28, 0, SeedPurpose::Transposition);
426 for _ in 0..200 {
427 let mut g = sample_valid(&a, &cfg, &mut rng);
428 for locus in &mut g[..cfg.head_len] {
430 *locus = Symbol::from_raw(8);
431 }
432 let before = g.clone();
433 ris_transposition(&mut g, cfg.head_len, &a, &mut rng);
434 assert_eq!(g, before, "RIS must not alter an all-terminal head");
435 }
436 }
437
438 proptest! {
439 #![proptest_config(ProptestConfig { cases: 128, ..ProptestConfig::default() })]
443
444 #[test]
452 fn prop_point_mutation_preserves_invariants(
453 head_len in 1usize..=16,
454 max_arity in 2usize..=3,
461 n_vars in 1usize..=8,
462 rate in 0.0f32..=1.0,
463 seed in any::<u64>(),
464 ) {
465 let cfg = GepConfig::new(head_len, max_arity, n_vars, 100).unwrap();
466 let a = Alphabet::new(ArithmeticFunctionSet, n_vars, vec![]);
467 let mut rng = seed_stream(seed, 0, SeedPurpose::Mutation);
468
469 let mut g = sample_valid(&a, &cfg, &mut rng);
470 point_mutation(&mut g, cfg.head_len, &a, rate, &mut rng);
471 prop_assert!(
472 tail_all_terminals(&g, cfg.head_len, &a),
473 "tail invariant violated: {g:?}"
474 );
475 prop_assert!(decodes_complete(&g, &a), "offspring failed to decode: {g:?}");
476 }
477
478 #[test]
484 fn prop_operators_yield_decodable_offspring(
485 head_len in 1usize..=16,
486 max_arity in 2usize..=3,
489 n_vars in 1usize..=8,
490 seed in any::<u64>(),
491 ) {
492 let cfg = GepConfig::new(head_len, max_arity, n_vars, 100).unwrap();
493 let a = Alphabet::new(ArithmeticFunctionSet, n_vars, vec![]);
494
495 let mut rng = seed_stream(seed, 0, SeedPurpose::Transposition);
497
498 let mut g = sample_valid(&a, &cfg, &mut rng);
499 is_transposition(&mut g, cfg.head_len, &mut rng);
500 prop_assert!(tail_all_terminals(&g, cfg.head_len, &a));
501 prop_assert!(decodes_complete(&g, &a));
502
503 let mut g = sample_valid(&a, &cfg, &mut rng);
504 ris_transposition(&mut g, cfg.head_len, &a, &mut rng);
505 prop_assert!(tail_all_terminals(&g, cfg.head_len, &a));
506 prop_assert!(decodes_complete(&g, &a));
507
508 let mut rng = seed_stream(seed, 0, SeedPurpose::Crossover);
511
512 let mut p1 = sample_valid(&a, &cfg, &mut rng);
513 let mut p2 = sample_valid(&a, &cfg, &mut rng);
514 one_point_crossover(&mut p1, &mut p2, &mut rng);
515 prop_assert!(tail_all_terminals(&p1, cfg.head_len, &a));
516 prop_assert!(tail_all_terminals(&p2, cfg.head_len, &a));
517 prop_assert!(decodes_complete(&p1, &a));
518 prop_assert!(decodes_complete(&p2, &a));
519
520 let mut p1 = sample_valid(&a, &cfg, &mut rng);
521 let mut p2 = sample_valid(&a, &cfg, &mut rng);
522 two_point_crossover(&mut p1, &mut p2, &mut rng);
523 prop_assert!(tail_all_terminals(&p1, cfg.head_len, &a));
524 prop_assert!(tail_all_terminals(&p2, cfg.head_len, &a));
525 prop_assert!(decodes_complete(&p1, &a));
526 prop_assert!(decodes_complete(&p2, &a));
527 }
528 }
529}