Skip to main content

rustyqlib/core/solvers/
secant.rs

1//! Secant method: Newton's convergence class without a derivative,
2//! approximating the slope from the last two iterates.
3
4use super::solver_1d::{Root, Solver1d};
5
6/// Secant iteration from two starting points.
7pub fn secant(cfg: &Solver1d, f: impl Fn(f64) -> f64, x0: f64, x1: f64) -> Root {
8    let (mut x_prev, mut x) = (x0, x1);
9    let mut f_prev = f(x_prev);
10    if f_prev.abs() <= cfg.tol {
11        return Root { x: x_prev, iterations: 0, converged: true };
12    }
13    for i in 0..cfg.max_iter {
14        let fx = f(x);
15        if fx.abs() <= cfg.tol {
16            return Root { x, iterations: i, converged: true };
17        }
18        let slope = (fx - f_prev) / (x - x_prev);
19        if slope == 0.0 || !slope.is_finite() {
20            return Root { x, iterations: i, converged: false };
21        }
22        (x_prev, f_prev) = (x, fx);
23        x -= fx / slope;
24    }
25    Root { x, iterations: cfg.max_iter, converged: f(x).abs() <= cfg.tol }
26}
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    #[test]
33    fn finds_sqrt_two() {
34        let r = secant(&Solver1d::default(), |x| x * x - 2.0, 1.0, 2.0);
35        assert!(r.converged && (r.x - std::f64::consts::SQRT_2).abs() < 1e-12, "{r:?}");
36    }
37}