1use crate::matrix::Matrix;
16#[allow(unused_imports)]
17use crate::prelude::*;
18use num_rational::Rational64;
19use num_traits::{One, Signed, Zero};
20
21pub type Vector = Vec<Rational64>;
23
24#[derive(Debug, Clone, PartialEq)]
26pub enum IPResult {
27 Optimal {
29 x: Vector,
31 objective: Rational64,
33 },
34 Infeasible,
36 Unbounded,
38 Unknown,
40}
41
42pub struct InteriorPointSolver {
49 c: Vector,
51 a: Matrix,
53 b: Vector,
55 tolerance: Rational64,
57 max_iterations: usize,
59}
60
61impl InteriorPointSolver {
62 pub fn new(c: Vector, a: Matrix, b: Vector) -> Self {
69 Self {
70 c,
71 a,
72 b,
73 tolerance: Rational64::new(1, 1000000), max_iterations: 100,
75 }
76 }
77
78 pub fn set_tolerance(&mut self, tol: Rational64) {
80 self.tolerance = tol;
81 }
82
83 pub fn set_max_iterations(&mut self, max_iter: usize) {
85 self.max_iterations = max_iter;
86 }
87
88 #[allow(clippy::needless_range_loop)]
92 pub fn solve(&self) -> IPResult {
93 let m = self.a.nrows();
94 let n = self.a.ncols();
95
96 if self.b.len() != m {
97 return IPResult::Unknown;
98 }
99 if self.c.len() != n {
100 return IPResult::Unknown;
101 }
102
103 let mut x = vec![Rational64::one(); n];
108 let mut y = vec![Rational64::zero(); m];
109 let mut s = vec![Rational64::one(); n];
110
111 for iteration in 0..self.max_iterations {
113 let mut gap = Rational64::zero();
115 for i in 0..n {
116 gap += x[i] * s[i];
117 }
118 gap /= Rational64::from(n as i64);
119
120 if gap.abs() < self.tolerance {
122 let mut objective = Rational64::zero();
124 for i in 0..n {
125 objective += self.c[i] * x[i];
126 }
127 return IPResult::Optimal { x, objective };
128 }
129
130 if iteration > 50 && gap > Rational64::from(1000000) {
132 return IPResult::Unknown;
133 }
134
135 let mut r_primal = vec![Rational64::zero(); m];
138 for i in 0..m {
139 for j in 0..n {
140 r_primal[i] += self.a.get(i, j) * x[j];
141 }
142 r_primal[i] -= self.b[i];
143 }
144
145 let mut r_dual = vec![Rational64::zero(); n];
147 for j in 0..n {
148 for i in 0..m {
149 r_dual[j] += self.a.get(i, j) * y[i];
150 }
151 r_dual[j] += s[j];
152 r_dual[j] -= self.c[j];
153 }
154
155 let mut r_comp = vec![Rational64::zero(); n];
158 for i in 0..n {
159 r_comp[i] = x[i] * s[i] - gap;
160 }
161
162 let (dx, dy, ds) =
166 self.compute_newton_direction(&x, &y, &s, &r_primal, &r_dual, &r_comp);
167
168 let alpha_primal = self.step_length(&x, &dx, &Rational64::new(995, 1000));
170 let alpha_dual = self.step_length(&s, &ds, &Rational64::new(995, 1000));
171
172 for i in 0..n {
174 x[i] += alpha_primal * dx[i];
175 s[i] += alpha_dual * ds[i];
176 }
177 for i in 0..m {
178 y[i] += alpha_dual * dy[i];
179 }
180 }
181
182 IPResult::Unknown
184 }
185
186 #[allow(clippy::too_many_arguments)]
191 fn compute_newton_direction(
192 &self,
193 x: &[Rational64],
194 _y: &[Rational64],
195 s: &[Rational64],
196 r_primal: &[Rational64],
197 r_dual: &[Rational64],
198 r_comp: &[Rational64],
199 ) -> (Vector, Vector, Vector) {
200 let n = x.len();
201 let m = r_primal.len();
202
203 let mut dx = vec![Rational64::zero(); n];
206 let mut dy = vec![Rational64::zero(); m];
207 let mut ds = vec![Rational64::zero(); n];
208
209 for i in 0..n {
211 if !s[i].is_zero() && !x[i].is_zero() {
212 let scale = x[i] / s[i];
213 dx[i] = -(r_dual[i] * scale + r_comp[i] / s[i]);
214 ds[i] = -(r_dual[i] + r_comp[i] / x[i]);
215 }
216 }
217
218 for i in 0..m {
220 if !r_primal[i].is_zero() {
221 dy[i] = -r_primal[i] / Rational64::from((n.max(1)) as i64);
222 }
223 }
224
225 (dx, dy, ds)
226 }
227
228 fn step_length(&self, x: &[Rational64], dx: &[Rational64], tau: &Rational64) -> Rational64 {
232 let mut alpha = Rational64::one();
233
234 for i in 0..x.len() {
235 if dx[i] < Rational64::zero() {
236 let max_step = -x[i] / dx[i];
237 if max_step < alpha {
238 alpha = max_step;
239 }
240 }
241 }
242
243 alpha * tau
245 }
246}
247
248pub fn log_barrier(x: &[Rational64], mu: &Rational64) -> Option<Rational64> {
253 let mut result = Rational64::zero();
254
255 for xi in x {
256 if xi <= &Rational64::zero() {
257 return None;
259 }
260 result -= mu / xi;
263 }
264
265 Some(result)
266}
267
268pub fn compute_mu(x: &[Rational64], s: &[Rational64]) -> Rational64 {
272 let n = x.len().max(1);
273 let mut sum = Rational64::zero();
274
275 for i in 0..x.len().min(s.len()) {
276 sum += x[i] * s[i];
277 }
278
279 sum / Rational64::from(n as i64)
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 fn rat(n: i64) -> Rational64 {
287 Rational64::from(n)
288 }
289
290 fn rat_frac(num: i64, den: i64) -> Rational64 {
291 Rational64::new(num, den)
292 }
293
294 #[test]
295 fn test_log_barrier() {
296 let x = vec![rat(1), rat(2), rat(3)];
298 let mu = rat(1);
299 let barrier = log_barrier(&x, &mu);
300 assert!(barrier.is_some());
301 }
302
303 #[test]
304 fn test_log_barrier_infeasible() {
305 let x = vec![rat(1), rat(-1), rat(3)];
307 let mu = rat(1);
308 let barrier = log_barrier(&x, &mu);
309 assert!(barrier.is_none());
310 }
311
312 #[test]
313 fn test_compute_mu() {
314 let x = vec![rat(2), rat(4)];
315 let s = vec![rat(3), rat(1)];
316 let mu = compute_mu(&x, &s);
317
318 assert_eq!(mu, rat(5));
320 }
321
322 #[test]
323 fn test_step_length() {
324 let c = vec![rat(-1), rat(-1)];
325 let a = Matrix::identity(2);
326 let b = vec![rat(1), rat(1)];
327 let solver = InteriorPointSolver::new(c, a, b);
328
329 let x = vec![rat(4), rat(2)];
330 let dx = vec![rat(-2), rat(-1)];
331 let tau = rat_frac(9, 10);
332
333 let alpha = solver.step_length(&x, &dx, &tau);
334
335 assert!(alpha <= rat(2));
339 assert!(alpha > Rational64::zero());
340 }
341
342 #[test]
343 fn test_simple_lp() {
344 let c = vec![rat(1), rat(1)];
349 let a = Matrix::from_rows(vec![vec![rat(1), rat(0)], vec![rat(0), rat(1)]]);
350 let b = vec![rat(1), rat(1)];
351
352 let mut solver = InteriorPointSolver::new(c, a, b);
353 solver.set_max_iterations(50);
354
355 let result = solver.solve();
356
357 match result {
359 IPResult::Optimal { x, objective } => {
360 assert_eq!(x.len(), 2);
361 assert!(objective >= rat(1));
363 assert!(objective <= rat(3));
364 }
365 _ => {
366 }
369 }
370 }
371
372 #[test]
373 fn test_interior_point_solver_creation() {
374 let c = vec![rat(1), rat(2)];
375 let a = Matrix::from_rows(vec![vec![rat(1), rat(1)]]);
376 let b = vec![rat(5)];
377
378 let solver = InteriorPointSolver::new(c, a, b);
379 assert_eq!(solver.c.len(), 2);
380 assert_eq!(solver.a.nrows(), 1);
381 assert_eq!(solver.a.ncols(), 2);
382 }
383}