Skip to main content

math_audio_differential_evolution/
mutant_adaptive.rs

1use ndarray::{Array1, Array2};
2use rand::seq::SliceRandom;
3use rand::Rng;
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
49#[cfg(test)]
50mod tests {
51    use crate::{differential_evolution, AdaptiveConfig, DEConfigBuilder, Mutation, Strategy};
52    use math_audio_test_functions::quadratic;
53
54    #[test]
55    fn test_adaptive_basic() {
56        // Test basic adaptive DE functionality
57        let bounds = [(-5.0, 5.0), (-5.0, 5.0)];
58
59        // Configure adaptive DE with SAM approach
60        let adaptive_config = AdaptiveConfig {
61            adaptive_mutation: true,
62            wls_enabled: false, // Start with mutation only
63            w_max: 0.9,
64            w_min: 0.1,
65            ..AdaptiveConfig::default()
66        };
67
68        let config = DEConfigBuilder::new()
69            .seed(42)
70            .maxiter(100)
71            .popsize(30)
72            .strategy(Strategy::AdaptiveBin)
73            .mutation(Mutation::Adaptive { initial_f: 0.8 })
74            .adaptive(adaptive_config)
75            .build()
76            .expect("popsize must be >= 4");
77
78        let result = differential_evolution(&quadratic, &bounds, config)
79            .expect("optimization should succeed");
80
81        // Should converge to global minimum at (0, 0)
82        assert!(
83            result.fun < 1e-3,
84            "Adaptive DE should converge: f={}",
85            result.fun
86        );
87
88        // Check that solution is close to expected optimum
89        for &xi in result.x.iter() {
90            assert!(
91                xi.abs() < 0.5,
92                "Solution component should be close to 0: {}",
93                xi
94            );
95        }
96    }
97
98    #[test]
99    fn test_adaptive_with_wls() {
100        // Test adaptive DE with Wrapper Local Search
101        let bounds = [(-5.0, 5.0), (-5.0, 5.0)];
102
103        let adaptive_config = AdaptiveConfig {
104            adaptive_mutation: true,
105            wls_enabled: true,
106            wls_prob: 0.2, // Apply WLS to 20% of population
107            wls_scale: 0.1,
108            ..AdaptiveConfig::default()
109        };
110
111        let config = DEConfigBuilder::new()
112            .seed(123)
113            .maxiter(200)
114            .popsize(40)
115            .strategy(Strategy::AdaptiveExp)
116            .adaptive(adaptive_config)
117            .build()
118            .expect("popsize must be >= 4");
119
120        let result = differential_evolution(&quadratic, &bounds, config)
121            .expect("optimization should succeed");
122
123        // Should converge even better with WLS
124        assert!(
125            result.fun < 1e-4,
126            "Adaptive DE with WLS should converge well: f={}",
127            result.fun
128        );
129
130        for &xi in result.x.iter() {
131            assert!(
132                xi.abs() < 0.2,
133                "Solution should be very close to 0 with WLS: {}",
134                xi
135            );
136        }
137    }
138}