use crate::core::constraint::LinearInequalityConstraints;
use crate::core::math::{MatTransposeVec, MatVec, NegInPlace, ScaledAdd, VectorIndex, VectorLen};
use crate::core::problem::{CostFunction, Gradient};
pub struct LogBarrier<'a, P> {
problem: &'a P,
mu: f64,
}
impl<'a, P> LogBarrier<'a, P> {
pub fn new(problem: &'a P, mu: f64) -> Self {
Self { problem, mu }
}
pub fn mu(&self) -> f64 {
self.mu
}
}
impl<P, V, M> CostFunction for LogBarrier<'_, P>
where
P: CostFunction<Param = V, Output = f64> + LinearInequalityConstraints<Param = V, Matrix = M>,
M: MatVec<V>,
V: ScaledAdd<f64> + NegInPlace + VectorIndex + VectorLen,
{
type Param = V;
type Output = f64;
type Error = <P as CostFunction>::Error;
fn cost(&self, x: &V) -> Result<f64, Self::Error> {
let mut s = self.problem.a().matvec(x);
s.neg_in_place();
s.scaled_add(1.0, self.problem.b());
let mut log_sum = 0.0;
for i in 0..s.vec_len() {
let si = s.get_scalar(i);
if si <= 0.0 {
return Ok(f64::INFINITY);
}
log_sum += si.ln();
}
Ok(self.problem.cost(x)? - self.mu * log_sum)
}
}
impl<P, V, M> Gradient for LogBarrier<'_, P>
where
P: CostFunction<Param = V, Output = f64>
+ Gradient<Gradient = V>
+ LinearInequalityConstraints<Param = V, Matrix = M>,
M: MatVec<V> + MatTransposeVec<V>,
V: ScaledAdd<f64> + NegInPlace + VectorIndex + VectorLen,
{
type Gradient = V;
fn gradient(&self, x: &V) -> Result<V, <Self as CostFunction>::Error> {
let mut s = self.problem.a().matvec(x);
s.neg_in_place();
s.scaled_add(1.0, self.problem.b());
for i in 0..s.vec_len() {
let si = s.get_scalar(i);
s.set_scalar(i, self.mu / si);
}
let mut g = self.problem.gradient(x)?;
let barrier_grad = self.problem.a().mat_transpose_vec(&s);
g.scaled_add(1.0, &barrier_grad);
Ok(g)
}
}
#[cfg(all(test, feature = "nalgebra"))]
mod tests {
use super::*;
use nalgebra::{DMatrix, DVector};
struct Probe {
a: DMatrix<f64>,
b: DVector<f64>,
}
impl Probe {
fn new() -> Self {
Self {
a: DMatrix::from_row_slice(1, 2, &[1.0, 1.0]),
b: DVector::from_vec(vec![2.0]),
}
}
}
impl CostFunction for Probe {
type Param = DVector<f64>;
type Output = f64;
type Error = std::convert::Infallible;
fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
Ok(0.5 * x.dot(x))
}
}
impl Gradient for Probe {
type Gradient = DVector<f64>;
fn gradient(&self, x: &DVector<f64>) -> Result<DVector<f64>, std::convert::Infallible> {
Ok(x.clone())
}
}
impl LinearInequalityConstraints for Probe {
type Matrix = DMatrix<f64>;
fn a(&self) -> &DMatrix<f64> {
&self.a
}
fn b(&self) -> &DVector<f64> {
&self.b
}
}
#[test]
fn cost_matches_closed_form_at_feasible_point() {
let p = Probe::new();
let mu = 0.5;
let lb = LogBarrier::new(&p, mu);
let x = DVector::from_vec(vec![0.0, 0.0]);
let expected = -mu * 2.0_f64.ln();
assert!((lb.cost(&x).unwrap() - expected).abs() < 1e-12);
}
#[test]
fn cost_is_infinite_outside_the_feasible_set() {
let p = Probe::new();
let lb = LogBarrier::new(&p, 1.0);
let x = DVector::from_vec(vec![2.0, 1.0]);
assert!(lb.cost(&x).unwrap().is_infinite());
}
#[test]
fn gradient_agrees_with_finite_differences() {
let p = Probe::new();
let lb = LogBarrier::new(&p, 0.7);
let x = DVector::from_vec(vec![0.3, -0.4]);
let analytic = lb.gradient(&x).unwrap();
let h = 1e-6;
for j in 0..2 {
let mut xp = x.clone();
let mut xm = x.clone();
xp[j] += h;
xm[j] -= h;
let fd = (lb.cost(&xp).unwrap() - lb.cost(&xm).unwrap()) / (2.0 * h);
assert!(
(analytic[j] - fd).abs() < 1e-5,
"component {j}: analytic {} vs fd {}",
analytic[j],
fd
);
}
}
}