use super::{ConvergenceStatus, OptimizationResult, Optimizer};
use crate::primitives::Vector;
#[derive(Debug, Clone)]
pub struct AugmentedLagrangian {
max_iter: usize,
tol: f32,
initial_rho: f32,
rho: f32,
rho_increase: f32,
rho_max: f32,
}
impl AugmentedLagrangian {
#[must_use]
pub fn new(max_iter: usize, tol: f32, initial_rho: f32) -> Self {
Self {
max_iter,
tol,
initial_rho,
rho: initial_rho,
rho_increase: 2.0,
rho_max: 1e6,
}
}
#[must_use]
pub fn with_rho_increase(mut self, factor: f32) -> Self {
self.rho_increase = factor;
self
}
pub fn minimize_equality<F, G, H, J>(
&mut self,
objective: F,
gradient: G,
equality: H,
equality_jac: J,
x0: Vector<f32>,
) -> OptimizationResult
where
F: Fn(&Vector<f32>) -> f32,
G: Fn(&Vector<f32>) -> Vector<f32>,
H: Fn(&Vector<f32>) -> Vector<f32>,
J: Fn(&Vector<f32>) -> Vec<Vector<f32>>,
{
let start_time = std::time::Instant::now();
let mut x = x0;
self.rho = self.initial_rho;
let h0 = equality(&x);
let m = h0.len(); let mut lambda = Vector::zeros(m);
for outer_iter in 0..self.max_iter {
let aug_grad = |x_inner: &Vector<f32>| {
let grad_f = gradient(x_inner);
let h_val = equality(x_inner);
let jac_h = equality_jac(x_inner);
let n = x_inner.len();
let mut aug_g = Vector::zeros(n);
for i in 0..n {
aug_g[i] = grad_f[i];
}
for j in 0..m {
let coeff = lambda[j] + self.rho * h_val[j];
for i in 0..n {
aug_g[i] += coeff * jac_h[j][i];
}
}
aug_g
};
let mut x_sub = x.clone();
let alpha = 0.01; for _sub_iter in 0..50 {
let grad = aug_grad(&x_sub);
let mut grad_norm_sq = 0.0;
for i in 0..grad.len() {
grad_norm_sq += grad[i] * grad[i];
}
if grad_norm_sq < 1e-8 {
break; }
for i in 0..x_sub.len() {
x_sub[i] -= alpha * grad[i];
}
}
x = x_sub;
let h_val = equality(&x);
for i in 0..m {
lambda[i] += self.rho * h_val[i];
}
let mut constraint_viol = 0.0;
for i in 0..m {
constraint_viol += h_val[i] * h_val[i];
}
constraint_viol = constraint_viol.sqrt();
if constraint_viol < self.tol {
let final_obj = objective(&x);
let grad_f = gradient(&x);
let mut grad_norm = 0.0;
for i in 0..grad_f.len() {
grad_norm += grad_f[i] * grad_f[i];
}
grad_norm = grad_norm.sqrt();
return OptimizationResult {
solution: x,
objective_value: final_obj,
iterations: outer_iter + 1,
status: ConvergenceStatus::Converged,
gradient_norm: grad_norm,
constraint_violation: constraint_viol,
elapsed_time: start_time.elapsed(),
};
}
if constraint_viol > 0.1 * self.tol && self.rho < self.rho_max {
self.rho *= self.rho_increase;
}
}
let final_obj = objective(&x);
let h_val = equality(&x);
let mut constraint_viol = 0.0;
for i in 0..h_val.len() {
constraint_viol += h_val[i] * h_val[i];
}
constraint_viol = constraint_viol.sqrt();
let grad_f = gradient(&x);
let mut grad_norm = 0.0;
for i in 0..grad_f.len() {
grad_norm += grad_f[i] * grad_f[i];
}
grad_norm = grad_norm.sqrt();
OptimizationResult {
solution: x,
objective_value: final_obj,
iterations: self.max_iter,
status: ConvergenceStatus::MaxIterations,
gradient_norm: grad_norm,
constraint_violation: constraint_viol,
elapsed_time: start_time.elapsed(),
}
}
}
impl Optimizer for AugmentedLagrangian {
fn step(&mut self, _params: &mut Vector<f32>, _gradients: &Vector<f32>) {
panic!(
"Augmented Lagrangian does not support stochastic updates (step). Use minimize_equality() for constrained optimization."
)
}
fn reset(&mut self) {
self.rho = self.initial_rho;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_al_new() {
let al = AugmentedLagrangian::new(100, 1e-6, 1.0);
let debug_str = format!("{:?}", al);
assert!(debug_str.contains("AugmentedLagrangian"));
}
#[test]
fn test_al_clone_debug() {
let al = AugmentedLagrangian::new(100, 1e-6, 1.0);
let cloned = al.clone();
let d1 = format!("{:?}", al);
let d2 = format!("{:?}", cloned);
assert_eq!(d1, d2);
}
#[test]
fn test_al_with_rho_increase() {
let al = AugmentedLagrangian::new(100, 1e-6, 1.0).with_rho_increase(5.0);
let debug_str = format!("{:?}", al);
assert!(debug_str.contains("5.0"));
}
#[test]
fn test_al_equality_constraint() {
let objective = |x: &Vector<f32>| 0.5 * (x[0] - 2.0).powi(2) + 0.5 * (x[1] - 3.0).powi(2);
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0] - 2.0, x[1] - 3.0]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0])];
let mut al = AugmentedLagrangian::new(200, 1e-4, 1.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 0.1);
}
#[test]
fn test_al_max_iterations() {
let objective = |x: &Vector<f32>| x[0] * x[0];
let gradient = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] - 100.0]); let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0])];
let mut al = AugmentedLagrangian::new(2, 1e-20, 0.1);
let x0 = Vector::from_slice(&[0.0]);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert_eq!(result.status, ConvergenceStatus::MaxIterations);
assert_eq!(result.iterations, 2);
assert!(result.objective_value.is_finite());
assert!(result.gradient_norm >= 0.0);
assert!(result.constraint_violation >= 0.0);
}
#[test]
fn test_al_rho_increase_triggers() {
let objective = |x: &Vector<f32>| (x[0] - 10.0).powi(2) + (x[1] - 10.0).powi(2);
let gradient =
|x: &Vector<f32>| Vector::from_slice(&[2.0 * (x[0] - 10.0), 2.0 * (x[1] - 10.0)]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] + x[1] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0, 1.0])];
let mut al = AugmentedLagrangian::new(100, 1e-4, 0.01).with_rho_increase(10.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(
result.status == ConvergenceStatus::Converged
|| result.status == ConvergenceStatus::MaxIterations
);
}
#[test]
fn test_al_reset() {
let mut al = AugmentedLagrangian::new(100, 1e-6, 5.0);
let objective = |x: &Vector<f32>| x[0] * x[0];
let gradient = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0])];
let _ = al.minimize_equality(
objective,
gradient,
equality,
equality_jac,
Vector::zeros(1),
);
al.reset();
let debug_str = format!("{:?}", al);
assert!(debug_str.contains("rho: 5.0"));
}
#[test]
#[should_panic(expected = "does not support stochastic updates")]
fn test_al_step_panics() {
let mut al = AugmentedLagrangian::new(100, 1e-6, 1.0);
let mut params = Vector::from_slice(&[1.0]);
let grad = Vector::from_slice(&[0.1]);
al.step(&mut params, &grad);
}
#[test]
fn test_al_subproblem_convergence() {
let objective = |x: &Vector<f32>| 0.5 * x[0] * x[0];
let gradient = |x: &Vector<f32>| Vector::from_slice(&[x[0]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0])];
let mut al = AugmentedLagrangian::new(100, 1e-4, 10.0);
let x0 = Vector::from_slice(&[0.0]);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!((result.solution[0] - 1.0).abs() < 0.5);
}
#[test]
fn test_al_multiple_constraints() {
let objective = |x: &Vector<f32>| x[0] * x[0] + x[1] * x[1];
let gradient = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0], 2.0 * x[1]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] - x[1], x[0] + x[1] - 2.0]);
let equality_jac = |_x: &Vector<f32>| {
vec![
Vector::from_slice(&[1.0, -1.0]),
Vector::from_slice(&[1.0, 1.0]),
]
};
let mut al = AugmentedLagrangian::new(200, 1e-3, 1.0);
let x0 = Vector::zeros(2);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
assert!(result.constraint_violation < 0.5);
}
#[test]
fn test_al_elapsed_time() {
let objective = |x: &Vector<f32>| x[0] * x[0];
let gradient = |x: &Vector<f32>| Vector::from_slice(&[2.0 * x[0]]);
let equality = |x: &Vector<f32>| Vector::from_slice(&[x[0] - 1.0]);
let equality_jac = |_x: &Vector<f32>| vec![Vector::from_slice(&[1.0])];
let mut al = AugmentedLagrangian::new(50, 1e-4, 1.0);
let x0 = Vector::zeros(1);
let result = al.minimize_equality(objective, gradient, equality, equality_jac, x0);
let _ = result.elapsed_time.as_nanos();
}
}