use crate::error::{MathError, MathResult};
use crate::solvers::{SolverConfig, SolverResult};
pub fn secant<F>(f: F, x0: f64, x1: f64, config: &SolverConfig) -> MathResult<SolverResult>
where
F: Fn(f64) -> f64,
{
let mut x_prev = x0;
let mut x_curr = x1;
let mut f_prev = f(x_prev);
let mut f_curr = f(x_curr);
for iteration in 0..config.max_iterations {
if f_curr.abs() < config.tolerance {
return Ok(SolverResult {
root: x_curr,
iterations: iteration,
residual: f_curr,
});
}
let denom = f_curr - f_prev;
if denom.abs() < 1e-15 {
return Err(MathError::DivisionByZero { value: denom });
}
let x_next = x_curr - f_curr * (x_curr - x_prev) / denom;
if (x_next - x_curr).abs() < config.tolerance {
let f_next = f(x_next);
return Ok(SolverResult {
root: x_next,
iterations: iteration + 1,
residual: f_next,
});
}
x_prev = x_curr;
f_prev = f_curr;
x_curr = x_next;
f_curr = f(x_curr);
}
Err(MathError::convergence_failed(
config.max_iterations,
f_curr.abs(),
))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_sqrt_2() {
let f = |x: f64| x * x - 2.0;
let result = secant(f, 1.0, 2.0, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, std::f64::consts::SQRT_2, epsilon = 1e-10);
}
#[test]
fn test_cube_root() {
let f = |x: f64| x * x * x - 27.0;
let result = secant(f, 2.0, 4.0, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, 3.0, epsilon = 1e-10);
}
#[test]
fn test_sin() {
let f = |x: f64| x.sin();
let result = secant(f, 3.0, 3.5, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, std::f64::consts::PI, epsilon = 1e-10);
}
#[test]
fn test_convergence_speed() {
let f = |x: f64| x * x - 2.0;
let result = secant(f, 1.0, 2.0, &SolverConfig::default()).unwrap();
assert!(result.iterations < 15);
}
#[test]
fn test_close_initial_guesses() {
let f = |x: f64| x * x - 2.0;
let result = secant(f, 1.4, 1.42, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, std::f64::consts::SQRT_2, epsilon = 1e-10);
}
}