use std::collections::VecDeque;
use crate::optimizers::{
ConvergenceStatus, Objective, OptimizeResult, dot, line_search, norm, step,
};
const HISTORY: usize = 10;
#[must_use]
pub fn lbfgs(obj: &impl Objective, x0: &[f64], max_iter: usize, tol: f64) -> OptimizeResult {
let mut x = x0.to_vec();
let mut g = obj.grad(&x);
let mut s_hist: VecDeque<Vec<f64>> = VecDeque::new();
let mut y_hist: VecDeque<Vec<f64>> = VecDeque::new();
let mut rho_hist: VecDeque<f64> = VecDeque::new();
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 dir = two_loop(&g, &s_hist, &y_hist, &rho_hist);
let alpha = line_search(obj, &x, &dir, &g);
let x_new = step(&x, alpha, &dir);
let g_new = obj.grad(&x_new);
let s: Vec<f64> = x_new.iter().zip(&x).map(|(a, b)| a - b).collect();
let y: Vec<f64> = g_new.iter().zip(&g).map(|(a, b)| a - b).collect();
let sy = dot(&s, &y);
if sy > 1e-12 {
if s_hist.len() == HISTORY {
s_hist.pop_front();
y_hist.pop_front();
rho_hist.pop_front();
}
rho_hist.push_back(1.0 / sy);
s_hist.push_back(s);
y_hist.push_back(y);
}
x = x_new;
g = g_new;
}
let fx = obj.value(&x);
OptimizeResult {
x,
fx,
iterations,
status,
}
}
#[allow(clippy::many_single_char_names)]
fn two_loop(
g: &[f64],
s_hist: &VecDeque<Vec<f64>>,
y_hist: &VecDeque<Vec<f64>>,
rho_hist: &VecDeque<f64>,
) -> Vec<f64> {
let mut q = g.to_vec();
let k = s_hist.len();
let mut alpha = vec![0.0; k];
for i in (0..k).rev() {
let (Some(s), Some(y), Some(&rho)) = (s_hist.get(i), y_hist.get(i), rho_hist.get(i)) else {
continue;
};
let a = rho * dot(s, &q);
if let Some(slot) = alpha.get_mut(i) {
*slot = a;
}
for (qi, yi) in q.iter_mut().zip(y) {
*qi -= a * yi;
}
}
let gamma = match (s_hist.back(), y_hist.back()) {
(Some(s), Some(y)) => {
let yy = dot(y, y);
if yy > 0.0 { dot(s, y) / yy } else { 1.0 }
}
_ => 1.0,
};
for qi in &mut q {
*qi *= gamma;
}
for i in 0..k {
let (Some(s), Some(y), Some(&rho), Some(&ai)) =
(s_hist.get(i), y_hist.get(i), rho_hist.get(i), alpha.get(i))
else {
continue;
};
let beta = rho * dot(y, &q);
for (qi, si) in q.iter_mut().zip(s) {
*qi += (ai - beta) * si;
}
}
q.iter().map(|qi| -qi).collect()
}