use crate::optimizers::{
ConvergenceStatus, Objective, OptimizeResult, dot, line_search, norm, step,
};
#[must_use]
pub fn conjugate_gradient(
obj: &impl Objective,
x0: &[f64],
max_iter: usize,
tol: f64,
) -> OptimizeResult {
let mut x = x0.to_vec();
let n = x.len().max(1);
let mut g = obj.grad(&x);
let mut dir: Vec<f64> = g.iter().map(|gi| -gi).collect();
let mut status = ConvergenceStatus::MaxIterReached;
let mut iterations = 0;
for step_idx in 0..max_iter {
iterations = step_idx + 1;
if norm(&g) < tol {
status = ConvergenceStatus::Converged;
break;
}
let alpha = line_search(obj, &x, &dir, &g);
x = step(&x, alpha, &dir);
let g_new = obj.grad(&x);
let denom = dot(&g, &g);
let beta = if step_idx % n == n - 1 || denom == 0.0 {
0.0
} else {
dot(&g_new, &g_new) / denom
};
dir = g_new
.iter()
.zip(&dir)
.map(|(gi, di)| beta.mul_add(*di, -gi))
.collect();
g = g_new;
}
let fx = obj.value(&x);
OptimizeResult {
x,
fx,
iterations,
status,
}
}