Skip to main content

math_audio_optimisation/
apply_wls.rs

1use ndarray::Array1;
2use rand::Rng;
3use std::f64::consts::PI;
4
5/// Wrapper Local Search (WLS) strategy for local refinement
6/// Uses Cauchy distribution to perturb selected dimensions
7pub(crate) fn apply_wls<R: Rng + ?Sized>(
8    x: &Array1<f64>,
9    lower: &Array1<f64>,
10    upper: &Array1<f64>,
11    scale: f64,
12    rng: &mut R,
13) -> Array1<f64> {
14    let mut result = x.clone();
15    let n_dims = x.len();
16
17    // Generate random wrapper mask - selects which dimensions to perturb
18    let n_selected = rng.random_range(1..=n_dims.max(1));
19    let mut dimensions: Vec<usize> = (0..n_dims).collect();
20    use rand::seq::SliceRandom;
21    dimensions.shuffle(rng);
22    let selected_dims = &dimensions[0..n_selected];
23
24    // Apply Cauchy perturbations to selected dimensions. Heavy tails give WLS
25    // occasional longer local jumps while clipping keeps candidates feasible.
26    for &dim in selected_dims {
27        let u = rng.random::<f64>().clamp(f64::EPSILON, 1.0 - f64::EPSILON);
28        let perturbation = (PI * (u - 0.5)).tan() * scale;
29        let new_val = x[dim] + perturbation;
30        // Clip to bounds
31        result[dim] = new_val.max(lower[dim]).min(upper[dim]);
32    }
33
34    result
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40    use rand::SeedableRng;
41    use rand::rngs::StdRng;
42
43    #[test]
44    fn wls_preserves_shape_and_bounds() {
45        let x = Array1::from(vec![0.0, 0.5, -0.5]);
46        let lower = Array1::from(vec![-1.0, -1.0, -1.0]);
47        let upper = Array1::from(vec![1.0, 1.0, 1.0]);
48        let mut rng = StdRng::seed_from_u64(7);
49
50        let y = apply_wls(&x, &lower, &upper, 10.0, &mut rng);
51
52        assert_eq!(y.len(), x.len());
53        for i in 0..y.len() {
54            assert!(
55                y[i] >= lower[i] && y[i] <= upper[i],
56                "dimension {i} escaped bounds: {} not in [{}, {}]",
57                y[i],
58                lower[i],
59                upper[i]
60            );
61        }
62    }
63
64    #[test]
65    fn zero_scale_wls_is_identity() {
66        let x = Array1::from(vec![0.0, 0.5, -0.5]);
67        let lower = Array1::from(vec![-1.0, -1.0, -1.0]);
68        let upper = Array1::from(vec![1.0, 1.0, 1.0]);
69        let mut rng = StdRng::seed_from_u64(11);
70
71        let y = apply_wls(&x, &lower, &upper, 0.0, &mut rng);
72
73        assert_eq!(y, x);
74    }
75}