1use crate::error::OptimError;
13use crate::types::{IterationRecord, OptimResult, OptimStatus};
14use numra_core::Scalar;
15use numra_linalg::{DenseMatrix, Matrix};
16
17#[derive(Clone, Debug)]
19pub struct LPOptions<S: Scalar> {
20 pub max_iter: usize,
21 pub tol: S,
22 pub verbose: bool,
23}
24
25impl<S: Scalar> Default for LPOptions<S> {
26 fn default() -> Self {
27 Self {
28 max_iter: 10_000,
29 tol: S::from_f64(1e-10),
30 verbose: false,
31 }
32 }
33}
34
35pub fn simplex_solve<
51 S: Scalar + faer::SimpleEntity + faer::Conjugate<Canonical = S> + faer::ComplexField,
52>(
53 c: &[S],
54 a_ineq: &[Vec<S>],
55 b_ineq: &[S],
56 a_eq: &[Vec<S>],
57 b_eq: &[S],
58 opts: &LPOptions<S>,
59) -> Result<OptimResult<S>, OptimError> {
60 let start = std::time::Instant::now();
61 let n_orig = c.len();
62 let m_ineq = a_ineq.len();
63 let m_eq = a_eq.len();
64 let m = m_ineq + m_eq;
65
66 if m == 0 && n_orig > 0 {
67 if c.iter().any(|&ci| ci < -opts.tol) {
69 return Err(OptimError::Unbounded);
70 }
71 let x = vec![S::ZERO; n_orig];
72 return Ok(OptimResult::unconstrained(
73 x,
74 S::ZERO,
75 c.to_vec(),
76 0,
77 0,
78 0,
79 true,
80 "Optimal at origin (no constraints)".into(),
81 OptimStatus::FunctionConverged,
82 )
83 .with_wall_time(start));
84 }
85
86 let n_slack = m_ineq;
88 let n_total = n_orig + n_slack;
89
90 let mut a_rows: Vec<Vec<S>> = Vec::with_capacity(m);
92 let mut b_std: Vec<S> = Vec::with_capacity(m);
93
94 for i in 0..m_eq {
96 if a_eq[i].len() != n_orig {
97 return Err(OptimError::DimensionMismatch {
98 expected: n_orig,
99 actual: a_eq[i].len(),
100 });
101 }
102 let mut row = a_eq[i].clone();
103 row.resize(n_total, S::ZERO);
104 let mut bi = b_eq[i];
105 if bi < S::ZERO {
106 for r in row.iter_mut() {
107 *r = -*r;
108 }
109 bi = -bi;
110 }
111 a_rows.push(row);
112 b_std.push(bi);
113 }
114
115 for i in 0..m_ineq {
117 if a_ineq[i].len() != n_orig {
118 return Err(OptimError::DimensionMismatch {
119 expected: n_orig,
120 actual: a_ineq[i].len(),
121 });
122 }
123 let mut row = a_ineq[i].clone();
124 row.resize(n_total, S::ZERO);
125 row[n_orig + i] = S::ONE;
126 let mut bi = b_ineq[i];
127 if bi < S::ZERO {
128 for r in row.iter_mut() {
129 *r = -*r;
130 }
131 bi = -bi;
132 }
133 a_rows.push(row);
134 b_std.push(bi);
135 }
136
137 let mut c_std = c.to_vec();
139 c_std.resize(n_total, S::ZERO);
140
141 let mut need_art = vec![false; m];
143 for item in need_art.iter_mut().take(m_eq) {
144 *item = true;
145 }
146 let has_artificials = need_art.iter().any(|&x| x);
147
148 let mut basis: Vec<usize> = Vec::with_capacity(m);
149
150 if has_artificials {
151 let n_phase1 = n_total + m;
153 let mut a_ph1: Vec<Vec<S>> = Vec::with_capacity(m);
154 for (i, row) in a_rows.iter().enumerate() {
155 let mut ph1_row = row.clone();
156 ph1_row.resize(n_phase1, S::ZERO);
157 if need_art[i] {
158 ph1_row[n_total + i] = S::ONE;
159 }
160 a_ph1.push(ph1_row);
161 }
162
163 let mut c_ph1 = vec![S::ZERO; n_phase1];
164 for i in 0..m {
165 if need_art[i] {
166 c_ph1[n_total + i] = S::ONE;
167 }
168 }
169
170 let mut basis_ph1 = Vec::with_capacity(m);
171 for i in 0..m_eq {
172 basis_ph1.push(n_total + i); }
174 for i in 0..m_ineq {
175 basis_ph1.push(n_orig + i); }
177
178 let ph1_result = simplex_core(&a_ph1, &mut b_std, &c_ph1, &mut basis_ph1, opts)?;
179
180 if ph1_result > opts.tol {
181 return Err(OptimError::LPInfeasible);
182 }
183
184 for &bj in &basis_ph1 {
186 if bj >= n_total {
187 let row = basis_ph1.iter().position(|&x| x == bj).unwrap();
188 if b_std[row].abs() > opts.tol {
189 return Err(OptimError::LPInfeasible);
190 }
191 }
192 }
193
194 basis = basis_ph1;
195 for row in 0..m {
197 if basis[row] >= n_total {
198 for (j, val) in a_rows[row].iter().enumerate().take(n_total) {
199 if !basis.contains(&j) && val.abs() > opts.tol {
200 basis[row] = j;
201 break;
202 }
203 }
204 }
205 }
206 } else {
207 for i in 0..m_ineq {
209 basis.push(n_orig + i);
210 }
211 }
212
213 let _opt_val = simplex_core(&a_rows, &mut b_std, &c_std, &mut basis, opts)?;
215
216 let mut x = vec![S::ZERO; n_total];
218 for (row, &bj) in basis.iter().enumerate() {
219 if bj < n_total {
220 x[bj] = b_std[row];
221 }
222 }
223
224 let x_orig: Vec<S> = x[..n_orig].to_vec();
225 let f_val: S = c
226 .iter()
227 .zip(x_orig.iter())
228 .map(|(&ci, &xi)| ci * xi)
229 .sum::<S>();
230
231 let mut b_mat = DenseMatrix::<S>::zeros(m, m);
233 for (col, &bj) in basis.iter().enumerate() {
234 for (i, a_row) in a_rows.iter().enumerate().take(m) {
235 b_mat.set(i, col, a_row[bj]);
236 }
237 }
238 let c_b: Vec<S> = basis.iter().map(|&j| c_std[j]).collect();
239 let mut bt = DenseMatrix::<S>::zeros(m, m);
241 for i in 0..m {
242 for j in 0..m {
243 bt.set(i, j, b_mat.get(j, i));
244 }
245 }
246 let pi = bt.solve(&c_b).unwrap_or_else(|_| vec![S::ZERO; m]);
247
248 let mut lambda_eq = Vec::with_capacity(m_eq);
249 let mut lambda_ineq = Vec::with_capacity(m_ineq);
250 for item in pi.iter().take(m_eq) {
251 lambda_eq.push(*item);
252 }
253 for i in 0..m_ineq {
254 lambda_ineq.push(pi[m_eq + i]);
255 }
256
257 let history = vec![IterationRecord {
258 iteration: 0,
259 objective: f_val,
260 gradient_norm: S::ZERO,
261 step_size: S::ZERO,
262 constraint_violation: S::ZERO,
263 }];
264
265 Ok((OptimResult {
266 lambda_eq,
267 lambda_ineq,
268 history,
269 ..OptimResult::unconstrained(
270 x_orig,
271 f_val,
272 c.to_vec(),
273 0,
274 0,
275 0,
276 true,
277 "Optimal solution found".into(),
278 OptimStatus::FunctionConverged,
279 )
280 })
281 .with_wall_time(start))
282}
283
284fn simplex_core<
291 S: Scalar + faer::SimpleEntity + faer::Conjugate<Canonical = S> + faer::ComplexField,
292>(
293 a_rows: &[Vec<S>],
294 b: &mut [S],
295 c: &[S],
296 basis: &mut [usize],
297 opts: &LPOptions<S>,
298) -> Result<S, OptimError> {
299 let m = a_rows.len();
300 let n = c.len();
301
302 for _iter in 0..opts.max_iter {
303 let mut b_mat = DenseMatrix::<S>::zeros(m, m);
305 for (col, &bj) in basis.iter().enumerate() {
306 for (row, a_row) in a_rows.iter().enumerate().take(m) {
307 b_mat.set(row, col, a_row[bj]);
308 }
309 }
310
311 let c_b: Vec<S> = basis.iter().map(|&j| c[j]).collect();
312
313 let mut bt = DenseMatrix::<S>::zeros(m, m);
315 for i in 0..m {
316 for j in 0..m {
317 bt.set(i, j, b_mat.get(j, i));
318 }
319 }
320 let pi = bt.solve(&c_b).map_err(|_| OptimError::SingularMatrix)?;
321
322 let mut entering = None;
324 let mut min_rc = -opts.tol;
325 for j in 0..n {
326 if basis.contains(&j) {
327 continue;
328 }
329 let mut rc = c[j];
330 for i in 0..m {
331 rc -= pi[i] * a_rows[i][j];
332 }
333 if rc < min_rc {
334 min_rc = rc;
335 entering = Some(j);
336 }
337 }
338
339 let entering = match entering {
340 Some(j) => j,
341 None => {
342 return Ok(basis
344 .iter()
345 .enumerate()
346 .map(|(row, &j)| c[j] * b[row])
347 .sum::<S>());
348 }
349 };
350
351 let a_col: Vec<S> = (0..m).map(|i| a_rows[i][entering]).collect();
353 let d = b_mat
354 .solve(&a_col)
355 .map_err(|_| OptimError::SingularMatrix)?;
356
357 let mut min_ratio = S::INFINITY;
359 let mut leaving_row = None;
360 for i in 0..m {
361 if d[i] > opts.tol {
362 let ratio = b[i] / d[i];
363 if ratio < min_ratio {
364 min_ratio = ratio;
365 leaving_row = Some(i);
366 }
367 }
368 }
369
370 let leaving_row = match leaving_row {
371 Some(r) => r,
372 None => return Err(OptimError::Unbounded),
373 };
374
375 let theta = min_ratio;
377 for i in 0..m {
378 if i == leaving_row {
379 b[i] = theta;
380 } else {
381 b[i] -= theta * d[i];
382 }
383 }
384 basis[leaving_row] = entering;
385
386 for bi in b.iter_mut() {
388 if *bi < S::ZERO && *bi > -opts.tol {
389 *bi = S::ZERO;
390 }
391 }
392 }
393
394 Err(OptimError::Other(format!(
395 "simplex: max iterations ({}) reached",
396 opts.max_iter
397 )))
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403
404 #[test]
405 fn test_lp_simple_2d() {
406 let c = vec![-1.0, -1.0];
408 let a_ineq = vec![vec![1.0, 1.0], vec![1.0, 0.0], vec![0.0, 1.0]];
409 let b_ineq = vec![4.0, 3.0, 3.0];
410 let opts = LPOptions::default();
411 let result = simplex_solve(&c, &a_ineq, &b_ineq, &[], &[], &opts).unwrap();
412 assert!(result.converged, "LP did not converge: {}", result.message);
413 assert!(
414 (result.f - (-4.0)).abs() < 1e-8,
415 "f={}, expected -4",
416 result.f
417 );
418 assert!(result.x[0] + result.x[1] - 4.0 < 1e-8);
419 }
420
421 #[test]
422 fn test_lp_with_equality() {
423 let c = vec![1.0, 2.0];
425 let a_eq = vec![vec![1.0, 1.0]];
426 let b_eq = vec![3.0];
427 let opts = LPOptions::default();
428 let result = simplex_solve(&c, &[], &[], &a_eq, &b_eq, &opts).unwrap();
429 assert!(result.converged, "LP did not converge: {}", result.message);
430 assert!((result.f - 3.0).abs() < 1e-8, "f={}", result.f);
431 assert!((result.x[0] - 3.0).abs() < 1e-8);
432 assert!(result.x[1].abs() < 1e-8);
433 }
434
435 #[test]
436 fn test_lp_unbounded() {
437 let c = vec![-1.0];
439 let opts = LPOptions::default();
440 let result = simplex_solve(&c, &[], &[], &[], &[], &opts);
441 assert!(result.is_err());
442 }
443
444 #[test]
445 fn test_lp_infeasible() {
446 let c2 = vec![1.0, 1.0];
454 let a_eq2 = vec![vec![1.0, 1.0], vec![1.0, 1.0]];
455 let b_eq2 = vec![1.0, 2.0];
456 let opts = LPOptions::default();
457 let result = simplex_solve(&c2, &[], &[], &a_eq2, &b_eq2, &opts);
458 assert!(result.is_err());
459 }
460
461 #[test]
462 fn test_lp_3d_production() {
463 let c = vec![-5.0, -4.0, -3.0];
466 let a_ineq = vec![vec![6.0, 4.0, 2.0], vec![3.0, 2.0, 5.0]];
467 let b_ineq = vec![240.0, 270.0];
468 let opts = LPOptions::default();
469 let result = simplex_solve(&c, &a_ineq, &b_ineq, &[], &[], &opts).unwrap();
470 assert!(result.converged, "LP did not converge: {}", result.message);
471 assert!(result.f <= -219.0, "f={}, expected <= -219", result.f);
472 }
473
474 #[test]
475 fn test_lp_dual_variables() {
476 let c = vec![-1.0, -1.0];
478 let a_ineq = vec![vec![1.0, 1.0]];
479 let b_ineq = vec![1.0];
480 let opts = LPOptions::default();
481 let result = simplex_solve(&c, &a_ineq, &b_ineq, &[], &[], &opts).unwrap();
482 assert!(result.converged);
483 assert!((result.f - (-1.0)).abs() < 1e-8, "f={}", result.f);
484 assert!(!result.lambda_ineq.is_empty());
485 }
486}