use crate::errors::QlResult;
use crate::fail;
use crate::math::comparison::close_n;
use crate::math::solver1d::{Solver1D, Solver1DState, SolverConfig, checked_value};
use crate::types::Real;
#[derive(Clone, Copy, Debug)]
pub struct FiniteDifferenceNewtonSafe {
config: SolverConfig,
}
impl FiniteDifferenceNewtonSafe {
pub fn new() -> Self {
FiniteDifferenceNewtonSafe {
config: SolverConfig::new(),
}
}
pub fn with_max_evaluations(mut self, evaluations: usize) -> Self {
self.config.max_evaluations = evaluations;
self
}
pub fn with_lower_bound(mut self, lower_bound: Real) -> Self {
self.config.lower_bound = Some(lower_bound);
self
}
pub fn with_upper_bound(mut self, upper_bound: Real) -> Self {
self.config.upper_bound = Some(upper_bound);
self
}
}
impl Default for FiniteDifferenceNewtonSafe {
fn default() -> Self {
FiniteDifferenceNewtonSafe::new()
}
}
impl Solver1D for FiniteDifferenceNewtonSafe {
fn config(&self) -> &SolverConfig {
&self.config
}
fn config_mut(&mut self) -> &mut SolverConfig {
&mut self.config
}
fn solve_impl<F>(
&mut self,
f: &mut F,
x_accuracy: Real,
st: &mut Solver1DState,
) -> QlResult<Real>
where
F: FnMut(Real) -> Real,
{
let (mut xl, mut xh) = if st.fx_min < 0.0 {
(st.x_min, st.x_max)
} else {
(st.x_max, st.x_min)
};
let mut froot = checked_value(f, st.root)?;
st.evaluation_number += 1;
let mut dfroot = if st.x_max - st.root < st.root - st.x_min {
(st.fx_max - froot) / (st.x_max - st.root)
} else {
(st.fx_min - froot) / (st.x_min - st.root)
};
let mut dx = st.x_max - st.x_min;
while st.evaluation_number <= self.config.max_evaluations {
let mut frootold = froot;
let mut rootold = st.root;
let dxold = dx;
if ((st.root - xh) * dfroot - froot) * ((st.root - xl) * dfroot - froot) > 0.0
|| (2.0 * froot).abs() > (dxold * dfroot).abs()
{
dx = 0.5 * (xh - xl);
st.root = xl + dx;
if close_n(st.root, rootold, 2500) {
rootold = xh;
frootold = checked_value(f, xh)?;
}
} else {
dx = froot / dfroot;
st.root -= dx;
}
if dx.abs() < x_accuracy {
return Ok(st.root);
}
froot = checked_value(f, st.root)?;
st.evaluation_number += 1;
dfroot = (frootold - froot) / (rootold - st.root);
if froot < 0.0 {
xl = st.root;
} else {
xh = st.root;
}
}
fail!(
"maximum number of function evaluations ({}) exceeded",
self.config.max_evaluations
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::solvers1d::testkit;
#[test]
fn finds_known_roots() {
testkit::check_finds_known_roots(FiniteDifferenceNewtonSafe::new);
}
#[test]
fn rejects_invalid_inputs() {
testkit::check_rejects_invalid_inputs(FiniteDifferenceNewtonSafe::new);
}
#[test]
fn honours_configured_bounds() {
testkit::check_honours_bounds(FiniteDifferenceNewtonSafe::new);
}
}