use crate::errors::QlResult;
use crate::fail;
use crate::math::comparison::close;
use crate::math::solver1d::{Solver1D, Solver1DState, SolverConfig};
use crate::types::Real;
#[derive(Clone, Copy, Debug)]
pub struct Secant {
config: SolverConfig,
}
impl Secant {
pub fn new() -> Self {
Secant {
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 Secant {
fn default() -> Self {
Secant::new()
}
}
impl Solver1D for Secant {
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 froot, mut xl, mut fl) = if st.fx_min.abs() < st.fx_max.abs() {
st.root = st.x_min;
(st.fx_min, st.x_max, st.fx_max)
} else {
st.root = st.x_max;
(st.fx_max, st.x_min, st.fx_min)
};
while st.evaluation_number <= self.max_evaluations() {
let dx = (xl - st.root) * froot / (froot - fl);
xl = st.root;
fl = froot;
st.root += dx;
froot = f(st.root);
st.evaluation_number += 1;
if dx.abs() < x_accuracy || close(froot, 0.0) {
return Ok(st.root);
}
}
fail!(
"maximum number of function evaluations ({}) exceeded",
self.max_evaluations()
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::solvers1d::testkit;
#[test]
fn finds_known_roots() {
testkit::check_finds_known_roots(Secant::new);
}
#[test]
fn last_call_is_made_with_the_root() {
testkit::check_last_call_with_root(Secant::new);
}
#[test]
fn rejects_invalid_inputs() {
testkit::check_rejects_invalid_inputs(Secant::new);
}
#[test]
fn honours_configured_bounds() {
testkit::check_honours_bounds(Secant::new);
}
}