Skip to main content

wafrift_evolution/evolution/crossover/
mutation.rs

1use crate::evolution::{Chromosome, GenePool};
2use crate::lineage::MutationOp;
3use rand::Rng;
4use wafrift_types::pick::pick_from_rng;
5
6/// Mutate a chromosome and return a log of applied mutations.
7pub fn mutate_with_log(
8    chromosome: &mut Chromosome,
9    gene_pool: &GenePool,
10    mutation_rate: f64,
11    rng: &mut impl Rng,
12) -> Vec<MutationOp> {
13    let mut log = Vec::new();
14    for gene in &mut chromosome.genes {
15        if rng.gen_bool(mutation_rate)
16            && let Some(value) = gene_pool.random_value(&gene.0, rng)
17            && value != gene.1
18        {
19            log.push(MutationOp {
20                gene_name: gene.0.clone(),
21                from: std::mem::replace(&mut gene.1, value.clone()),
22                to: value,
23                operator: "value_mutation".into(),
24            });
25        }
26    }
27    log
28}
29
30/// Basic mutation: randomly changes gene values.
31pub fn mutate(
32    chromosome: &mut Chromosome,
33    gene_pool: &GenePool,
34    mutation_rate: f64,
35    rng: &mut impl Rng,
36) {
37    let _ = mutate_with_log(chromosome, gene_pool, mutation_rate, rng);
38}
39
40/// Structural mutation: add new genes from the gene pool.
41pub fn structural_add_mutation(
42    chromosome: &mut Chromosome,
43    gene_pool: &GenePool,
44    add_rate: f64,
45    rng: &mut impl Rng,
46) {
47    let pool_names: Vec<&str> = gene_pool.gene_names();
48    let missing_names: Vec<&str> = pool_names
49        .into_iter()
50        .filter(|name| !chromosome.has_gene(name))
51        .collect();
52
53    if !missing_names.is_empty() && rng.gen_bool(add_rate) {
54        let name = pick_from_rng(&missing_names, missing_names[0], rng);
55        if let Some(value) = gene_pool.random_value(name, rng) {
56            chromosome.genes.push((name.to_string(), value));
57        }
58    }
59}
60
61/// Essential gene names that should not be removed.
62pub const ESSENTIAL_GENES: &[&str] = &["encoding"];
63
64/// Structural mutation: remove genes from the chromosome.
65pub fn structural_remove_mutation(
66    chromosome: &mut Chromosome,
67    remove_rate: f64,
68    min_genes: usize,
69    rng: &mut impl Rng,
70) {
71    if chromosome.genes.len() > min_genes && rng.gen_bool(remove_rate) {
72        let removable: Vec<usize> = chromosome
73            .genes
74            .iter()
75            .enumerate()
76            .filter(|(_, (name, _))| !ESSENTIAL_GENES.contains(&name.as_str()))
77            .map(|(i, _)| i)
78            .collect();
79
80        if !removable.is_empty() {
81            let idx = pick_from_rng(&removable, removable[0], rng);
82            chromosome.genes.remove(idx);
83        }
84    }
85}
86
87/// Swap mutation: exchanges values between two genes.
88pub fn swap_mutation(chromosome: &mut Chromosome, swap_rate: f64, rng: &mut impl Rng) {
89    if chromosome.genes.len() < 2 || !rng.gen_bool(swap_rate) {
90        return;
91    }
92    let idx_a = rng.gen_range(0..chromosome.genes.len());
93    let idx_b = rng.gen_range(0..chromosome.genes.len());
94    if idx_a != idx_b {
95        chromosome.genes.swap(idx_a, idx_b);
96    }
97}
98
99/// Scramble mutation: randomly shuffles a subset of genes.
100pub fn scramble_mutation(chromosome: &mut Chromosome, scramble_rate: f64, rng: &mut impl Rng) {
101    if chromosome.genes.len() < 3 || !rng.gen_bool(scramble_rate) {
102        return;
103    }
104    let start = rng.gen_range(0..chromosome.genes.len() - 1);
105    let end = rng.gen_range(start + 1..chromosome.genes.len());
106    for i in (start + 1..end).rev() {
107        let j = rng.gen_range(start..=i);
108        chromosome.genes.swap(i, j);
109    }
110}
111
112/// Complete mutation operator applying all mutation types.
113pub fn comprehensive_mutate(
114    chromosome: &mut Chromosome,
115    gene_pool: &GenePool,
116    value_mutation_rate: f64,
117    structural_add_rate: f64,
118    structural_remove_rate: f64,
119    min_genes: usize,
120    rng: &mut impl Rng,
121) -> Vec<MutationOp> {
122    let log = mutate_with_log(chromosome, gene_pool, value_mutation_rate, rng);
123    structural_add_mutation(chromosome, gene_pool, structural_add_rate, rng);
124    structural_remove_mutation(chromosome, structural_remove_rate, min_genes, rng);
125    swap_mutation(chromosome, 0.1, rng);
126    scramble_mutation(chromosome, 0.05, rng);
127    log
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use rand::{SeedableRng, rngs::StdRng};
134
135    #[test]
136    fn empty_chromosome_mutation_is_noop() {
137        let mut chromosome = Chromosome::new(Vec::new());
138        let gene_pool = GenePool::default_wafrift();
139        let mut rng = StdRng::seed_from_u64(23);
140        let log = mutate_with_log(&mut chromosome, &gene_pool, 1.0, &mut rng);
141        assert!(chromosome.genes.is_empty());
142        assert!(log.is_empty());
143    }
144}