use crate::error::{MathError, MathResult};
use crate::solvers::{SolverConfig, SolverResult};
pub fn newton_raphson<F, DF>(
f: F,
df: DF,
initial_guess: f64,
config: &SolverConfig,
) -> MathResult<SolverResult>
where
F: Fn(f64) -> f64,
DF: Fn(f64) -> f64,
{
let mut x = initial_guess;
for iteration in 0..config.max_iterations {
let fx = f(x);
if fx.abs() < config.tolerance {
return Ok(SolverResult {
root: x,
iterations: iteration,
residual: fx,
});
}
let dfx = df(x);
if dfx.abs() < 1e-15 {
return Err(MathError::DivisionByZero { value: dfx });
}
let step = fx / dfx;
x -= step;
if step.abs() < config.tolerance {
let final_fx = f(x);
return Ok(SolverResult {
root: x,
iterations: iteration + 1,
residual: final_fx,
});
}
}
Err(MathError::convergence_failed(
config.max_iterations,
f(x).abs(),
))
}
pub fn newton_raphson_numerical<F>(
f: F,
initial_guess: f64,
config: &SolverConfig,
) -> MathResult<SolverResult>
where
F: Fn(f64) -> f64,
{
let h = 1e-8;
let df = |x: f64| {
let f1 = f(x + h);
let f2 = f(x - h);
(f1 - f2) / (2.0 * h)
};
newton_raphson(&f, df, initial_guess, config)
}
#[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 df = |x: f64| 2.0 * x;
let result = newton_raphson(f, df, 1.5, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, std::f64::consts::SQRT_2, epsilon = 1e-10);
assert!(result.iterations < 10); }
#[test]
fn test_cube_root() {
let f = |x: f64| x * x * x - 27.0;
let df = |x: f64| 3.0 * x * x;
let result = newton_raphson(f, df, 2.0, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, 3.0, epsilon = 1e-10);
}
#[test]
fn test_numerical_derivative() {
let f = |x: f64| x * x - 2.0;
let result = newton_raphson_numerical(f, 1.5, &SolverConfig::default()).unwrap();
assert_relative_eq!(result.root, std::f64::consts::SQRT_2, epsilon = 1e-8);
}
#[test]
fn test_zero_derivative_error() {
let f = |x: f64| x * x * x - 1.0;
let df = |x: f64| 3.0 * x * x;
let result = newton_raphson(f, df, 0.0, &SolverConfig::default());
assert!(result.is_err());
}
#[test]
fn test_convergence_fail() {
let f = |x: f64| x.sin();
let df = |x: f64| x.cos();
let config = SolverConfig::new(1e-15, 5); let result = newton_raphson(f, df, 0.5, &config);
assert!(result.is_ok());
}
}