pub fn golden(mut f: impl FnMut(f64) -> f64, mut a: f64, mut b: f64, tol: f64) -> (f64, f64) {
let g = (5f64.sqrt() - 1.0) / 2.0;
let mut c = b - g * (b - a);
let mut d = a + g * (b - a);
let (mut fc, mut fd) = (f(c), f(d));
while (b - a).abs() > tol {
if fc < fd {
b = d;
(d, fd) = (c, fc);
c = b - g * (b - a);
fc = f(c);
} else {
a = c;
(c, fc) = (d, fd);
d = a + g * (b - a);
fd = f(d);
}
}
let x = (a + b) / 2.0;
(x, f(x))
}
pub fn nelder_mead(mut f: impl FnMut(&[f64]) -> f64, start: &[f64], step: f64, iters: usize) -> (Vec<f64>, f64) {
let n = start.len();
let mut pts: Vec<Vec<f64>> = vec![start.to_vec()];
for i in 0..n {
let mut p = start.to_vec();
p[i] += step;
pts.push(p);
}
let mut vals: Vec<f64> = pts.iter().map(|p| f(p)).collect();
let lerp = |a: &[f64], b: &[f64], t: f64| -> Vec<f64> { a.iter().zip(b).map(|(x, y)| x + t * (y - x)).collect() };
for _ in 0..iters {
let mut order: Vec<usize> = (0..=n).collect();
order.sort_by(|&i, &j| vals[i].total_cmp(&vals[j]));
pts = order.iter().map(|&i| pts[i].clone()).collect();
vals = order.iter().map(|&i| vals[i]).collect();
let centroid: Vec<f64> = (0..n).map(|k| pts[..n].iter().map(|p| p[k]).sum::<f64>() / n as f64).collect();
let worst = pts[n].clone();
let refl = lerp(&worst, ¢roid, 2.0);
let fr = f(&refl);
if fr < vals[0] {
let exp = lerp(&worst, ¢roid, 3.0);
let fe = f(&exp);
(pts[n], vals[n]) = if fe < fr { (exp, fe) } else { (refl, fr) };
} else if fr < vals[n - 1] {
(pts[n], vals[n]) = (refl, fr);
} else {
let con = lerp(&worst, ¢roid, 0.5);
let fcn = f(&con);
if fcn < vals[n] {
(pts[n], vals[n]) = (con, fcn);
} else {
for i in 1..=n {
pts[i] = lerp(&pts[0], &pts[i], 0.5);
vals[i] = f(&pts[i]);
}
}
}
}
let best = (0..=n).min_by(|&i, &j| vals[i].total_cmp(&vals[j])).unwrap_or(0);
(pts[best].clone(), vals[best])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn finds_minima() {
let (x, _) = golden(|x| (x - 1.7).powi(2) + 3.0, -5.0, 5.0, 1e-8);
assert!((x - 1.7).abs() < 1e-6);
let (p, v) = nelder_mead(|p| (p[0] - 1.0).powi(2) + 10.0 * (p[1] + 2.0).powi(2), &[0.0, 0.0], 0.5, 400);
assert!((p[0] - 1.0).abs() < 1e-4 && (p[1] + 2.0).abs() < 1e-4 && v < 1e-7);
}
}