Skip to content

Commit

Permalink
Add root finding using Halley's method
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlend committed Sep 22, 2024
1 parent 48a657e commit dc57def
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 4 deletions.
90 changes: 90 additions & 0 deletions src/kete_core/src/fitting/halley.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
//! # Halley's method
//!
//! Third order root finding algorithm.
//! This is the next order method of newton-raphson.
use crate::{errors::Error, prelude::NeosResult};

/// Solve root using Halley's method.
///
/// This accepts a three functions, the first being a single input function for which
/// the root is desired. The second function being the derivative of the first with
/// respect to the input variable. The third is the third derivative.
///
/// ```
/// use kete_core::fitting::halley;
/// let f = |x| { 1.0 * x * x - 1.0 };
/// let d = |x| { 2.0 * x };
/// let dd = |_| { 2.0};
/// let root = halley(f, d, dd, 0.0, 1e-10).unwrap();
/// assert!((root - 1.0).abs() < 1e-12);
/// ```
///
#[inline(always)]
pub fn halley<Func, Der, SecDer>(
func: Func,
der: Der,
sec_der: SecDer,
start: f64,
atol: f64,
) -> NeosResult<f64>
where
Func: Fn(f64) -> f64,
Der: Fn(f64) -> f64,
SecDer: Fn(f64) -> f64,
{
let mut x = start;

// if the starting position has derivative of 0, nudge it a bit.
if der(x).abs() < 1e-12 {
x += 0.1;
}

let mut f_eval: f64;
let mut d_eval: f64;
let mut dd_eval: f64;
let mut step: f64;
for _ in 0..100 {
f_eval = func(x);
if f_eval.abs() < atol {
return Ok(x);
}
d_eval = der(x);

// Derivative is 0, cannot solve
if d_eval.abs() < 1e-12 {
Err(Error::Convergence(
"Halley's root finding failed to converge due to zero derivative.".into(),
))?;
}

dd_eval = sec_der(x);

if !dd_eval.is_finite() || !d_eval.is_finite() || !f_eval.is_finite() {
Err(Error::Convergence(
"Halley root finding failed to converge due to non-finite evaluations".into(),
))?;
}
step = f_eval / d_eval;
step = step / (1.0 - step * dd_eval / (2.0 * d_eval));

x -= step;
}
Err(Error::Convergence(
"Halley's root finding hit iteration limit without converging.".into(),
))?
}

#[cfg(test)]
mod tests {
use crate::fitting::halley;

#[test]
fn test_haley() {
let f = |x| 1.0 * x * x - 1.0;
let d = |x| 2.0 * x;
let dd = |_| 2.0;
let root = halley(f, d, dd, 0.0, 1e-10).unwrap();
assert!((root - 1.0).abs() < 1e-12);
}
}
2 changes: 2 additions & 0 deletions src/kete_core/src/fitting/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! # Fitting
//! Fitting tools, including root finding.
mod halley;
mod newton;
mod reduced_chi2;

pub use halley::halley;
pub use newton::newton_raphson;
pub use reduced_chi2::{fit_reduced_chi2, reduced_chi2, reduced_chi2_der};
5 changes: 1 addition & 4 deletions src/kete_core/src/fitting/newton.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ where

// if the starting position has derivative of 0, nudge it a bit.
if der(x).abs() < 1e-12 {
x += 1.0;
x += 0.1;
}

let mut f_eval: f64;
Expand Down Expand Up @@ -75,8 +75,5 @@ mod tests {

let root = newton_raphson(f, d, 0.0, 1e-10).unwrap();
assert!((root - 1.0).abs() < 1e-12);

let root = newton_raphson(f, d, 1.0, 1e-10).unwrap();
assert!((root - 1.0).abs() < 1e-12);
}
}

0 comments on commit dc57def

Please sign in to comment.