math_audio_optimisation/
impl_helpers.rs1use crate::{DEReport, DifferentialEvolution};
2use ndarray::{Array1, Array2, Zip};
3use oxiblas_ndarray::blas::matvec;
4
5impl<'a, F> DifferentialEvolution<'a, F>
8where
9 F: Fn(&Array1<f64>) -> f64 + Sync,
10{
11 pub(crate) fn energy(&self, x: &Array1<f64>) -> f64 {
12 let base = (self.func)(x);
13 base + self.penalty(x)
14 }
15
16 pub(crate) fn penalty(&self, x: &Array1<f64>) -> f64 {
17 let mut p = 0.0;
18 for (f, w) in &self.config.penalty_ineq {
20 let v = f(x);
21 let viol = v.max(0.0);
22 p += w * viol * viol;
23 }
24 for (h, w) in &self.config.penalty_eq {
26 let v = h(x);
27 p += w * v * v;
28 }
29 if let Some(lp) = &self.config.linear_penalty {
31 let ax = matvec(&lp.a, &x.to_owned());
32 Zip::from(&ax)
33 .and(&lp.lb)
34 .and(&lp.ub)
35 .for_each(|&v, &lo, &hi| {
36 if v < lo {
37 let d = lo - v;
38 p += lp.weight * d * d;
39 } else if v > hi {
40 let d = v - hi;
41 p += lp.weight * d * d;
42 }
43 });
44 }
45 p
46 }
47
48 #[allow(clippy::too_many_arguments)]
49 pub(crate) fn finish_report(
50 &self,
51 pop: Array2<f64>,
52 energies: Array1<f64>,
53 x: Array1<f64>,
54 fun: f64,
55 success: bool,
56 message: String,
57 nit: usize,
58 nfev: usize,
59 ) -> DEReport {
60 DEReport {
61 x,
62 fun,
63 success,
64 message,
65 nit,
66 nfev,
67 population: pop,
68 population_energies: energies,
69 }
70 }
71
72 pub(crate) fn polish(&self, x0: &Array1<f64>) -> (Array1<f64>, f64, usize) {
73 let polish_cfg = match &self.config.polish {
74 Some(cfg) if cfg.enabled => cfg,
75 _ => {
76 let f = self.energy(x0);
77 return (x0.clone(), f, 1);
78 }
79 };
80
81 let n = x0.len();
82 let mut x = x0.clone();
83 let mut best_f = self.energy(&x);
84 let mut nfev = 1;
85
86 let initial_step = 0.1;
87 let min_step = 1e-8;
88 let mut step = initial_step;
89
90 let max_eval = polish_cfg.maxeval.min(200 * n);
91
92 while nfev < max_eval && step > min_step {
93 let mut improved = false;
94
95 for i in 0..n {
96 if nfev >= max_eval {
97 break;
98 }
99
100 let original = x[i];
101 let bounds_span = self.upper[i] - self.lower[i];
102 let dim_step = step * bounds_span.max(1.0);
103
104 for delta in [dim_step, -dim_step] {
105 if nfev >= max_eval {
106 break;
107 }
108 x[i] = (original + delta).clamp(self.lower[i], self.upper[i]);
109 let f = self.energy(&x);
110 nfev += 1;
111
112 if f < best_f {
113 best_f = f;
114 improved = true;
115 break;
116 }
117 x[i] = original;
118 }
119 }
120
121 if !improved {
122 step *= 0.5;
123 }
124 }
125
126 (x, best_f, nfev)
127 }
128}