use crate::optimizers::{
ConvergenceStatus, Objective, OptimizeResult, dot, line_search, matvec, norm, step,
};
#[must_use]
pub fn newton(obj: &impl Objective, x0: &[f64], max_iter: usize, tol: f64) -> OptimizeResult {
let mut x = x0.to_vec();
let mut status = ConvergenceStatus::MaxIterReached;
let mut iterations = 0;
for step_idx in 0..max_iter {
iterations = step_idx + 1;
let g = obj.grad(&x);
if norm(&g) < tol {
status = ConvergenceStatus::Converged;
break;
}
let hess = obj.hessian(&x);
let neg_g: Vec<f64> = g.iter().map(|gi| -gi).collect();
let mut dir = cg_solve(&hess, &neg_g);
if dot(&dir, &g) >= 0.0 {
dir = neg_g;
}
let alpha = line_search(obj, &x, &dir, &g);
x = step(&x, alpha, &dir);
}
let fx = obj.value(&x);
OptimizeResult {
x,
fx,
iterations,
status,
}
}
#[allow(clippy::many_single_char_names)]
fn cg_solve(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
let n = b.len();
let mut p = vec![0.0; n];
let mut r = b.to_vec();
let mut d = r.clone();
let mut rs_old = dot(&r, &r);
for _ in 0..n.max(1) {
if rs_old.sqrt() < 1e-12 {
break;
}
let ad = matvec(a, &d);
let denom = dot(&d, &ad);
if denom.abs() < 1e-30 {
break;
}
let alpha = rs_old / denom;
for (pi, di) in p.iter_mut().zip(&d) {
*pi += alpha * di;
}
for (ri, adi) in r.iter_mut().zip(&ad) {
*ri -= alpha * adi;
}
let rs_new = dot(&r, &r);
let beta = rs_new / rs_old;
for (di, ri) in d.iter_mut().zip(&r) {
*di = beta.mul_add(*di, *ri);
}
rs_old = rs_new;
}
p
}