math_audio_optimisation/
apply_wls.rs1use ndarray::Array1;
2use rand::Rng;
3use std::f64::consts::PI;
4
5pub(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 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 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 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}