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