Skip to main content

wafrift_evolution/evolution/crossover/
strategies.rs

1use crate::evolution::Chromosome;
2use rand::Rng;
3
4/// Crossover strategy for breeding chromosomes.
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum CrossoverStrategy {
7    Uniform,
8    SinglePoint,
9    MultiPoint(usize),
10    OrderBased,
11}
12
13fn canonical_gene_names(parent_a: &Chromosome, parent_b: &Chromosome) -> Vec<String> {
14    let mut names = Vec::new();
15    for (name, _) in &parent_a.genes {
16        if !names.contains(name) {
17            names.push(name.clone());
18        }
19    }
20    for (name, _) in &parent_b.genes {
21        if !names.contains(name) {
22            names.push(name.clone());
23        }
24    }
25    names
26}
27
28fn gene_value_for(parent: &Chromosome, name: &str) -> Option<String> {
29    parent
30        .genes
31        .iter()
32        .find(|(gene_name, _)| gene_name == name)
33        .map(|(_, value)| value.clone())
34}
35
36fn value_from_parent(primary: &Chromosome, secondary: &Chromosome, name: &str) -> String {
37    gene_value_for(primary, name)
38        .or_else(|| gene_value_for(secondary, name))
39        .unwrap_or_else(|| String::from("None"))
40}
41
42fn build_child<F>(parent_a: &Chromosome, parent_b: &Chromosome, mut choose_a: F) -> Chromosome
43where
44    F: FnMut(usize, &str) -> bool,
45{
46    let child_genes = canonical_gene_names(parent_a, parent_b)
47        .into_iter()
48        .enumerate()
49        .map(|(index, name)| {
50            let value = if choose_a(index, &name) {
51                value_from_parent(parent_a, parent_b, &name)
52            } else {
53                value_from_parent(parent_b, parent_a, &name)
54            };
55            (name, value)
56        })
57        .collect();
58    Chromosome::new(child_genes)
59}
60
61/// Uniform crossover: each gene independently from either parent.
62#[must_use]
63pub fn uniform_crossover(
64    parent_a: &Chromosome,
65    parent_b: &Chromosome,
66    rng: &mut impl Rng,
67) -> Chromosome {
68    build_child(parent_a, parent_b, |_, _| rng.gen_bool(0.5))
69}
70
71/// Single-point crossover: split genes at one point and swap tails.
72#[must_use]
73pub fn single_point_crossover(
74    parent_a: &Chromosome,
75    parent_b: &Chromosome,
76    rng: &mut impl Rng,
77) -> Chromosome {
78    let gene_names = canonical_gene_names(parent_a, parent_b);
79    let max_len = gene_names.len();
80    if max_len == 0 {
81        return Chromosome::new(Vec::new());
82    }
83    let point = rng.gen_range(1..=max_len);
84    build_child(parent_a, parent_b, |index, _| index < point)
85}
86
87/// Multi-point crossover: split at multiple points.
88#[must_use]
89pub fn multi_point_crossover(
90    parent_a: &Chromosome,
91    parent_b: &Chromosome,
92    num_points: usize,
93    rng: &mut impl Rng,
94) -> Chromosome {
95    let gene_names = canonical_gene_names(parent_a, parent_b);
96    let max_len = gene_names.len();
97    if max_len == 0 {
98        return Chromosome::new(Vec::new());
99    }
100    // gen_range(1..max_len) panics on empty range when max_len == 1
101    // (single distinct gene shared between both parents). Single-gene
102    // parents have no meaningful split point — fall back to a clone of
103    // parent_a, same shape as max_len == 0.
104    if max_len == 1 {
105        return build_child(parent_a, parent_b, |_, _| true);
106    }
107    let mut points: Vec<usize> = (0..num_points.min(max_len))
108        .map(|_| rng.gen_range(1..max_len))
109        .collect();
110    points.sort_unstable();
111    points.dedup();
112    build_child(parent_a, parent_b, |index, _| {
113        points.iter().filter(|point| index >= **point).count() % 2 == 0
114    })
115}
116
117/// Order-based crossover: preserves relative ordering of genes.
118#[must_use]
119pub fn order_based_crossover(
120    parent_a: &Chromosome,
121    parent_b: &Chromosome,
122    rng: &mut impl Rng,
123) -> Chromosome {
124    build_child(parent_a, parent_b, |_, name| {
125        let prefer_a = parent_a
126            .genes
127            .iter()
128            .position(|(gene_name, _)| gene_name == name)
129            .unwrap_or(usize::MAX);
130        let prefer_b = parent_b
131            .genes
132            .iter()
133            .position(|(gene_name, _)| gene_name == name)
134            .unwrap_or(usize::MAX);
135        if prefer_a == prefer_b {
136            rng.gen_bool(0.5)
137        } else {
138            prefer_a < prefer_b
139        }
140    })
141}
142
143/// Perform crossover using a specified strategy.
144#[must_use]
145pub fn crossover_with_strategy(
146    parent_a: &Chromosome,
147    parent_b: &Chromosome,
148    strategy: CrossoverStrategy,
149    rng: &mut impl Rng,
150) -> Chromosome {
151    match strategy {
152        CrossoverStrategy::Uniform => uniform_crossover(parent_a, parent_b, rng),
153        CrossoverStrategy::SinglePoint => single_point_crossover(parent_a, parent_b, rng),
154        CrossoverStrategy::MultiPoint(points) => {
155            multi_point_crossover(parent_a, parent_b, points, rng)
156        }
157        CrossoverStrategy::OrderBased => order_based_crossover(parent_a, parent_b, rng),
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use rand::{SeedableRng, rngs::StdRng};
165
166    #[test]
167    fn empty_chromosome_crossover_returns_empty() {
168        let parent_a = Chromosome::new(Vec::new());
169        let parent_b = Chromosome::new(Vec::new());
170        let mut rng = StdRng::seed_from_u64(13);
171        let child = single_point_crossover(&parent_a, &parent_b, &mut rng);
172        assert!(child.genes.is_empty());
173    }
174
175    #[test]
176    fn single_gene_single_point_crossover_returns_valid_clone_shape() {
177        let parent_a = Chromosome::new(vec![("encoding".into(), "UrlEncode".into())]);
178        let parent_b = Chromosome::new(vec![("encoding".into(), "None".into())]);
179        let mut rng = StdRng::seed_from_u64(17);
180        let child = single_point_crossover(&parent_a, &parent_b, &mut rng);
181        assert_eq!(child.genes.len(), 1);
182        assert_eq!(child.genes[0].0, "encoding");
183    }
184
185    #[test]
186    fn crossover_with_identical_parents_matches_parent() {
187        let parent_a = Chromosome::new(vec![
188            ("encoding".into(), "UrlEncode".into()),
189            ("content_type".into(), "JsonNested".into()),
190        ]);
191        let parent_b = parent_a.clone();
192        let mut rng = StdRng::seed_from_u64(19);
193        let child = uniform_crossover(&parent_a, &parent_b, &mut rng);
194        assert_eq!(child.genes, parent_a.genes);
195        assert_eq!(child.genes, parent_b.genes);
196    }
197}