use crate::core::math::Scalar;
use super::common::{
Bracket, Counts, Evaluator, Point, Settings, ValueOnly, num, root_builders,
same_sign, secant,
};
use super::{RootError, RootResult};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Toms748Root<F: Scalar = f64> {
settings: Settings<F>,
}
impl<F: Scalar> Toms748Root<F> {
pub fn new(lower: F, upper: F) -> Self {
Self {
settings: Settings::new(lower, upper),
}
}
root_builders!();
pub fn solve<C, E>(
&self,
function: C,
) -> Result<RootResult<F>, RootError<E, F>>
where
C: FnMut(F) -> Result<F, E>,
{
let mut eval = ValueOnly(function);
let mut counts = Counts::default();
let mut bracket =
Bracket::initialize(&self.settings, &mut eval, &mut counts)?;
if self.settings.max_iter == 0 || bracket.converged(&self.settings) {
return Ok(bracket.result(&self.settings, 0, counts));
}
let candidate = interior(&bracket, secant(bracket.a, bracket.b));
let point = eval.evaluate(candidate, &mut counts)?;
let mut d = bracket.update(point);
let mut e = None;
let mut iterations = 1;
while iterations < self.settings.max_iter
&& !bracket.converged(&self.settings)
{
iterations += 1;
let start_width = bracket.width();
for steps in [2, 3] {
let cubic =
e.map(|e| inverse_cubic(bracket.a, bracket.b, d, e));
let candidate = match cubic {
Some(c) if bracket.contains(c) => c,
_ => newton_quadratic(&bracket, d, steps),
};
let point =
eval.evaluate(interior(&bracket, candidate), &mut counts)?;
e = Some(d);
d = bracket.update(point);
if bracket.converged(&self.settings) {
return Ok(bracket.result(
&self.settings,
iterations,
counts,
));
}
}
let best = bracket.best();
let step = secant(bracket.a, bracket.b) - best.x;
let candidate = if step.abs() <= num::<F>(0.25) * bracket.width() {
best.x + num::<F>(2.0) * step
} else {
bracket.midpoint()
};
let point =
eval.evaluate(interior(&bracket, candidate), &mut counts)?;
e = Some(d);
d = bracket.update(point);
if bracket.converged(&self.settings) {
return Ok(bracket.result(&self.settings, iterations, counts));
}
if bracket.width() > num::<F>(0.5) * start_width {
let point = eval.evaluate(bracket.midpoint(), &mut counts)?;
e = Some(d);
d = bracket.update(point);
}
}
Ok(bracket.result(&self.settings, iterations, counts))
}
}
fn interior<F: Scalar>(bracket: &Bracket<F>, candidate: F) -> F {
if bracket.contains(candidate) {
candidate
} else {
bracket.midpoint()
}
}
fn inverse_cubic<F: Scalar>(
a: Point<F>,
b: Point<F>,
c: Point<F>,
d: Point<F>,
) -> F {
let scale = a
.value
.abs()
.max(b.value.abs())
.max(c.value.abs())
.max(d.value.abs());
let [fa, fb, fc, fd] = [
a.value / scale,
b.value / scale,
c.value / scale,
d.value / scale,
];
let values = [fa, fb, fc, fd];
for i in 0..4 {
for j in 0..i {
if values[i] == values[j] {
return F::nan();
}
}
}
let q11 = (c.x - d.x) * (fc / (fd - fc));
let q21 = (b.x - c.x) * (fb / (fc - fb));
let q31 = (a.x - b.x) * (fa / (fb - fa));
let d21 = (b.x - c.x) * (fc / (fc - fb));
let d31 = (a.x - b.x) * (fb / (fb - fa));
let q22 = (d21 - q11) * (fb / (fd - fb));
let q32 = (d31 - q21) * (fa / (fc - fa));
let d32 = (d31 - q21) * (fc / (fc - fa));
let q33 = (d32 - q22) * (fa / (fd - fa));
a.x + (q31 + q32 + q33)
}
fn newton_quadratic<F: Scalar>(
bracket: &Bracket<F>,
d: Point<F>,
steps: usize,
) -> F {
let width = bracket.width();
let td = (d.x - bracket.a.x) / width;
let scale = bracket
.a
.value
.abs()
.max(bracket.b.value.abs())
.max(d.value.abs());
let fa = bracket.a.value / scale;
let fb = bracket.b.value / scale;
let fd = d.value / scale;
let b = fb - fa;
let a = ((fd - fb) / (td - F::one()) - b) / td;
if !a.is_finite() || a == F::zero() {
return secant(bracket.a, bracket.b);
}
let mut r = if same_sign(a, fa) {
F::zero()
} else {
F::one()
};
for _ in 0..steps {
let polynomial = (a * (r - F::one()) + b) * r + fa;
let derivative = b + a * (num::<F>(2.0) * r - F::one());
r = r - polynomial / derivative;
}
interior(bracket, bracket.a.x + width * r)
}