use crate::core::augmented_lagrangian::AugmentedLagrangian;
use crate::core::constraint::LinearEqualityConstraints;
use crate::core::executor::run_loop;
use crate::core::inner::WarmStart;
use crate::core::math::{Dot, MatTransposeVec, MatVec, NormSquared, ScaleInPlace, ScaledAdd};
use crate::core::problem::{CostFunction, Gradient, Problem};
use crate::core::solver::Solver;
use crate::core::state::{BasicState, CountsMirror, GradientState, State};
use crate::core::termination::{
GradientTolerance, MaxIter, TerminationCriterion, TerminationReason,
};
pub struct AugmentedLagrangianMethod<So, V> {
inner_solver: So,
inner_max_iter: u64,
inner_grad_tol: f64,
rho0: f64,
rho: f64,
rho_increase: f64,
feasibility_decrease: f64,
tol: f64,
lambda: Option<V>,
c_norm: f64,
c_norm_prev: f64,
}
impl<So, V> AugmentedLagrangianMethod<So, V> {
pub fn new(inner_solver: So) -> Self {
Self {
inner_solver,
inner_max_iter: 50,
inner_grad_tol: 1e-8,
rho0: 10.0,
rho: 10.0,
rho_increase: 10.0,
feasibility_decrease: 0.25,
tol: 1e-8,
lambda: None,
c_norm: f64::INFINITY,
c_norm_prev: f64::INFINITY,
}
}
pub fn rho0(mut self, rho0: f64) -> Self {
assert!(rho0 > 0.0, "rho0 must be > 0");
self.rho0 = rho0;
self
}
pub fn rho_increase(mut self, rho_increase: f64) -> Self {
assert!(rho_increase > 1.0, "rho_increase must be > 1");
self.rho_increase = rho_increase;
self
}
pub fn feasibility_decrease(mut self, feasibility_decrease: f64) -> Self {
assert!(
feasibility_decrease > 0.0 && feasibility_decrease < 1.0,
"feasibility_decrease must be in (0, 1)"
);
self.feasibility_decrease = feasibility_decrease;
self
}
pub fn tol(mut self, tol: f64) -> Self {
assert!(tol > 0.0, "tol must be > 0");
self.tol = tol;
self
}
pub fn inner_max_iter(mut self, inner_max_iter: u64) -> Self {
assert!(inner_max_iter >= 1, "inner_max_iter must be ≥ 1");
self.inner_max_iter = inner_max_iter;
self
}
pub fn inner_grad_tol(mut self, inner_grad_tol: f64) -> Self {
assert!(inner_grad_tol >= 0.0, "inner_grad_tol must be ≥ 0");
self.inner_grad_tol = inner_grad_tol;
self
}
}
impl<P, V, M, So> Solver<P, BasicState<V>> for AugmentedLagrangianMethod<So, V>
where
P: CostFunction<Param = V, Output = f64>
+ Gradient<Gradient = V>
+ LinearEqualityConstraints<Param = V, Matrix = M>,
M: MatVec<V> + MatTransposeVec<V>,
V: ScaledAdd<f64> + Dot + NormSquared + ScaleInPlace + Clone,
So: WarmStart<V>
+ for<'a> Solver<AugmentedLagrangian<'a, P, V>, So::State, Error = <P as CostFunction>::Error>,
So::State: GradientState<Param = V> + CountsMirror,
{
type Error = <P as CostFunction>::Error;
fn init(
&mut self,
problem: &mut Problem<P>,
mut state: BasicState<V>,
) -> Result<BasicState<V>, Self::Error> {
self.rho = self.rho0;
self.c_norm = f64::INFINITY;
self.c_norm_prev = f64::INFINITY;
let mut lambda = problem.inner().b().clone();
lambda.scale_in_place(0.0);
self.lambda = Some(lambda);
let (cost, grad) = problem.cost_and_gradient(state.param())?;
state.cost = Some(cost);
state.gradient = Some(grad);
Ok(state)
}
fn next_iter(
&mut self,
problem: &mut Problem<P>,
mut state: BasicState<V>,
) -> Result<(BasicState<V>, Option<TerminationReason>), Self::Error> {
let lambda = self.lambda.as_ref().expect("init populates lambda");
let mut al_wrapper =
Problem::new(AugmentedLagrangian::new(problem.inner(), lambda, self.rho));
let mut criteria: Vec<Box<dyn TerminationCriterion<So::State>>> = vec![
Box::new(MaxIter(self.inner_max_iter)),
Box::new(GradientTolerance(self.inner_grad_tol)),
];
let inner_state = self.inner_solver.seed(state.param());
let result = run_loop(
&mut al_wrapper,
inner_state,
&mut self.inner_solver,
&mut criteria,
self.inner_max_iter,
)?;
let inner_counts = *al_wrapper.counts();
problem.counts_mut().add(&inner_counts);
if result.reason.is_failure() {
return Ok((state, Some(TerminationReason::SolverFailed)));
}
state.param = result.state.param().clone();
let (cost, grad) = problem.cost_and_gradient(&state.param)?;
state.cost = Some(cost);
state.gradient = Some(grad);
let mut c = problem.inner().a().matvec(&state.param);
c.scaled_add(-1.0, problem.inner().b());
self.c_norm_prev = self.c_norm;
self.c_norm = c.norm_squared().sqrt();
if self.c_norm <= self.feasibility_decrease * self.c_norm_prev {
let lambda = self.lambda.as_mut().expect("init populates lambda");
lambda.scaled_add(self.rho, &c);
} else {
self.rho *= self.rho_increase;
}
Ok((state, None))
}
fn terminate(&self, _state: &BasicState<V>) -> Option<TerminationReason> {
if self.c_norm <= self.tol {
Some(TerminationReason::SolverConverged)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type Builder = AugmentedLagrangianMethod<(), Vec<f64>>;
#[test]
#[should_panic(expected = "rho0 must be > 0")]
fn rejects_nonpositive_rho0() {
let _ = Builder::new(()).rho0(0.0);
}
#[test]
#[should_panic(expected = "rho_increase must be > 1")]
fn rejects_rho_increase_not_greater_than_one() {
let _ = Builder::new(()).rho_increase(1.0);
}
#[test]
#[should_panic(expected = "feasibility_decrease must be in (0, 1)")]
fn rejects_feasibility_decrease_out_of_range() {
let _ = Builder::new(()).feasibility_decrease(1.0);
}
#[test]
#[should_panic(expected = "tol must be > 0")]
fn rejects_nonpositive_tol() {
let _ = Builder::new(()).tol(0.0);
}
#[test]
#[should_panic(expected = "inner_max_iter must be ≥ 1")]
fn rejects_zero_inner_max_iter() {
let _ = Builder::new(()).inner_max_iter(0);
}
#[test]
#[should_panic(expected = "inner_grad_tol must be ≥ 0")]
fn rejects_negative_inner_grad_tol() {
let _ = Builder::new(()).inner_grad_tol(-1.0);
}
}