cambrian/
meta_adapt.rs

1use crate::meta::{CrossoverParams, MutationParams};
2use rand::rngs::StdRng;
3use rand_distr::{Cauchy, Distribution};
4
5const META_PARAMS_MUTATION_EXPONENT_SCALE: f64 = 0.1;
6const META_PARAMS_MUTATION_RESCALE_FLOOR: f64 = 1e-12;
7const META_PARAMS_MUTATION_RESCALE_CEIL: f64 = 1e12;
8
9fn rescale(value: f64, rng: &mut StdRng) -> f64 {
10    let exponent: f64 = Cauchy::new(0.0, META_PARAMS_MUTATION_EXPONENT_SCALE)
11        .unwrap()
12        .sample(rng);
13
14    let factor = 10.0f64.powf(exponent);
15
16    value
17        * factor
18            .max(META_PARAMS_MUTATION_RESCALE_FLOOR)
19            .min(META_PARAMS_MUTATION_RESCALE_CEIL)
20}
21
22fn rescale_prob(prob: f64, rng: &mut StdRng) -> f64 {
23    rescale(prob, rng).min(1.0)
24}
25
26pub fn create_exploratory(rng: &mut StdRng) -> (CrossoverParams, MutationParams) {
27    let crossover_params = CrossoverParams {
28        crossover_prob: 0.5,
29        selection_pressure: 0.5,
30    };
31
32    let mutation_params = MutationParams {
33        mutation_prob: 0.5,
34        mutation_scale: 1.0,
35    };
36
37    mutate(crossover_params, mutation_params, rng)
38}
39
40pub fn mutate(
41    crossover_params: CrossoverParams,
42    mutation_params: MutationParams,
43    rng: &mut StdRng,
44) -> (CrossoverParams, MutationParams) {
45    let crossover_params = CrossoverParams {
46        crossover_prob: rescale_prob(crossover_params.crossover_prob, rng),
47        selection_pressure: rescale_prob(crossover_params.selection_pressure, rng),
48    };
49
50    let mutation_params = MutationParams {
51        mutation_prob: rescale_prob(mutation_params.mutation_prob, rng),
52        mutation_scale: rescale(mutation_params.mutation_scale, rng),
53    };
54
55    (crossover_params, mutation_params)
56}
57
58#[cfg(test)]
59mod tests {
60    use super::*;
61    use float_cmp::assert_approx_eq;
62    use rand::SeedableRng;
63
64    fn assert_min_less_than(vals: &[f64], cmp: f64) {
65        assert!(*vals.iter().min_by(|a, b| a.total_cmp(b)).unwrap() < cmp);
66    }
67
68    fn assert_max_greater_than(vals: &[f64], cmp: f64) {
69        assert!(*vals.iter().max_by(|a, b| a.total_cmp(b)).unwrap() > cmp);
70    }
71
72    fn assert_max_equal_to(vals: &[f64], cmp: f64) {
73        assert_approx_eq!(
74            f64,
75            *vals.iter().max_by(|a, b| a.total_cmp(b)).unwrap(),
76            cmp
77        );
78    }
79
80    #[test]
81    fn mutate_covers_orders_of_magnitude() {
82        let mut rng = StdRng::seed_from_u64(0);
83
84        let crossover_params = CrossoverParams {
85            crossover_prob: 0.5,
86            selection_pressure: 0.5,
87        };
88
89        let mutation_params = MutationParams {
90            mutation_prob: 0.5,
91            mutation_scale: 0.5,
92        };
93
94        let mut crossover_probs = Vec::new();
95        let mut selection_pressures = Vec::new();
96        let mut mutation_probs = Vec::new();
97        let mut mutation_scales = Vec::new();
98
99        for _ in 0..100 {
100            let (crossover_params, mutation_params) =
101                mutate(crossover_params.clone(), mutation_params.clone(), &mut rng);
102
103            crossover_probs.push(crossover_params.crossover_prob);
104            selection_pressures.push(crossover_params.selection_pressure);
105            mutation_probs.push(mutation_params.mutation_prob);
106            mutation_scales.push(mutation_params.mutation_scale);
107        }
108
109        for vals in [crossover_probs, selection_pressures, mutation_probs] {
110            assert_min_less_than(&vals, 0.1);
111            assert_max_equal_to(&vals, 1.0);
112        }
113
114        assert_min_less_than(&mutation_scales, 0.1);
115        assert_max_greater_than(&mutation_scales, 10.0);
116    }
117
118    #[test]
119    fn exploratory_covers_orders_of_magnitude() {
120        let mut rng = StdRng::seed_from_u64(0);
121
122        let mut crossover_probs = Vec::new();
123        let mut selection_pressures = Vec::new();
124        let mut mutation_probs = Vec::new();
125        let mut mutation_scales = Vec::new();
126
127        for _ in 0..100 {
128            let (crossover_params, mutation_params) = create_exploratory(&mut rng);
129
130            crossover_probs.push(crossover_params.crossover_prob);
131            selection_pressures.push(crossover_params.selection_pressure);
132            mutation_probs.push(mutation_params.mutation_prob);
133            mutation_scales.push(mutation_params.mutation_scale);
134        }
135
136        for vals in [crossover_probs, selection_pressures, mutation_probs] {
137            assert_min_less_than(&vals, 0.1);
138            assert_max_equal_to(&vals, 1.0);
139        }
140
141        assert_min_less_than(&mutation_scales, 0.5);
142        assert_max_greater_than(&mutation_scales, 5.0);
143    }
144}