Skip to main content

math_audio_optimisation/
mutant_adaptive.rs

1use ndarray::{Array1, Array2};
2use rand::Rng;
3use rand::seq::SliceRandom;
4
5use crate::mutant_rand1::mutant_rand1;
6
7/// Adaptive mutation based on Self-Adaptive Mutation (SAM) from the paper
8/// Uses linearly decreasing weight w to select from top individuals
9pub(crate) fn mutant_adaptive<R: Rng + ?Sized>(
10    i: usize,
11    pop: &Array2<f64>,
12    sorted_indices: &[usize],
13    w: f64,
14    f: f64,
15    rng: &mut R,
16) -> Array1<f64> {
17    // Calculate w% of population size for adaptive selection
18    let w_size = ((w * pop.nrows() as f64) as usize)
19        .max(1)
20        .min(pop.nrows() - 1);
21
22    // Select gr_better from top w% individuals randomly
23    let top_indices = &sorted_indices[0..w_size];
24    let gr_better_idx = top_indices[rng.random_range(0..w_size)];
25    // Get two distinct random indices different from i and gr_better_idx
26    let mut available: Vec<usize> = (0..pop.nrows())
27        .filter(|&idx| idx != i && idx != gr_better_idx)
28        .collect();
29    available.shuffle(rng);
30
31    if available.len() < 2 {
32        // Fallback to standard rand1 if not enough individuals
33        return mutant_rand1(i, pop, f, rng);
34    }
35
36    let r1 = available[0];
37    let r2 = available[1];
38
39    // Adaptive mutation: x_i + F * (x_gr_better - x_i + x_r1 - x_r2)
40    // This is the SAM approach from equation (18) in the paper
41    pop.row(i).to_owned()
42        + &((pop.row(gr_better_idx).to_owned() - pop.row(i).to_owned() + pop.row(r1).to_owned()
43            - pop.row(r2).to_owned())
44            * f)
45}
46
47/// Tests for adaptive differential evolution strategies
48#[cfg(test)]
49mod tests {
50    use crate::{AdaptiveConfig, DEConfigBuilder, Mutation, Strategy, differential_evolution};
51    use math_audio_test_functions::quadratic;
52
53    #[test]
54    fn test_adaptive_basic() {
55        // Test basic adaptive DE functionality
56        let bounds = [(-5.0, 5.0), (-5.0, 5.0)];
57
58        // Configure adaptive DE with SAM approach
59        let adaptive_config = AdaptiveConfig {
60            adaptive_mutation: true,
61            wls_enabled: false, // Start with mutation only
62            w_max: 0.9,
63            w_min: 0.1,
64            ..AdaptiveConfig::default()
65        };
66
67        let config = DEConfigBuilder::new()
68            .seed(42)
69            .maxiter(100)
70            .popsize(30)
71            .strategy(Strategy::AdaptiveBin)
72            .mutation(Mutation::Adaptive { initial_f: 0.8 })
73            .adaptive(adaptive_config)
74            .build()
75            .expect("popsize must be >= 4");
76
77        let result = differential_evolution(&quadratic, &bounds, config)
78            .expect("optimization should succeed");
79
80        // Should converge to global minimum at (0, 0)
81        assert!(
82            result.fun < 1e-3,
83            "Adaptive DE should converge: f={}",
84            result.fun
85        );
86
87        // Check that solution is close to expected optimum
88        for &xi in result.x.iter() {
89            assert!(
90                xi.abs() < 0.5,
91                "Solution component should be close to 0: {}",
92                xi
93            );
94        }
95    }
96
97    #[test]
98    fn test_adaptive_with_wls() {
99        // Test adaptive DE with Wrapper Local Search
100        let bounds = [(-5.0, 5.0), (-5.0, 5.0)];
101
102        let adaptive_config = AdaptiveConfig {
103            adaptive_mutation: true,
104            wls_enabled: true,
105            wls_prob: 0.2, // Apply WLS to 20% of population
106            wls_scale: 0.1,
107            ..AdaptiveConfig::default()
108        };
109
110        let config = DEConfigBuilder::new()
111            .seed(123)
112            .maxiter(200)
113            .popsize(40)
114            .strategy(Strategy::AdaptiveExp)
115            .adaptive(adaptive_config)
116            .build()
117            .expect("popsize must be >= 4");
118
119        let result = differential_evolution(&quadratic, &bounds, config)
120            .expect("optimization should succeed");
121
122        // Should converge even better with WLS
123        assert!(
124            result.fun < 1e-4,
125            "Adaptive DE with WLS should converge well: f={}",
126            result.fun
127        );
128
129        for &xi in result.x.iter() {
130            assert!(
131                xi.abs() < 0.2,
132                "Solution should be very close to 0 with WLS: {}",
133                xi
134            );
135        }
136    }
137}