use crate::errors::QlResult;
use crate::fail;
use crate::math::comparison::close;
use crate::math::solver1d::{
DerivativeSolver, Function1D, Solver1DState, SolverConfig, checked_derivative,
checked_function_value,
};
use crate::types::Real;
#[derive(Clone, Copy, Debug)]
pub struct NewtonSafe {
config: SolverConfig,
}
impl NewtonSafe {
pub fn new() -> Self {
NewtonSafe {
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 NewtonSafe {
fn default() -> Self {
NewtonSafe::new()
}
}
impl DerivativeSolver for NewtonSafe {
fn config(&self) -> &SolverConfig {
&self.config
}
fn refine<G: Function1D>(
&self,
g: &mut G,
x_accuracy: Real,
mut st: Solver1DState,
) -> QlResult<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 dxold = st.x_max - st.x_min;
let mut dx = dxold;
let mut froot = checked_function_value(g, st.root)?;
if close(froot, 0.0) {
return Ok(st.root);
}
let mut dfroot = checked_derivative(g, st.root)?;
st.evaluation_number += 1;
while st.evaluation_number <= self.config.max_evaluations {
if ((st.root - xh) * dfroot - froot) * ((st.root - xl) * dfroot - froot) > 0.0
|| (2.0 * froot).abs() > (dxold * dfroot).abs()
{
dxold = dx;
dx = 0.5 * (xh - xl);
st.root = xl + dx;
} else {
dxold = dx;
dx = froot / dfroot;
st.root -= dx;
}
if dx.abs() < x_accuracy {
let _ = checked_function_value(g, st.root)?;
st.evaluation_number += 1;
return Ok(st.root);
}
froot = checked_function_value(g, st.root)?;
if close(froot, 0.0) {
return Ok(st.root);
}
dfroot = checked_derivative(g, st.root)?;
st.evaluation_number += 1;
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::solver1d::func1d;
use crate::math::solvers1d::testkit;
#[test]
fn finds_known_roots() {
testkit::check_derivative_solver_finds_roots(NewtonSafe::new);
}
#[test]
fn handles_the_bracket_escape_stress_case() {
testkit::check_safe_derivative_solver(NewtonSafe::new);
}
#[test]
fn last_call_is_made_with_the_root() {
testkit::check_derivative_last_call(NewtonSafe::new);
}
#[test]
fn rejects_invalid_inputs() {
testkit::check_derivative_rejects(NewtonSafe::new);
}
#[test]
fn honours_configured_bounds() {
let solver = NewtonSafe::new().with_upper_bound(2.0);
assert!(
solver
.solve_bracketed(func1d(testkit::f1, testkit::d1), 1e-8, 1.5, 0.0, 3.0)
.is_err()
);
}
#[test]
fn flat_root_returns_the_root_not_nan() {
let g = func1d(
|x: Real| (x - 1.0).powi(3),
|x: Real| 3.0 * (x - 1.0).powi(2),
);
let root = NewtonSafe::new()
.solve_bracketed(g, 1e-10, 1.0, 0.0, 2.0)
.unwrap();
assert_eq!(root, 1.0);
}
#[test]
fn degrades_to_bisection_with_a_useless_derivative() {
let g = func1d(|x: Real| x * x - 2.0, |_: Real| 0.0);
let root = NewtonSafe::new()
.solve_bracketed(g, 1e-10, 1.0, 0.0, 2.0)
.unwrap();
assert!((root - 2.0_f64.sqrt()).abs() <= 1e-9, "root={root}");
}
}