1use num_traits::{One, Signed, Zero};
2
3use crate::error::Error;
4use crate::num::{Coef, Rational};
5use crate::problem::{Problem, Relation, Sense};
6use crate::sensitivity::{self, Cells, Sensitivity, StandardForm};
7use crate::trace::{NamedValue, Phase, RatioTest, Snapshot, Step, Termination, Trace};
8
9#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum Method {
17 Simplex,
20 BigM,
22 TwoPhase,
24}
25
26#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum PivotRule {
32 Dantzig,
35 Bland,
37}
38
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[cfg_attr(feature = "serde", serde(default))]
49#[derive(Clone, Debug, PartialEq, Eq)]
50#[non_exhaustive]
51pub struct Options {
52 pub pivot_rule: PivotRule,
53 pub max_iterations: usize,
55}
56
57impl Default for Options {
58 fn default() -> Self {
59 Options {
60 pivot_rule: PivotRule::Dantzig,
61 max_iterations: 10_000,
62 }
63 }
64}
65
66impl Options {
67 pub fn pivot_rule(mut self, rule: PivotRule) -> Self {
68 self.pivot_rule = rule;
69 self
70 }
71
72 pub fn max_iterations(mut self, n: usize) -> Self {
73 self.max_iterations = n;
74 self
75 }
76}
77
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
83#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "snake_case"))]
84#[derive(Clone, Debug, PartialEq, Eq)]
85pub enum Status {
86 Optimal {
88 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::one"))]
89 objective: Rational,
90 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::vec"))]
91 values: Vec<Rational>,
92 },
93 Infeasible,
94 Unbounded,
95}
96
97#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
102#[derive(Clone, Debug, PartialEq, Eq)]
103#[non_exhaustive]
104pub struct Solution {
105 #[cfg_attr(feature = "serde", serde(flatten))]
106 pub status: Status,
107 #[cfg_attr(
111 feature = "serde",
112 serde(default, skip_serializing_if = "Option::is_none")
113 )]
114 pub sensitivity: Option<Sensitivity>,
115 pub trace: Trace,
116}
117
118pub fn solve(problem: &Problem, method: Method, options: &Options) -> Result<Solution, Error> {
136 problem.validate()?;
137 let mut trace = Trace::new(problem.sense);
138 let mut t = Tableau::standard_form(problem);
139 let sense = problem.sense;
140 let costs = &problem.objective;
141
142 let status = match method {
143 Method::Simplex if t.has_artificials() => return Err(Error::ArtificialVariablesRequired),
144 Method::Simplex => t.single_phase(sense, costs, Coef::zero(), options, &mut trace)?,
145 Method::TwoPhase if !t.has_artificials() => {
146 t.single_phase(sense, costs, Coef::zero(), options, &mut trace)?
147 }
148 Method::TwoPhase => t.two_phase(sense, costs, options, &mut trace)?,
149 Method::BigM => {
150 let penalty = match sense {
152 Sense::Maximize => Coef::big_m(-Rational::one()),
153 Sense::Minimize => Coef::big_m(Rational::one()),
154 };
155 t.single_phase(sense, costs, penalty, options, &mut trace)?
156 }
157 };
158
159 let sensitivity = match status {
160 End::Optimal => t.sensitivity(problem),
161 _ => None,
162 };
163 let (status, termination) = match status {
164 End::Optimal => {
165 let status = Status::Optimal {
166 objective: t.z_rhs.c.clone(),
167 values: t.values(problem.num_vars()),
168 };
169 (status, Termination::Optimal)
170 }
171 End::Infeasible => (Status::Infeasible, Termination::Infeasible),
172 End::Unbounded => (Status::Unbounded, Termination::Unbounded),
173 };
174 trace.push(Step::Finished { termination });
175 Ok(Solution {
176 status,
177 sensitivity,
178 trace,
179 })
180}
181
182enum End {
183 Optimal,
184 Infeasible,
185 Unbounded,
186}
187
188#[derive(Clone)]
189struct Tableau {
190 sense: Sense,
192 objective_label: &'static str,
193 names: Vec<String>,
194 artificial: Vec<bool>,
195 artificial_owner: Vec<Option<usize>>,
197 removed: Vec<usize>,
199 rows: Vec<Vec<Rational>>,
200 rhs: Vec<Rational>,
201 basis: Vec<usize>,
202 z: Vec<Coef>,
203 z_rhs: Coef,
204}
205
206impl Tableau {
207 fn standard_form(problem: &Problem) -> Self {
211 let n = problem.num_vars();
212 let normalized: Vec<(Vec<Rational>, Relation, Rational)> = problem
213 .constraints
214 .iter()
215 .map(|c| {
216 if c.rhs.is_negative() {
217 let flipped = match c.relation {
218 Relation::Le => Relation::Ge,
219 Relation::Ge => Relation::Le,
220 Relation::Eq => Relation::Eq,
221 };
222 (
223 c.coeffs.iter().map(|v| -v.clone()).collect(),
224 flipped,
225 -c.rhs.clone(),
226 )
227 } else {
228 (c.coeffs.clone(), c.relation, c.rhs.clone())
229 }
230 })
231 .collect();
232
233 let mut names: Vec<String> = (0..n).map(|j| problem.var_name(j)).collect();
234 let mut slack_col = vec![None; normalized.len()];
235 for (i, (_, relation, _)) in normalized.iter().enumerate() {
236 if *relation != Relation::Eq {
237 slack_col[i] = Some(names.len());
238 names.push(format!("s{}", i + 1));
239 }
240 }
241 let first_artificial = names.len();
242 let mut art_col = vec![None; normalized.len()];
243 let mut artificial_owner = vec![None; first_artificial];
244 for (i, (_, relation, _)) in normalized.iter().enumerate() {
245 if *relation != Relation::Le {
246 art_col[i] = Some(names.len());
247 artificial_owner.push(Some(i));
248 names.push(format!("a{}", i + 1));
249 }
250 }
251
252 let width = names.len();
253 let mut rows = Vec::with_capacity(normalized.len());
254 let mut rhs = Vec::with_capacity(normalized.len());
255 let mut basis = Vec::with_capacity(normalized.len());
256 for (i, (coeffs, relation, b)) in normalized.into_iter().enumerate() {
257 let mut row = vec![Rational::zero(); width];
258 row[..n].clone_from_slice(&coeffs);
259 if let Some(s) = slack_col[i] {
260 row[s] = if relation == Relation::Le {
261 Rational::one()
262 } else {
263 -Rational::one()
264 };
265 }
266 if let Some(a) = art_col[i] {
267 row[a] = Rational::one();
268 }
269 basis.push(
270 art_col[i]
271 .or(slack_col[i])
272 .expect("every row has a basic column"),
273 );
274 rows.push(row);
275 rhs.push(b);
276 }
277
278 Tableau {
279 sense: problem.sense,
280 objective_label: "z",
281 artificial: (0..width).map(|j| j >= first_artificial).collect(),
282 artificial_owner,
283 removed: Vec::new(),
284 names,
285 rows,
286 rhs,
287 basis,
288 z: vec![Coef::zero(); width],
289 z_rhs: Coef::zero(),
290 }
291 }
292
293 fn has_artificials(&self) -> bool {
294 self.artificial.iter().any(|&a| a)
295 }
296
297 fn snapshot(&self) -> Snapshot {
298 Snapshot {
299 sense: self.sense,
300 objective_label: self.objective_label.to_string(),
301 columns: self.names.clone(),
302 basis: self.basis.clone(),
303 rows: self.rows.clone(),
304 rhs: self.rhs.clone(),
305 z: self.z.clone(),
306 z_rhs: self.z_rhs.clone(),
307 }
308 }
309
310 fn set_objective(
314 &mut self,
315 sense: Sense,
316 label: &'static str,
317 costs: &[Rational],
318 artificial_cost: &Coef,
319 ) {
320 self.sense = sense;
321 self.objective_label = label;
322 self.z = (0..self.names.len())
323 .map(|j| {
324 if j < costs.len() {
325 -Coef::constant(costs[j].clone())
326 } else if self.artificial[j] {
327 -artificial_cost.clone()
328 } else {
329 Coef::zero()
330 }
331 })
332 .collect();
333 self.z_rhs = Coef::zero();
334 for i in 0..self.rows.len() {
335 let factor = self.z[self.basis[i]].clone();
336 if factor.is_zero() {
337 continue;
338 }
339 for (zj, aij) in self.z.iter_mut().zip(&self.rows[i]) {
340 zj.sub_mul(&factor, aij);
341 }
342 self.z_rhs.sub_mul(&factor, &self.rhs[i]);
343 }
344 }
345
346 fn pivot(&mut self, row: usize, col: usize) {
347 let pivot = self.rows[row][col].clone();
348 for v in self.rows[row].iter_mut() {
349 *v /= &pivot;
350 }
351 self.rhs[row] /= &pivot;
352
353 let pivot_row = self.rows[row].clone();
354 let pivot_rhs = self.rhs[row].clone();
355 for i in 0..self.rows.len() {
356 if i == row || self.rows[i][col].is_zero() {
357 continue;
358 }
359 let factor = self.rows[i][col].clone();
360 for (v, p) in self.rows[i].iter_mut().zip(&pivot_row) {
361 *v -= &factor * p;
362 }
363 self.rhs[i] -= &factor * &pivot_rhs;
364 }
365
366 let factor = self.z[col].clone();
367 if !factor.is_zero() {
368 for (zj, p) in self.z.iter_mut().zip(&pivot_row) {
369 zj.sub_mul(&factor, p);
370 }
371 self.z_rhs.sub_mul(&factor, &pivot_rhs);
372 }
373 self.basis[row] = col;
374 }
375
376 fn entering(&self, rule: PivotRule) -> Option<usize> {
386 let sense = self.sense;
387 let improves = |j: &usize| match sense {
388 Sense::Maximize => self.z[*j].is_negative(),
389 Sense::Minimize => self.z[*j].is_positive(),
390 };
391 let improves_penalty = |j: &usize| match sense {
392 Sense::Maximize => self.z[*j].m.is_negative(),
393 Sense::Minimize => self.z[*j].m.is_positive(),
394 };
395 let mut candidates = (0..self.z.len()).filter(improves);
396 match rule {
397 PivotRule::Bland => (0..self.z.len())
398 .find(improves_penalty)
399 .or_else(|| candidates.next()),
400 PivotRule::Dantzig => candidates.min_by(|&a, &b| match sense {
402 Sense::Maximize => self.z[a].cmp(&self.z[b]),
403 Sense::Minimize => self.z[b].cmp(&self.z[a]),
404 }),
405 }
406 }
407
408 fn ratio_test(&self, col: usize) -> (Vec<RatioTest>, Option<usize>) {
410 let ratios: Vec<RatioTest> = (0..self.rows.len())
411 .map(|i| {
412 let a = &self.rows[i][col];
413 let ratio = if a.is_positive() {
414 Some(&self.rhs[i] / a)
415 } else {
416 None
417 };
418 RatioTest { row: i, ratio }
419 })
420 .collect();
421 let leaving = ratios
422 .iter()
423 .filter_map(|r| r.ratio.as_ref().map(|v| (v, self.basis[r.row], r.row)))
424 .min()
425 .map(|(_, _, row)| row);
426 (ratios, leaving)
427 }
428
429 fn optimize(
431 &mut self,
432 phase: Phase,
433 options: &Options,
434 trace: &mut Trace,
435 ) -> Result<End, Error> {
436 let mut iteration = 0;
437 loop {
438 trace.push(Step::Tableau {
439 phase,
440 iteration,
441 snapshot: self.snapshot(),
442 });
443
444 let Some(entering) = self.entering(options.pivot_rule) else {
445 return Ok(End::Optimal);
446 };
447 let (ratios, leaving_row) = self.ratio_test(entering);
448 let Some(leaving_row) = leaving_row else {
449 return Ok(End::Unbounded);
450 };
451 if iteration == options.max_iterations {
452 return Err(Error::IterationLimit(options.max_iterations));
453 }
454
455 let leaving = self.basis[leaving_row];
456 trace.push(Step::Pivot {
457 phase,
458 iteration,
459 entering,
460 entering_name: self.names[entering].clone(),
461 leaving_row,
462 leaving,
463 leaving_name: self.names[leaving].clone(),
464 pivot_value: self.rows[leaving_row][entering].clone(),
465 ratios,
466 });
467 self.pivot(leaving_row, entering);
468 iteration += 1;
469 }
470 }
471
472 fn positive_artificials(&self) -> Vec<NamedValue> {
474 (0..self.rows.len())
475 .filter(|&i| self.artificial[self.basis[i]] && self.rhs[i].is_positive())
476 .map(|i| NamedValue::new(self.names[self.basis[i]].clone(), self.rhs[i].clone()))
477 .collect()
478 }
479
480 fn single_phase(
482 &mut self,
483 sense: Sense,
484 costs: &[Rational],
485 artificial_cost: Coef,
486 options: &Options,
487 trace: &mut Trace,
488 ) -> Result<End, Error> {
489 self.set_objective(sense, "z", costs, &artificial_cost);
490 let end = self.optimize(Phase::Single, options, trace)?;
491
492 let positive = self.positive_artificials();
498 if positive.is_empty() {
499 return Ok(end);
500 }
501 trace.push(Step::ArtificialRemainsPositive {
502 artificials: positive,
503 unbounded_direction: matches!(end, End::Unbounded),
504 });
505 Ok(End::Infeasible)
506 }
507
508 fn two_phase(
509 &mut self,
510 sense: Sense,
511 costs: &[Rational],
512 options: &Options,
513 trace: &mut Trace,
514 ) -> Result<End, Error> {
515 let zeros = vec![Rational::zero(); costs.len()];
517 self.set_objective(
518 Sense::Minimize,
519 "r",
520 &zeros,
521 &Coef::constant(Rational::one()),
522 );
523 self.optimize(Phase::One, options, trace)?;
525
526 let artificial_sum = self.z_rhs.c.clone();
527 let feasible = artificial_sum.is_zero();
528 trace.push(Step::PhaseOneFinished {
529 artificial_sum,
530 feasible,
531 });
532 if !feasible {
533 return Ok(End::Infeasible);
534 }
535
536 self.drop_artificials(trace);
537 self.set_objective(sense, "z", costs, &Coef::zero());
538 self.optimize(Phase::Two, options, trace)
539 }
540
541 fn drop_artificials(&mut self, trace: &mut Trace) {
544 let mut i = 0;
545 while i < self.rows.len() {
546 if !self.artificial[self.basis[i]] {
547 i += 1;
548 continue;
549 }
550 let replacement =
551 (0..self.names.len()).find(|&j| !self.artificial[j] && !self.rows[i][j].is_zero());
552 match replacement {
553 Some(j) => {
554 self.pivot(i, j);
555 trace.push(Step::ArtificialPivotedOut {
556 row: i,
557 entering: j,
558 entering_name: self.names[j].clone(),
559 });
560 i += 1;
561 }
562 None => {
563 self.removed
566 .push(self.artificial_owner[self.basis[i]].expect("artificial column"));
567 self.rows.remove(i);
568 self.rhs.remove(i);
569 self.basis.remove(i);
570 trace.push(Step::RedundantRowRemoved { row: i });
571 }
572 }
573 }
574
575 let keep: Vec<usize> = (0..self.names.len())
576 .filter(|&j| !self.artificial[j])
577 .collect();
578 let mut new_index = vec![usize::MAX; self.names.len()];
579 for (new, &old) in keep.iter().enumerate() {
580 new_index[old] = new;
581 }
582 self.names = keep.iter().map(|&j| self.names[j].clone()).collect();
583 self.artificial = vec![false; keep.len()];
584 self.artificial_owner = vec![None; keep.len()];
585 for row in self.rows.iter_mut() {
586 *row = keep.iter().map(|&j| row[j].clone()).collect();
587 }
588 for b in self.basis.iter_mut() {
589 *b = new_index[*b];
590 }
591 self.z = vec![Coef::zero(); keep.len()];
592 self.z_rhs = Coef::zero();
593 }
594
595 fn sensitivity(&self, problem: &Problem) -> Option<Sensitivity> {
604 let mut removed = self.removed.clone();
605 let mut basis = Vec::with_capacity(self.basis.len());
606 for (i, &b) in self.basis.iter().enumerate() {
607 if !self.artificial[b] {
608 basis.push(b);
609 continue;
610 }
611 let redundant =
612 (0..self.names.len()).all(|j| self.artificial[j] || self.rows[i][j].is_zero());
613 if !redundant {
614 return self.rebased_sensitivity(problem);
615 }
616 removed.push(self.artificial_owner[b].expect("artificial column"));
617 }
618 let cells = Cells::FinalTableau {
619 artificial_columns: self.has_artificials(),
620 };
621 self.analyze(problem, &basis, &removed, cells)
622 }
623
624 fn rebased_sensitivity(&self, problem: &Problem) -> Option<Sensitivity> {
625 let mut t = self.clone();
626 let mut scratch = Trace::new(problem.sense);
627 t.drop_artificials(&mut scratch);
628 t.set_objective(problem.sense, "z", &problem.objective, &Coef::zero());
629 let options = Options::default().pivot_rule(PivotRule::Bland);
630 match t.optimize(Phase::Two, &options, &mut scratch) {
631 Ok(End::Optimal) => t.analyze(problem, &t.basis, &t.removed, Cells::None),
632 _ => None, }
634 }
635
636 fn analyze(
637 &self,
638 problem: &Problem,
639 basis: &[usize],
640 removed: &[usize],
641 cells: Cells,
642 ) -> Option<Sensitivity> {
643 let start = Tableau::standard_form(problem);
644 let form = StandardForm {
645 rows: &start.rows,
646 rhs: &start.rhs,
647 names: &start.names,
648 artificial: &start.artificial,
649 };
650 sensitivity::analyze(problem, form, basis, removed, cells)
651 }
652
653 fn values(&self, n: usize) -> Vec<Rational> {
654 let mut values = vec![Rational::zero(); n];
655 for (i, &b) in self.basis.iter().enumerate() {
656 if b < n {
657 values[b] = self.rhs[i].clone();
658 }
659 }
660 values
661 }
662}