use crate::matrix::Matrix;
use num_rational::Rational64;
use num_traits::{One, Signed, Zero};
pub type Vector = Vec<Rational64>;
#[derive(Debug, Clone, PartialEq)]
pub enum IPResult {
Optimal {
x: Vector,
objective: Rational64,
},
Infeasible,
Unbounded,
Unknown,
}
pub struct InteriorPointSolver {
c: Vector,
a: Matrix,
b: Vector,
tolerance: Rational64,
max_iterations: usize,
}
impl InteriorPointSolver {
pub fn new(c: Vector, a: Matrix, b: Vector) -> Self {
Self {
c,
a,
b,
tolerance: Rational64::new(1, 1000000), max_iterations: 100,
}
}
pub fn set_tolerance(&mut self, tol: Rational64) {
self.tolerance = tol;
}
pub fn set_max_iterations(&mut self, max_iter: usize) {
self.max_iterations = max_iter;
}
#[allow(clippy::needless_range_loop)]
pub fn solve(&self) -> IPResult {
let m = self.a.nrows();
let n = self.a.ncols();
if self.b.len() != m {
return IPResult::Unknown;
}
if self.c.len() != n {
return IPResult::Unknown;
}
let mut x = vec![Rational64::one(); n];
let mut y = vec![Rational64::zero(); m];
let mut s = vec![Rational64::one(); n];
for iteration in 0..self.max_iterations {
let mut gap = Rational64::zero();
for i in 0..n {
gap += x[i] * s[i];
}
gap /= Rational64::from(n as i64);
if gap.abs() < self.tolerance {
let mut objective = Rational64::zero();
for i in 0..n {
objective += self.c[i] * x[i];
}
return IPResult::Optimal { x, objective };
}
if iteration > 50 && gap > Rational64::from(1000000) {
return IPResult::Unknown;
}
let mut r_primal = vec![Rational64::zero(); m];
for i in 0..m {
for j in 0..n {
r_primal[i] += self.a.get(i, j) * x[j];
}
r_primal[i] -= self.b[i];
}
let mut r_dual = vec![Rational64::zero(); n];
for j in 0..n {
for i in 0..m {
r_dual[j] += self.a.get(i, j) * y[i];
}
r_dual[j] += s[j];
r_dual[j] -= self.c[j];
}
let mut r_comp = vec![Rational64::zero(); n];
for i in 0..n {
r_comp[i] = x[i] * s[i] - gap;
}
let (dx, dy, ds) =
self.compute_newton_direction(&x, &y, &s, &r_primal, &r_dual, &r_comp);
let alpha_primal = self.step_length(&x, &dx, &Rational64::new(995, 1000));
let alpha_dual = self.step_length(&s, &ds, &Rational64::new(995, 1000));
for i in 0..n {
x[i] += alpha_primal * dx[i];
s[i] += alpha_dual * ds[i];
}
for i in 0..m {
y[i] += alpha_dual * dy[i];
}
}
IPResult::Unknown
}
#[allow(clippy::too_many_arguments)]
fn compute_newton_direction(
&self,
x: &[Rational64],
_y: &[Rational64],
s: &[Rational64],
r_primal: &[Rational64],
r_dual: &[Rational64],
r_comp: &[Rational64],
) -> (Vector, Vector, Vector) {
let n = x.len();
let m = r_primal.len();
let mut dx = vec![Rational64::zero(); n];
let mut dy = vec![Rational64::zero(); m];
let mut ds = vec![Rational64::zero(); n];
for i in 0..n {
if !s[i].is_zero() && !x[i].is_zero() {
let scale = x[i] / s[i];
dx[i] = -(r_dual[i] * scale + r_comp[i] / s[i]);
ds[i] = -(r_dual[i] + r_comp[i] / x[i]);
}
}
for i in 0..m {
if !r_primal[i].is_zero() {
dy[i] = -r_primal[i] / Rational64::from((n.max(1)) as i64);
}
}
(dx, dy, ds)
}
fn step_length(&self, x: &[Rational64], dx: &[Rational64], tau: &Rational64) -> Rational64 {
let mut alpha = Rational64::one();
for i in 0..x.len() {
if dx[i] < Rational64::zero() {
let max_step = -x[i] / dx[i];
if max_step < alpha {
alpha = max_step;
}
}
}
alpha * tau
}
}
pub fn log_barrier(x: &[Rational64], mu: &Rational64) -> Option<Rational64> {
let mut result = Rational64::zero();
for xi in x {
if xi <= &Rational64::zero() {
return None;
}
result -= mu / xi;
}
Some(result)
}
pub fn compute_mu(x: &[Rational64], s: &[Rational64]) -> Rational64 {
let n = x.len().max(1);
let mut sum = Rational64::zero();
for i in 0..x.len().min(s.len()) {
sum += x[i] * s[i];
}
sum / Rational64::from(n as i64)
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> Rational64 {
Rational64::from(n)
}
fn rat_frac(num: i64, den: i64) -> Rational64 {
Rational64::new(num, den)
}
#[test]
fn test_log_barrier() {
let x = vec![rat(1), rat(2), rat(3)];
let mu = rat(1);
let barrier = log_barrier(&x, &mu);
assert!(barrier.is_some());
}
#[test]
fn test_log_barrier_infeasible() {
let x = vec![rat(1), rat(-1), rat(3)];
let mu = rat(1);
let barrier = log_barrier(&x, &mu);
assert!(barrier.is_none());
}
#[test]
fn test_compute_mu() {
let x = vec![rat(2), rat(4)];
let s = vec![rat(3), rat(1)];
let mu = compute_mu(&x, &s);
assert_eq!(mu, rat(5));
}
#[test]
fn test_step_length() {
let c = vec![rat(-1), rat(-1)];
let a = Matrix::identity(2);
let b = vec![rat(1), rat(1)];
let solver = InteriorPointSolver::new(c, a, b);
let x = vec![rat(4), rat(2)];
let dx = vec![rat(-2), rat(-1)];
let tau = rat_frac(9, 10);
let alpha = solver.step_length(&x, &dx, &tau);
assert!(alpha <= rat(2));
assert!(alpha > Rational64::zero());
}
#[test]
fn test_simple_lp() {
let c = vec![rat(1), rat(1)];
let a = Matrix::from_rows(vec![vec![rat(1), rat(0)], vec![rat(0), rat(1)]]);
let b = vec![rat(1), rat(1)];
let mut solver = InteriorPointSolver::new(c, a, b);
solver.set_max_iterations(50);
let result = solver.solve();
match result {
IPResult::Optimal { x, objective } => {
assert_eq!(x.len(), 2);
assert!(objective >= rat(1));
assert!(objective <= rat(3));
}
_ => {
}
}
}
#[test]
fn test_interior_point_solver_creation() {
let c = vec![rat(1), rat(2)];
let a = Matrix::from_rows(vec![vec![rat(1), rat(1)]]);
let b = vec![rat(5)];
let solver = InteriorPointSolver::new(c, a, b);
assert_eq!(solver.c.len(), 2);
assert_eq!(solver.a.nrows(), 1);
assert_eq!(solver.a.ncols(), 2);
}
}