pub const OPTIMIZE_TOL: f64 = 1.220_703_125e-4;
const BRENT_SQRT_EPS: f64 = 1.490_116_119_384_765_6e-8;
const BRENT_GOLDEN: f64 = 0.381_966_011_250_105_15;
pub const UNIROOT_TOL: f64 = 1.220_703_125e-4;
pub const UNIROOT_MAXIT: usize = 1000;
const ZEROIN_EPS: f64 = f64::EPSILON;
const ZEROIN_INTERP_FRAC: f64 = 0.75;
pub fn brent_fmin<F>(ax: f64, bx: f64, mut f: F, tol: f64) -> f64
where
F: FnMut(f64) -> f64,
{
let (mut a, mut b) = (ax, bx);
let mut v = a + BRENT_GOLDEN * (b - a);
let (mut w, mut x) = (v, v);
let mut d = 0.0_f64;
let mut e = 0.0_f64;
let mut fx = f(x);
let (mut fv, mut fw) = (fx, fx);
let tol3 = tol / 3.0;
loop {
let xm = 0.5 * (a + b);
let tol1 = BRENT_SQRT_EPS * x.abs() + tol3;
let t2 = 2.0 * tol1;
if (x - xm).abs() <= t2 - 0.5 * (b - a) {
return x;
}
let (mut p, mut q, mut r) = (0.0_f64, 0.0_f64, 0.0_f64);
if e.abs() > tol1 {
r = (x - w) * (fx - fv);
q = (x - v) * (fx - fw);
p = (x - v) * q - (x - w) * r;
q = 2.0 * (q - r);
if q > 0.0 {
p = -p;
} else {
q = -q;
}
r = e;
e = d;
}
if p.abs() >= (0.5 * q * r).abs() || p <= q * (a - x) || p >= q * (b - x) {
e = if x < xm { b - x } else { a - x };
d = BRENT_GOLDEN * e;
} else {
d = p / q;
let u = x + d;
if u - a < t2 || b - u < t2 {
d = if x >= xm { -tol1 } else { tol1 };
}
}
let u = if d.abs() >= tol1 {
x + d
} else if d > 0.0 {
x + tol1
} else {
x - tol1
};
let fu = f(u);
if fu <= fx {
if u < x {
b = x;
} else {
a = x;
}
v = w;
fv = fw;
w = x;
fw = fx;
x = u;
fx = fu;
} else {
if u < x {
a = u;
} else {
b = u;
}
if fu <= fw || w == x {
v = w;
fv = fw;
w = u;
fw = fu;
} else if fu <= fv || v == x || v == w {
v = u;
fv = fu;
}
}
}
}
pub fn zeroin<F>(
ax: f64,
bx: f64,
fa: f64,
fb: f64,
mut f: F,
tol: f64,
maxit: usize,
) -> Option<f64>
where
F: FnMut(f64) -> f64,
{
let (mut a, mut b, mut fa, mut fb) = (ax, bx, fa, fb);
if fa == 0.0 {
return Some(a);
}
if fb == 0.0 {
return Some(b);
}
if fa * fb > 0.0 {
return None;
}
let (mut c, mut fc) = (a, fa);
for _ in 0..=maxit {
let prev_step = b - a;
if fc.abs() < fb.abs() {
a = b;
b = c;
c = a;
fa = fb;
fb = fc;
fc = fa;
}
let tol_act = 2.0 * ZEROIN_EPS * b.abs() + tol / 2.0;
let mut new_step = (c - b) / 2.0;
if new_step.abs() <= tol_act || fb == 0.0 {
return Some(b);
}
if prev_step.abs() >= tol_act && fa.abs() > fb.abs() {
let cb = c - b;
let (mut p, mut q) = if a == c {
let t1 = fb / fa;
(cb * t1, 1.0 - t1)
} else {
let q0 = fa / fc;
let t1 = fb / fc;
let t2 = fb / fa;
(
t2 * (cb * q0 * (q0 - t1) - (b - a) * (t1 - 1.0)),
(q0 - 1.0) * (t1 - 1.0) * (t2 - 1.0),
)
};
if p > 0.0 {
q = -q;
} else {
p = -p;
}
if p < (ZEROIN_INTERP_FRAC * cb * q - (tol_act * q).abs() / 2.0)
&& p < (prev_step * q / 2.0).abs()
{
new_step = p / q;
}
}
if new_step.abs() < tol_act {
new_step = if new_step > 0.0 { tol_act } else { -tol_act };
}
a = b;
fa = fb;
b += new_step;
fb = f(b);
if (fb > 0.0 && fc > 0.0) || (fb < 0.0 && fc < 0.0) {
c = a;
fc = fa;
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_brent_fmin_matches_r_optimize() {
let got = brent_fmin(0.0, 1.0, |x| (x - 1.0 / 3.0).powi(2), OPTIMIZE_TOL);
assert_relative_eq!(got, 0.333_333_333_333_333_3, max_relative = 1e-14);
}
#[test]
fn test_brent_fmin_matches_r_on_a_skewed_objective() {
let got = brent_fmin(0.0, 3.0, |x| x * (4.0 * x).sin(), OPTIMIZE_TOL);
assert_relative_eq!(got, 1.2282971127458993, max_relative = 1e-14);
}
#[test]
fn test_brent_fmin_finds_a_boundary_minimum() {
let got = brent_fmin(-1.0, 2.0, |x| x, OPTIMIZE_TOL);
assert_relative_eq!(got, -0.999_924_248_162_969_8, max_relative = 1e-12);
}
#[test]
fn test_brent_fmin_tighter_tol_gets_closer() {
let quartic = |x: f64| (x - 1.0 / 3.0).powi(4);
let target = 1.0 / 3.0;
let loose = brent_fmin(0.0, 1.0, quartic, OPTIMIZE_TOL);
assert_relative_eq!(loose, 0.33333944604491594, max_relative = 1e-12);
let tight = brent_fmin(0.0, 1.0, quartic, 1e-12);
assert_relative_eq!(tight, 0.333_333_333_266_562_2, max_relative = 1e-12);
assert!((tight - target).abs() < (loose - target).abs());
}
}