1pub fn minimize(lo: f64, hi: f64, mut f: impl FnMut(f64) -> f64) -> f64 {
19 const CELLS: usize = 64;
20 let h = (hi - lo) / CELLS as f64;
21 let x = |j: usize| lo + h * j as f64;
22 let mut best = 1;
23 let mut f_best = f(x(1));
24 for j in 2..CELLS {
25 let v = f(x(j));
26 if v.total_cmp(&f_best).is_lt() {
27 (best, f_best) = (j, v);
28 }
29 }
30 golden_section(x(best - 1), x(best + 1), f)
31}
32
33pub fn golden_section(a: f64, b: f64, mut f: impl FnMut(f64) -> f64) -> f64 {
43 let (mut a, mut b) = (a, b);
44 let g = 0.5 * (5f64.sqrt() - 1.0);
45 let (mut c, mut d) = (b - g * (b - a), a + g * (b - a));
46 let (mut fc, mut fd) = (f(c), f(d));
47 for _ in 0..200 {
48 if b - a <= 1e-15 * b.abs() {
49 break;
50 }
51 if fc < fd {
52 (b, d, fd) = (d, c, fc);
53 c = b - g * (b - a);
54 fc = f(c);
55 } else {
56 (a, c, fc) = (c, d, fd);
57 d = a + g * (b - a);
58 fd = f(d);
59 }
60 }
61 0.5 * (a + b)
62}
63
64pub fn brent(a: f64, b: f64, tol: f64, mut f: impl FnMut(f64) -> f64) -> (f64, f64, usize) {
84 const GOLD: f64 = 0.381_966_011_250_105_1; let tol = tol.max(f64::EPSILON.sqrt());
87 let (mut a, mut b) = (a.min(b), a.max(b));
88 let mut x = a + GOLD * (b - a);
89 let (mut w, mut v) = (x, x);
90 let mut fx = f(x);
91 let (mut fw, mut fv) = (fx, fx);
92 let mut evaluations = 1;
93 let (mut d, mut e) = (0.0f64, 0.0f64);
94 for _ in 0..500 {
95 let m = 0.5 * (a + b);
96 let tol1 = tol * x.abs() + 1e-12 * tol;
97 let tol2 = 2.0 * tol1;
98 if (x - m).abs() <= tol2 - 0.5 * (b - a) {
99 break;
100 }
101 let mut golden = true;
102 if e.abs() > tol1 {
103 let r = (x - w) * (fx - fv);
105 let q0 = (x - v) * (fx - fw);
106 let mut p = (x - v) * q0 - (x - w) * r;
107 let mut q = 2.0 * (q0 - r);
108 if q > 0.0 {
109 p = -p;
110 }
111 q = q.abs();
112 if p.abs() < (0.5 * q * e).abs() && p > q * (a - x) && p < q * (b - x) {
113 e = d;
114 d = p / q;
115 let u = x + d;
116 if u - a < tol2 || b - u < tol2 {
117 d = if x < m { tol1 } else { -tol1 };
118 }
119 golden = false;
120 }
121 }
122 if golden {
123 e = if x < m { b - x } else { a - x };
124 d = GOLD * e;
125 }
126 let u = if d.abs() >= tol1 {
127 x + d
128 } else if d > 0.0 {
129 x + tol1
130 } else {
131 x - tol1
132 };
133 let fu = f(u);
134 evaluations += 1;
135 if fu <= fx {
136 if u < x {
137 b = x;
138 } else {
139 a = x;
140 }
141 (v, fv, w, fw, x, fx) = (w, fw, x, fx, u, fu);
142 } else {
143 if u < x {
144 a = u;
145 } else {
146 b = u;
147 }
148 if fu <= fw || w == x {
149 (v, fv, w, fw) = (w, fw, u, fu);
150 } else if fu <= fv || v == x || v == w {
151 (v, fv) = (u, fu);
152 }
153 }
154 }
155 (x, fx, evaluations)
156}
157
158#[derive(Debug, Clone, Copy, PartialEq)]
179pub struct NelderMead {
180 pub initial_step: f64,
183 pub tolerance: f64,
185 pub max_iterations: usize,
187}
188
189impl Default for NelderMead {
190 fn default() -> Self {
192 NelderMead {
193 initial_step: 0.1,
194 tolerance: 1e-10,
195 max_iterations: 10_000,
196 }
197 }
198}
199
200#[derive(Debug, Clone, PartialEq)]
210pub struct Minimum {
211 pub x: Vec<f64>,
213 pub value: f64,
215 pub iterations: usize,
217 pub converged: bool,
219}
220
221pub fn nelder_mead(mut f: impl FnMut(&[f64]) -> f64, x0: &[f64], options: &NelderMead) -> Minimum {
246 let mut eval = |x: &[f64]| {
247 let v = f(x);
248 if v.is_finite() { v } else { f64::INFINITY }
249 };
250 let mut x = x0.to_vec();
251 let mut value = eval(&x);
252 let mut iterations = 0;
253 let mut converged = x0.is_empty();
254 let valid = options.initial_step.is_finite() && options.initial_step > options.tolerance;
255 if !converged && valid {
256 for _pass in 0..2 {
257 (x, value, converged) = simplex_pass(&mut eval, x, value, options, &mut iterations);
258 if !converged {
259 break;
260 }
261 }
262 }
263 Minimum {
264 x,
265 value,
266 iterations,
267 converged,
268 }
269}
270
271fn simplex_pass(
274 f: &mut impl FnMut(&[f64]) -> f64,
275 start: Vec<f64>,
276 f_start: f64,
277 options: &NelderMead,
278 iterations: &mut usize,
279) -> (Vec<f64>, f64, bool) {
280 let n = start.len();
281 let tol = options.tolerance;
282 let mut simplex = vec![(start.clone(), f_start)];
283 for i in 0..n {
284 let mut v = start.clone();
285 v[i] += options.initial_step * v[i].abs().max(1.0);
288 let fv = f(&v);
289 simplex.push((v, fv));
290 }
291 let along = |c: &[f64], p: &[f64], t: f64| -> Vec<f64> {
293 c.iter().zip(p).map(|(c, p)| c + t * (p - c)).collect()
294 };
295 loop {
296 simplex.sort_by(|a, b| a.1.total_cmp(&b.1));
297 let (x_best, f_best) = (&simplex[0].0, simplex[0].1);
298 let spread = simplex[n].1 - f_best;
299 let scale = x_best.iter().fold(1.0f64, |m, x| m.max(x.abs()));
300 let diameter = simplex[1..]
301 .iter()
302 .flat_map(|(v, _)| v.iter().zip(x_best).map(|(a, b)| (a - b).abs()))
303 .fold(0.0f64, f64::max);
304 if spread <= tol * f_best.abs().max(1.0) && diameter <= tol * scale {
306 return (x_best.clone(), f_best, true);
307 }
308 if *iterations >= options.max_iterations {
309 return (x_best.clone(), f_best, false);
310 }
311 *iterations += 1;
312
313 let mut centroid = vec![0.0; n];
314 for (v, _) in &simplex[..n] {
315 for (c, x) in centroid.iter_mut().zip(v) {
316 *c += x / n as f64;
317 }
318 }
319 let (f_second, f_worst) = (simplex[n - 1].1, simplex[n].1);
320 let xr = along(¢roid, &simplex[n].0, -1.0);
321 let fr = f(&xr);
322 if fr < f_best {
323 let xe = along(¢roid, &simplex[n].0, -2.0);
324 let fe = f(&xe);
325 simplex[n] = if fe < fr { (xe, fe) } else { (xr, fr) };
326 continue;
327 }
328 if fr < f_second {
329 simplex[n] = (xr, fr);
330 continue;
331 }
332 let (xc, accept) = if fr < f_worst {
334 let xc = along(¢roid, &xr, 0.5);
335 let fc = f(&xc);
336 ((xc, fc), fc <= fr)
337 } else {
338 let xc = along(¢roid, &simplex[n].0, 0.5);
339 let fc = f(&xc);
340 ((xc, fc), fc < f_worst)
341 };
342 if accept {
343 simplex[n] = xc;
344 continue;
345 }
346 let x_best = simplex[0].0.clone();
347 for (v, fv) in &mut simplex[1..] {
348 *v = along(&x_best, v, 0.5);
349 *fv = f(v);
350 }
351 }
352}
353
354#[cfg(test)]
355mod tests {
356 use super::*;
357
358 fn rosenbrock(x: &[f64]) -> f64 {
359 100.0 * (x[1] - x[0] * x[0]).powi(2) + (1.0 - x[0]).powi(2)
360 }
361
362 #[test]
363 fn nelder_mead_finds_the_bottom_of_a_quadratic_bowl() {
364 let f = |x: &[f64]| {
367 let (a, b, c) = (x[0] - 1.0, x[1] + 2.0, x[2] - 0.5);
368 a * a + 4.0 * b * b + 0.5 * c * c + 0.3 * a * b + 2.0
369 };
370 let m = nelder_mead(f, &[0.0, 0.0, 0.0], &NelderMead::default());
371 assert!(m.converged, "{m:?}");
372 for (x, want) in m.x.iter().zip([1.0, -2.0, 0.5]) {
373 assert!((x - want).abs() < 1e-7, "{m:?}");
374 }
375 assert!((m.value - 2.0).abs() < 1e-12, "{m:?}");
376 }
377
378 #[test]
379 fn nelder_mead_solves_rosenbrock() {
380 let m = nelder_mead(rosenbrock, &[-1.2, 1.0], &NelderMead::default());
382 assert!(m.converged, "{m:?}");
383 assert!(
384 (m.x[0] - 1.0).abs() < 1e-6 && (m.x[1] - 1.0).abs() < 1e-6,
385 "{m:?}"
386 );
387 assert!(m.value < 1e-12, "{m:?}");
388 }
389
390 #[test]
391 fn nelder_mead_works_in_one_dimension() {
392 let m = nelder_mead(|x| x[0].exp() - 3.0 * x[0], &[5.0], &NelderMead::default());
394 assert!(m.converged, "{m:?}");
395 assert!((m.x[0] - 3f64.ln()).abs() < 1e-7, "{m:?}");
396 }
397
398 #[test]
399 fn nelder_mead_steps_around_a_nan_region() {
400 let mut calls = 0;
404 let mut infeasible_calls = Vec::new();
405 let f = |x: &[f64]| {
406 calls += 1;
407 if x[0] >= 0.55 {
408 infeasible_calls.push(calls - 1);
409 return f64::NAN;
410 }
411 (x[0] - 0.54).powi(2) + (x[1] - 1.0).powi(2)
412 };
413 let m = nelder_mead(f, &[0.5, 0.0], &NelderMead::default());
414 assert!(m.converged, "{m:?}");
415 assert!(
416 (m.x[0] - 0.54).abs() < 1e-7 && (m.x[1] - 1.0).abs() < 1e-7,
417 "{m:?}"
418 );
419 assert_eq!(infeasible_calls.first(), Some(&1), "{infeasible_calls:?}");
420 assert!(
421 infeasible_calls.len() > 1,
422 "only the starting vertex was infeasible"
423 );
424 }
425
426 #[test]
427 fn nelder_mead_moves_off_a_start_near_zero() {
428 let m = nelder_mead(|x| (x[0] - 3.0).powi(2), &[1e-10], &NelderMead::default());
431 assert!(m.converged && m.iterations > 0, "{m:?}");
432 assert!((m.x[0] - 3.0).abs() < 1e-7 && m.value < 1e-14, "{m:?}");
433 }
434
435 #[test]
436 fn nelder_mead_rejects_a_step_within_tolerance() {
437 let f = |x: &[f64]| (x[0] - 3.0).powi(2);
438 for initial_step in [0.0, 1e-10, -0.1, f64::NAN, f64::INFINITY] {
439 let options = NelderMead {
440 initial_step,
441 ..NelderMead::default()
442 };
443 let m = nelder_mead(f, &[1.0], &options);
444 assert!(!m.converged && m.iterations == 0 && m.x == [1.0], "{m:?}");
445 }
446 }
447
448 #[test]
449 fn nelder_mead_reports_an_infeasible_start() {
450 let m = nelder_mead(|_| f64::NAN, &[1.0, 1.0], &NelderMead::default());
451 assert!(!m.converged && m.value == f64::INFINITY, "{m:?}");
452 }
453
454 #[test]
455 fn nelder_mead_stops_at_max_iterations() {
456 let options = NelderMead {
457 max_iterations: 10,
458 ..NelderMead::default()
459 };
460 let m = nelder_mead(rosenbrock, &[-1.2, 1.0], &options);
461 assert!(!m.converged && m.iterations == 10, "{m:?}");
462 assert!(m.value <= rosenbrock(&[-1.2, 1.0]), "{m:?}");
463 }
464
465 #[test]
466 fn brent_beats_golden_section_on_smooth_functions() {
467 let f = |x: f64| x.exp() - 3.0 * x;
468 let (x, _, n) = brent(0.0, 3.0, 1e-12, f);
469 assert!((x - 3f64.ln()).abs() < 1e-7, "{x}");
470 assert!(n < 20, "{n}");
471 let (x, _, _) = brent(1.0, 2.0, 1e-12, |x| x * x);
473 assert!((x - 1.0).abs() < 1e-7);
474 }
475
476 #[test]
477 fn minimize_escapes_a_local_minimum() {
478 let f = |x: f64| (x - 3.0).powi(2) * (x - 0.5).powi(2) - 0.5 * x;
480 let x = minimize(0.0, 4.0, f);
481 assert!(x > 2.5, "{x}");
482 }
483}