use crate::core::barrier::{LogBarrier, strict_feasibility};
use crate::core::constraint::LinearInequalityConstraints;
use crate::core::executor::run_loop;
use crate::core::inner::{InitialState, WarmStart};
use crate::core::math::{
MatTransposeVec, MatVec, NegInPlace, NormSquared, Scalar, ScaledAdd,
VectorIndex, VectorLen,
};
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 BarrierMethod<So, F = f64> {
inner_solver: So,
inner_max_iter: u64,
inner_grad_tol: F,
mu0: F,
mu: F,
reduction: F,
tol: F,
phase_one_tol: F,
gap: F,
phase: BarrierPhase,
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum BarrierPhase {
PhaseOne,
PhaseTwo,
Failed,
}
struct StopAtStrictFeasibility<'a, So> {
inner: &'a mut So,
}
impl<'a, 'p, P, V, M, S, So, F> Solver<LogBarrier<'p, P, F>, S>
for StopAtStrictFeasibility<'a, So>
where
F: Scalar,
P: CostFunction<Param = V, Output = F>
+ LinearInequalityConstraints<Param = V, Matrix = M>,
M: MatVec<V>,
V: ScaledAdd<F> + VectorIndex<F> + VectorLen,
S: State<Param = V>,
So: Solver<LogBarrier<'p, P, F>, S>,
{
type Error = So::Error;
fn init(
&mut self,
problem: &mut Problem<LogBarrier<'p, P, F>>,
state: S,
) -> Result<S, Self::Error> {
self.inner.init(problem, state)
}
fn next_iter(
&mut self,
problem: &mut Problem<LogBarrier<'p, P, F>>,
state: S,
) -> Result<(S, Option<TerminationReason>), Self::Error> {
let (state, reason) = self.inner.next_iter(problem, state)?;
let inner_failed = reason.is_some_and(|reason| reason.is_failure());
if !inner_failed
&& problem.inner().strict_feasibility(state.param()) == Some(true)
{
Ok((state, Some(TerminationReason::SolverConverged)))
} else {
Ok((state, reason))
}
}
fn terminate(&self, state: &S) -> Option<TerminationReason> {
self.inner.terminate(state)
}
}
impl<So> BarrierMethod<So> {
pub fn new(inner_solver: So) -> Self {
Self {
inner_solver,
inner_max_iter: 50,
inner_grad_tol: 1e-8,
mu0: 1.0,
mu: 1.0,
reduction: 10.0,
tol: 1e-8,
phase_one_tol: 1e-8,
gap: f64::INFINITY,
phase: BarrierPhase::PhaseTwo,
}
}
}
impl<So, F: Scalar> BarrierMethod<So, F> {
pub fn mu0(mut self, mu0: F) -> Self {
assert!(mu0 > F::zero(), "mu0 must be > 0");
self.mu0 = mu0;
self
}
pub fn with_reduction(mut self, reduction: F) -> Self {
assert!(reduction > F::one(), "reduction must be > 1");
self.reduction = reduction;
self
}
pub fn with_tol(mut self, tol: F) -> Self {
assert!(tol > F::zero(), "tol must be > 0");
self.tol = tol;
self
}
pub fn with_phase_one_tol(mut self, phase_one_tol: F) -> Self {
assert!(phase_one_tol > F::zero(), "phase_one_tol must be > 0");
self.phase_one_tol = phase_one_tol;
self
}
pub fn with_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 with_inner_grad_tol(mut self, inner_grad_tol: F) -> Self {
assert!(inner_grad_tol >= F::zero(), "inner_grad_tol must be ≥ 0");
self.inner_grad_tol = inner_grad_tol;
self
}
}
impl<So, V, F> InitialState<V> for BarrierMethod<So, F>
where
F: Scalar,
V: Clone,
{
type State = BasicState<V, F>;
fn seed(&self, x: &V) -> Self::State {
BasicState::new(x.clone())
}
}
impl<P, V, M, So, F> Solver<P, BasicState<V, F>> for BarrierMethod<So, F>
where
F: Scalar,
P: CostFunction<Param = V, Output = F>
+ Gradient<Gradient = V>
+ LinearInequalityConstraints<Param = V, Matrix = M>,
M: MatVec<V> + MatTransposeVec<V>,
V: ScaledAdd<F>
+ NegInPlace
+ VectorIndex<F>
+ VectorLen
+ NormSquared<F>
+ Clone,
So: WarmStart<V>
+ for<'a> Solver<
LogBarrier<'a, P, F>,
So::State,
Error = <P as CostFunction>::Error,
>,
So::State: GradientState<Param = V, Float = F> + CountsMirror,
{
type Error = <P as CostFunction>::Error;
fn init(
&mut self,
problem: &mut Problem<P>,
mut state: BasicState<V, F>,
) -> Result<BasicState<V, F>, Self::Error> {
self.mu = self.mu0;
self.gap = F::infinity();
self.phase = match strict_feasibility(problem.inner(), state.param()) {
Some(true) => BarrierPhase::PhaseTwo,
Some(false) => BarrierPhase::PhaseOne,
None => BarrierPhase::Failed,
};
if self.phase == BarrierPhase::Failed {
state.cost = Some(F::infinity());
state.gradient = None;
return Ok(state);
}
if self.phase == BarrierPhase::PhaseOne {
state.cost = Some(F::infinity());
state.gradient = None;
return Ok(state);
}
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, F>,
) -> Result<(BasicState<V, F>, Option<TerminationReason>), Self::Error>
{
if self.phase == BarrierPhase::Failed {
return Ok((state, Some(TerminationReason::SolverFailed)));
}
if self.phase == BarrierPhase::PhaseOne {
let mut barrier_wrapper =
Problem::new(LogBarrier::phase_one(problem.inner(), self.mu));
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 mut phase_one_solver = StopAtStrictFeasibility {
inner: &mut self.inner_solver,
};
let result = run_loop(
&mut barrier_wrapper,
inner_state,
&mut phase_one_solver,
&mut criteria,
self.inner_max_iter,
)?;
let inner_counts = *barrier_wrapper.counts();
problem.counts_mut().add(&inner_counts);
if result.reason.is_failure() {
self.phase = BarrierPhase::Failed;
return Ok((state, Some(TerminationReason::SolverFailed)));
}
let centered = result.state.gradient().is_some_and(|gradient| {
gradient.norm_squared()
<= self.inner_grad_tol * self.inner_grad_tol
});
let candidate = result.state.param();
let feasibility = strict_feasibility(problem.inner(), candidate);
let Some(is_strictly_feasible) = feasibility else {
self.phase = BarrierPhase::Failed;
return Ok((state, Some(TerminationReason::SolverFailed)));
};
state.param = candidate.clone();
if is_strictly_feasible {
let (cost, grad) = problem.cost_and_gradient(&state.param)?;
state.cost = Some(cost);
state.gradient = Some(grad);
self.phase = BarrierPhase::PhaseTwo;
self.mu = self.mu0;
self.gap = F::infinity();
return Ok((state, None));
}
if centered {
let phase_one_gap =
F::from_usize(problem.inner().b().vec_len()).unwrap()
* self.mu;
if phase_one_gap <= self.phase_one_tol {
self.phase = BarrierPhase::Failed;
return Ok((state, Some(TerminationReason::SolverFailed)));
}
self.mu = self.mu / self.reduction;
}
state.cost = Some(F::infinity());
state.gradient = None;
return Ok((state, None));
}
let mut barrier_wrapper =
Problem::new(LogBarrier::new(problem.inner(), self.mu));
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 barrier_wrapper,
inner_state,
&mut self.inner_solver,
&mut criteria,
self.inner_max_iter,
)?;
let inner_counts = *barrier_wrapper.counts();
problem.counts_mut().add(&inner_counts);
if result.reason.is_failure() {
self.phase = BarrierPhase::Failed;
return Ok((state, Some(TerminationReason::SolverFailed)));
}
let candidate = result.state.param();
if strict_feasibility(problem.inner(), candidate) != Some(true) {
self.phase = BarrierPhase::Failed;
return Ok((state, Some(TerminationReason::SolverFailed)));
}
state.param = candidate.clone();
let (cost, grad) = problem.cost_and_gradient(&state.param)?;
state.cost = Some(cost);
state.gradient = Some(grad);
self.gap =
F::from_usize(problem.inner().b().vec_len()).unwrap() * self.mu;
self.mu = self.mu / self.reduction;
Ok((state, None))
}
fn terminate(
&self,
_state: &BasicState<V, F>,
) -> Option<TerminationReason> {
if self.phase == BarrierPhase::PhaseTwo && self.gap <= self.tol {
Some(TerminationReason::SolverConverged)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "mu0 must be > 0")]
fn rejects_nonpositive_mu0() {
let _ = BarrierMethod::new(()).mu0(0.0);
}
#[test]
#[should_panic(expected = "reduction must be > 1")]
fn rejects_reduction_not_greater_than_one() {
let _ = BarrierMethod::new(()).with_reduction(1.0);
}
#[test]
#[should_panic(expected = "tol must be > 0")]
fn rejects_nonpositive_tol() {
let _ = BarrierMethod::new(()).with_tol(0.0);
}
#[test]
#[should_panic(expected = "phase_one_tol must be > 0")]
fn rejects_nonpositive_phase_one_tol() {
let _ = BarrierMethod::new(()).with_phase_one_tol(0.0);
}
#[test]
#[should_panic(expected = "inner_max_iter must be ≥ 1")]
fn rejects_zero_inner_max_iter() {
let _ = BarrierMethod::new(()).with_inner_max_iter(0);
}
#[test]
#[should_panic(expected = "inner_grad_tol must be ≥ 0")]
fn rejects_negative_inner_grad_tol() {
let _ = BarrierMethod::new(()).with_inner_grad_tol(-1.0);
}
}