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::{
8 DualRatioTest, NamedValue, Phase, RatioTest, Snapshot, Step, Termination, Trace,
9};
10
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
16#[derive(Clone, Copy, Debug, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum Method {
19 Simplex,
22 BigM,
24 TwoPhase,
26 DualSimplex,
32}
33
34#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
36#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
37#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38#[non_exhaustive]
39pub enum PivotRule {
40 Dantzig,
44 Bland,
47}
48
49#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
58#[cfg_attr(feature = "serde", serde(default))]
59#[derive(Clone, Debug, PartialEq, Eq)]
60#[non_exhaustive]
61pub struct Options {
62 pub pivot_rule: PivotRule,
63 pub max_iterations: usize,
65}
66
67impl Default for Options {
68 fn default() -> Self {
69 Options {
70 pivot_rule: PivotRule::Dantzig,
71 max_iterations: 10_000,
72 }
73 }
74}
75
76impl Options {
77 pub fn pivot_rule(mut self, rule: PivotRule) -> Self {
78 self.pivot_rule = rule;
79 self
80 }
81
82 pub fn max_iterations(mut self, n: usize) -> Self {
83 self.max_iterations = n;
84 self
85 }
86}
87
88#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "snake_case"))]
94#[derive(Clone, Debug, PartialEq, Eq)]
95pub enum Status {
96 Optimal {
98 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::one"))]
99 objective: Rational,
100 #[cfg_attr(feature = "serde", serde(with = "crate::serde_rational::vec"))]
101 values: Vec<Rational>,
102 },
103 Infeasible,
104 Unbounded,
105}
106
107#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
114#[derive(Clone, Debug, PartialEq, Eq)]
115#[non_exhaustive]
116pub struct Solution {
117 #[cfg_attr(feature = "serde", serde(flatten))]
118 pub status: Status,
119 pub problem: Problem,
121 #[cfg_attr(
125 feature = "serde",
126 serde(default, skip_serializing_if = "Option::is_none")
127 )]
128 pub sensitivity: Option<Sensitivity>,
129 pub trace: Trace,
130}
131
132pub fn solve(problem: &Problem, method: Method, options: &Options) -> Result<Solution, Error> {
150 problem.validate()?;
151 let mut trace = Trace::new(problem.sense);
152 let sense = problem.sense;
153 let costs = &problem.objective;
154
155 if method == Method::DualSimplex {
156 check_dual_feasible(problem)?;
157 let mut t = Tableau::dual_form(problem);
158 t.set_objective(sense, "z", costs, &Coef::zero());
159 let end = t.optimize_dual(options, &mut trace)?;
160 return Ok(finish(&t, problem, end, trace));
161 }
162
163 let mut t = Tableau::standard_form(problem);
164 let end = match method {
165 Method::Simplex if t.has_artificials() => return Err(Error::ArtificialVariablesRequired),
166 Method::Simplex => t.single_phase(sense, costs, Coef::zero(), options, &mut trace)?,
167 Method::TwoPhase if !t.has_artificials() => {
168 t.single_phase(sense, costs, Coef::zero(), options, &mut trace)?
169 }
170 Method::TwoPhase => t.two_phase(sense, costs, options, &mut trace)?,
171 Method::BigM => {
172 let penalty = match sense {
174 Sense::Maximize => Coef::big_m(-Rational::one()),
175 Sense::Minimize => Coef::big_m(Rational::one()),
176 };
177 t.single_phase(sense, costs, penalty, options, &mut trace)?
178 }
179 Method::DualSimplex => unreachable!("handled above"),
180 };
181 Ok(finish(&t, problem, end, trace))
182}
183
184pub fn reoptimize(
212 previous: &Solution,
213 problem: &Problem,
214 options: &Options,
215) -> Result<Solution, Error> {
216 problem.validate()?;
217 let old = &previous.problem;
218 let (Status::Optimal { .. }, Some(sensitivity)) = (&previous.status, &previous.sensitivity)
219 else {
220 return Err(Error::NotOptimal);
221 };
222 let m = old.constraints.len();
223 let same_prefix = problem.sense == old.sense
224 && problem.objective == old.objective
225 && problem.var_names == old.var_names
226 && problem.constraints.len() >= m
227 && problem.constraints[..m] == old.constraints[..];
228 if !same_prefix {
229 return Err(Error::InvalidProblem(
230 "the extended problem must be the previous problem with constraints appended".into(),
231 ));
232 }
233 for (i, c) in problem.constraints.iter().enumerate().skip(m) {
234 if c.relation == Relation::Eq {
235 return Err(Error::NotDualFeasible(format!(
236 "constraint {} is an equality; added constraints must be <= or >=",
237 i + 1
238 )));
239 }
240 }
241
242 let mut trace = Trace::new(problem.sense);
243 let mut t = Tableau::from_basis(old, sensitivity)?;
244 t.append_constraints(problem, m, &mut trace);
245 let end = t.optimize_dual(options, &mut trace)?;
246 Ok(finish(&t, problem, end, trace))
247}
248
249fn check_dual_feasible(problem: &Problem) -> Result<(), Error> {
252 for (i, c) in problem.constraints.iter().enumerate() {
253 if c.relation == Relation::Eq {
254 return Err(Error::NotDualFeasible(format!(
255 "constraint {} is an equality, which has no slack",
256 i + 1
257 )));
258 }
259 }
260 let (wrong, needs) = match problem.sense {
261 Sense::Maximize => (Signed::is_positive as fn(&Rational) -> bool, "non-positive"),
262 Sense::Minimize => (Signed::is_negative as fn(&Rational) -> bool, "non-negative"),
263 };
264 if let Some((j, c)) = problem.objective.iter().enumerate().find(|(_, c)| wrong(c)) {
265 return Err(Error::NotDualFeasible(format!(
266 "{} has cost {c}, but the costs must be {needs} when {}",
267 problem.var_name(j),
268 match problem.sense {
269 Sense::Maximize => "maximizing",
270 Sense::Minimize => "minimizing",
271 }
272 )));
273 }
274 Ok(())
275}
276
277fn finish(t: &Tableau, problem: &Problem, end: End, mut trace: Trace) -> Solution {
280 let sensitivity = match end {
281 End::Optimal => t.sensitivity(problem),
282 _ => None,
283 };
284 let (status, termination) = match end {
285 End::Optimal => {
286 let status = Status::Optimal {
287 objective: t.z_rhs.c.clone(),
288 values: t.values(problem.num_vars()),
289 };
290 (status, Termination::Optimal)
291 }
292 End::Infeasible => (Status::Infeasible, Termination::Infeasible),
293 End::Unbounded => (Status::Unbounded, Termination::Unbounded),
294 };
295 trace.push(Step::Finished { termination });
296 Solution {
297 status,
298 problem: problem.clone(),
299 sensitivity,
300 trace,
301 }
302}
303
304enum End {
305 Optimal,
306 Infeasible,
307 Unbounded,
308}
309
310#[derive(Clone)]
311struct Tableau {
312 sense: Sense,
314 objective_label: &'static str,
315 names: Vec<String>,
316 artificial: Vec<bool>,
317 artificial_owner: Vec<Option<usize>>,
319 removed: Vec<usize>,
321 rows: Vec<Vec<Rational>>,
322 rhs: Vec<Rational>,
323 basis: Vec<usize>,
324 z: Vec<Coef>,
325 z_rhs: Coef,
326}
327
328impl Tableau {
329 fn standard_form(problem: &Problem) -> Self {
333 let n = problem.num_vars();
334 let normalized: Vec<(Vec<Rational>, Relation, Rational)> = problem
335 .constraints
336 .iter()
337 .map(|c| {
338 if c.rhs.is_negative() {
339 let flipped = match c.relation {
340 Relation::Le => Relation::Ge,
341 Relation::Ge => Relation::Le,
342 Relation::Eq => Relation::Eq,
343 };
344 (
345 c.coeffs.iter().map(|v| -v.clone()).collect(),
346 flipped,
347 -c.rhs.clone(),
348 )
349 } else {
350 (c.coeffs.clone(), c.relation, c.rhs.clone())
351 }
352 })
353 .collect();
354
355 let mut names: Vec<String> = (0..n).map(|j| problem.var_name(j)).collect();
356 let mut slack_col = vec![None; normalized.len()];
357 for (i, (_, relation, _)) in normalized.iter().enumerate() {
358 if *relation != Relation::Eq {
359 slack_col[i] = Some(names.len());
360 names.push(format!("s{}", i + 1));
361 }
362 }
363 let first_artificial = names.len();
364 let mut art_col = vec![None; normalized.len()];
365 let mut artificial_owner = vec![None; first_artificial];
366 for (i, (_, relation, _)) in normalized.iter().enumerate() {
367 if *relation != Relation::Le {
368 art_col[i] = Some(names.len());
369 artificial_owner.push(Some(i));
370 names.push(format!("a{}", i + 1));
371 }
372 }
373
374 let width = names.len();
375 let mut rows = Vec::with_capacity(normalized.len());
376 let mut rhs = Vec::with_capacity(normalized.len());
377 let mut basis = Vec::with_capacity(normalized.len());
378 for (i, (coeffs, relation, b)) in normalized.into_iter().enumerate() {
379 let mut row = vec![Rational::zero(); width];
380 row[..n].clone_from_slice(&coeffs);
381 if let Some(s) = slack_col[i] {
382 row[s] = if relation == Relation::Le {
383 Rational::one()
384 } else {
385 -Rational::one()
386 };
387 }
388 if let Some(a) = art_col[i] {
389 row[a] = Rational::one();
390 }
391 basis.push(
392 art_col[i]
393 .or(slack_col[i])
394 .expect("every row has a basic column"),
395 );
396 rows.push(row);
397 rhs.push(b);
398 }
399
400 Tableau {
401 sense: problem.sense,
402 objective_label: "z",
403 artificial: (0..width).map(|j| j >= first_artificial).collect(),
404 artificial_owner,
405 removed: Vec::new(),
406 names,
407 rows,
408 rhs,
409 basis,
410 z: vec![Coef::zero(); width],
411 z_rhs: Coef::zero(),
412 }
413 }
414
415 fn dual_form(problem: &Problem) -> Self {
419 let n = problem.num_vars();
420 let m = problem.constraints.len();
421 let width = n + m;
422 let mut names: Vec<String> = (0..n).map(|j| problem.var_name(j)).collect();
423 names.extend((0..m).map(|i| format!("s{}", i + 1)));
424
425 let mut rows = Vec::with_capacity(m);
426 let mut rhs = Vec::with_capacity(m);
427 for (i, c) in problem.constraints.iter().enumerate() {
428 let mut row = vec![Rational::zero(); width];
429 let (coeffs, b) = match c.relation {
430 Relation::Le => (c.coeffs.clone(), c.rhs.clone()),
431 Relation::Ge => (
432 c.coeffs.iter().map(|v| -v.clone()).collect(),
433 -c.rhs.clone(),
434 ),
435 Relation::Eq => unreachable!("rejected by check_dual_feasible"),
436 };
437 row[..n].clone_from_slice(&coeffs);
438 row[n + i] = Rational::one();
439 rows.push(row);
440 rhs.push(b);
441 }
442
443 Tableau {
444 sense: problem.sense,
445 objective_label: "z",
446 artificial: vec![false; width],
447 artificial_owner: vec![None; width],
448 removed: Vec::new(),
449 names,
450 rows,
451 rhs,
452 basis: (n..width).collect(),
453 z: vec![Coef::zero(); width],
454 z_rhs: Coef::zero(),
455 }
456 }
457
458 fn from_basis(problem: &Problem, sensitivity: &Sensitivity) -> Result<Self, Error> {
462 let mismatch =
463 || Error::InvalidProblem("the previous solution does not belong to its problem".into());
464 let mut t = Tableau::standard_form(problem);
465 let real = t
466 .artificial
467 .iter()
468 .position(|&a| a)
469 .unwrap_or(t.names.len());
470 t.names.truncate(real);
471 t.artificial.truncate(real);
472 t.artificial_owner.truncate(real);
473 for row in t.rows.iter_mut() {
474 row.truncate(real);
475 }
476 t.z = vec![Coef::zero(); real];
477
478 if sensitivity.constraints.len() != t.rows.len() {
479 return Err(mismatch());
480 }
481 t.removed = (0..t.rows.len())
482 .filter(|&i| sensitivity.constraints[i].redundant)
483 .collect();
484 for &i in t.removed.iter().rev() {
485 t.rows.remove(i);
486 t.rhs.remove(i);
487 }
488
489 let basis: Vec<usize> = sensitivity
490 .basic_variables
491 .iter()
492 .map(|name| t.names.iter().position(|n| n == name).ok_or_else(mismatch))
493 .collect::<Result<_, _>>()?;
494 if basis.len() != t.rows.len() {
495 return Err(mismatch());
496 }
497 let mut assigned = vec![false; t.rows.len()];
501 let mut row_of = Vec::with_capacity(basis.len());
502 for &j in &basis {
503 let i = (0..t.rows.len())
504 .find(|&i| !assigned[i] && !t.rows[i][j].is_zero())
505 .ok_or_else(mismatch)?;
506 t.pivot(i, j);
507 assigned[i] = true;
508 row_of.push(i);
509 }
510 t.rows = row_of.iter().map(|&i| t.rows[i].clone()).collect();
511 t.rhs = row_of.iter().map(|&i| t.rhs[i].clone()).collect();
512 t.basis = basis;
513 t.set_objective(problem.sense, "z", &problem.objective, &Coef::zero());
514 Ok(t)
515 }
516
517 fn append_constraints(&mut self, problem: &Problem, from: usize, trace: &mut Trace) {
521 let n = problem.num_vars();
522 for (i, c) in problem.constraints.iter().enumerate().skip(from) {
523 let (coeffs, mut b): (Vec<Rational>, Rational) = match c.relation {
524 Relation::Le => (c.coeffs.clone(), c.rhs.clone()),
525 Relation::Ge => (
526 c.coeffs.iter().map(|v| -v.clone()).collect(),
527 -c.rhs.clone(),
528 ),
529 Relation::Eq => unreachable!("rejected by reoptimize"),
530 };
531 self.names.push(format!("s{}", i + 1));
532 self.artificial.push(false);
533 self.artificial_owner.push(None);
534 self.z.push(Coef::zero());
535 for row in self.rows.iter_mut() {
536 row.push(Rational::zero());
537 }
538 let width = self.names.len();
539 let mut row = vec![Rational::zero(); width];
540 row[..n].clone_from_slice(&coeffs);
541 row[width - 1] = Rational::one();
542 for (r, &j) in self.basis.iter().enumerate() {
543 let factor = row[j].clone();
544 if factor.is_zero() {
545 continue;
546 }
547 for (v, p) in row.iter_mut().zip(&self.rows[r]) {
548 *v -= &factor * p;
549 }
550 b -= &factor * &self.rhs[r];
551 }
552 let satisfied = !b.is_negative();
553 self.rows.push(row);
554 self.rhs.push(b);
555 self.basis.push(width - 1);
556 trace.push(Step::ConstraintAdded {
557 constraint: i,
558 row: self.rows.len() - 1,
559 satisfied,
560 });
561 }
562 }
563
564 fn has_artificials(&self) -> bool {
565 self.artificial.iter().any(|&a| a)
566 }
567
568 fn snapshot(&self) -> Snapshot {
569 Snapshot {
570 sense: self.sense,
571 objective_label: self.objective_label.to_string(),
572 columns: self.names.clone(),
573 basis: self.basis.clone(),
574 rows: self.rows.clone(),
575 rhs: self.rhs.clone(),
576 z: self.z.clone(),
577 z_rhs: self.z_rhs.clone(),
578 }
579 }
580
581 fn set_objective(
585 &mut self,
586 sense: Sense,
587 label: &'static str,
588 costs: &[Rational],
589 artificial_cost: &Coef,
590 ) {
591 self.sense = sense;
592 self.objective_label = label;
593 self.z = (0..self.names.len())
594 .map(|j| {
595 if j < costs.len() {
596 -Coef::constant(costs[j].clone())
597 } else if self.artificial[j] {
598 -artificial_cost.clone()
599 } else {
600 Coef::zero()
601 }
602 })
603 .collect();
604 self.z_rhs = Coef::zero();
605 for i in 0..self.rows.len() {
606 let factor = self.z[self.basis[i]].clone();
607 if factor.is_zero() {
608 continue;
609 }
610 for (zj, aij) in self.z.iter_mut().zip(&self.rows[i]) {
611 zj.sub_mul(&factor, aij);
612 }
613 self.z_rhs.sub_mul(&factor, &self.rhs[i]);
614 }
615 }
616
617 fn pivot(&mut self, row: usize, col: usize) {
618 let pivot = self.rows[row][col].clone();
619 for v in self.rows[row].iter_mut() {
620 *v /= &pivot;
621 }
622 self.rhs[row] /= &pivot;
623
624 let pivot_row = self.rows[row].clone();
625 let pivot_rhs = self.rhs[row].clone();
626 for i in 0..self.rows.len() {
627 if i == row || self.rows[i][col].is_zero() {
628 continue;
629 }
630 let factor = self.rows[i][col].clone();
631 for (v, p) in self.rows[i].iter_mut().zip(&pivot_row) {
632 *v -= &factor * p;
633 }
634 self.rhs[i] -= &factor * &pivot_rhs;
635 }
636
637 let factor = self.z[col].clone();
638 if !factor.is_zero() {
639 for (zj, p) in self.z.iter_mut().zip(&pivot_row) {
640 zj.sub_mul(&factor, p);
641 }
642 self.z_rhs.sub_mul(&factor, &pivot_rhs);
643 }
644 self.basis[row] = col;
645 }
646
647 fn entering(&self, rule: PivotRule) -> Option<usize> {
657 let sense = self.sense;
658 let improves = |j: &usize| match sense {
659 Sense::Maximize => self.z[*j].is_negative(),
660 Sense::Minimize => self.z[*j].is_positive(),
661 };
662 let improves_penalty = |j: &usize| match sense {
663 Sense::Maximize => self.z[*j].m.is_negative(),
664 Sense::Minimize => self.z[*j].m.is_positive(),
665 };
666 let mut candidates = (0..self.z.len()).filter(improves);
667 match rule {
668 PivotRule::Bland => (0..self.z.len())
669 .find(improves_penalty)
670 .or_else(|| candidates.next()),
671 PivotRule::Dantzig => candidates.min_by(|&a, &b| match sense {
673 Sense::Maximize => self.z[a].cmp(&self.z[b]),
674 Sense::Minimize => self.z[b].cmp(&self.z[a]),
675 }),
676 }
677 }
678
679 fn ratio_test(&self, col: usize) -> (Vec<RatioTest>, Option<usize>) {
681 let ratios: Vec<RatioTest> = (0..self.rows.len())
682 .map(|i| {
683 let a = &self.rows[i][col];
684 let ratio = if a.is_positive() {
685 Some(&self.rhs[i] / a)
686 } else {
687 None
688 };
689 RatioTest { row: i, ratio }
690 })
691 .collect();
692 let leaving = ratios
693 .iter()
694 .filter_map(|r| r.ratio.as_ref().map(|v| (v, self.basis[r.row], r.row)))
695 .min()
696 .map(|(_, _, row)| row);
697 (ratios, leaving)
698 }
699
700 fn optimize(
702 &mut self,
703 phase: Phase,
704 options: &Options,
705 trace: &mut Trace,
706 ) -> Result<End, Error> {
707 let mut iteration = 0;
708 loop {
709 trace.push(Step::Tableau {
710 phase,
711 iteration,
712 snapshot: self.snapshot(),
713 });
714
715 let Some(entering) = self.entering(options.pivot_rule) else {
716 return Ok(End::Optimal);
717 };
718 let (ratios, leaving_row) = self.ratio_test(entering);
719 let Some(leaving_row) = leaving_row else {
720 return Ok(End::Unbounded);
721 };
722 if iteration == options.max_iterations {
723 return Err(Error::IterationLimit(options.max_iterations));
724 }
725
726 let leaving = self.basis[leaving_row];
727 trace.push(Step::Pivot {
728 phase,
729 iteration,
730 entering,
731 entering_name: self.names[entering].clone(),
732 leaving_row,
733 leaving,
734 leaving_name: self.names[leaving].clone(),
735 pivot_value: self.rows[leaving_row][entering].clone(),
736 ratios,
737 });
738 self.pivot(leaving_row, entering);
739 iteration += 1;
740 }
741 }
742
743 fn optimize_dual(&mut self, options: &Options, trace: &mut Trace) -> Result<End, Error> {
746 let phase = Phase::Dual;
747 let mut iteration = 0;
748 loop {
749 trace.push(Step::Tableau {
750 phase,
751 iteration,
752 snapshot: self.snapshot(),
753 });
754
755 let Some(leaving_row) = self.leaving_row(options.pivot_rule) else {
756 return Ok(End::Optimal);
757 };
758 let leaving = self.basis[leaving_row];
759 let (ratios, entering) = self.dual_ratio_test(leaving_row);
760 let Some(entering) = entering else {
761 trace.push(Step::InfeasibleRow {
762 row: leaving_row,
763 basic: NamedValue::new(
764 self.names[leaving].clone(),
765 self.rhs[leaving_row].clone(),
766 ),
767 });
768 return Ok(End::Infeasible);
769 };
770 if iteration == options.max_iterations {
771 return Err(Error::IterationLimit(options.max_iterations));
772 }
773
774 trace.push(Step::DualPivot {
775 phase,
776 iteration,
777 leaving_row,
778 leaving,
779 leaving_name: self.names[leaving].clone(),
780 entering,
781 entering_name: self.names[entering].clone(),
782 pivot_value: self.rows[leaving_row][entering].clone(),
783 ratios,
784 });
785 self.pivot(leaving_row, entering);
786 iteration += 1;
787 }
788 }
789
790 fn leaving_row(&self, rule: PivotRule) -> Option<usize> {
794 let negative = (0..self.rows.len()).filter(|&i| self.rhs[i].is_negative());
795 match rule {
796 PivotRule::Dantzig => negative.min_by_key(|&i| (self.rhs[i].clone(), self.basis[i])),
797 PivotRule::Bland => negative.min_by_key(|&i| self.basis[i]),
798 }
799 }
800
801 fn dual_ratio_test(&self, row: usize) -> (Vec<DualRatioTest>, Option<usize>) {
806 let ratios: Vec<DualRatioTest> = (0..self.names.len())
807 .map(|j| {
808 let a = &self.rows[row][j];
809 let ratio = a.is_negative().then(|| {
810 debug_assert!(self.z[j].m.is_zero(), "dual simplex with a symbolic M");
811 self.z[j].c.abs() / a.abs()
812 });
813 DualRatioTest { column: j, ratio }
814 })
815 .collect();
816 let entering = ratios
817 .iter()
818 .filter_map(|r| r.ratio.as_ref().map(|v| (v, r.column)))
819 .min()
820 .map(|(_, j)| j);
821 (ratios, entering)
822 }
823
824 fn positive_artificials(&self) -> Vec<NamedValue> {
826 (0..self.rows.len())
827 .filter(|&i| self.artificial[self.basis[i]] && self.rhs[i].is_positive())
828 .map(|i| NamedValue::new(self.names[self.basis[i]].clone(), self.rhs[i].clone()))
829 .collect()
830 }
831
832 fn single_phase(
834 &mut self,
835 sense: Sense,
836 costs: &[Rational],
837 artificial_cost: Coef,
838 options: &Options,
839 trace: &mut Trace,
840 ) -> Result<End, Error> {
841 self.set_objective(sense, "z", costs, &artificial_cost);
842 let end = self.optimize(Phase::Single, options, trace)?;
843
844 let positive = self.positive_artificials();
850 if positive.is_empty() {
851 return Ok(end);
852 }
853 trace.push(Step::ArtificialRemainsPositive {
854 artificials: positive,
855 unbounded_direction: matches!(end, End::Unbounded),
856 });
857 Ok(End::Infeasible)
858 }
859
860 fn two_phase(
861 &mut self,
862 sense: Sense,
863 costs: &[Rational],
864 options: &Options,
865 trace: &mut Trace,
866 ) -> Result<End, Error> {
867 let zeros = vec![Rational::zero(); costs.len()];
869 self.set_objective(
870 Sense::Minimize,
871 "r",
872 &zeros,
873 &Coef::constant(Rational::one()),
874 );
875 self.optimize(Phase::One, options, trace)?;
877
878 let artificial_sum = self.z_rhs.c.clone();
879 let feasible = artificial_sum.is_zero();
880 trace.push(Step::PhaseOneFinished {
881 artificial_sum,
882 feasible,
883 });
884 if !feasible {
885 return Ok(End::Infeasible);
886 }
887
888 self.drop_artificials(trace);
889 self.set_objective(sense, "z", costs, &Coef::zero());
890 self.optimize(Phase::Two, options, trace)
891 }
892
893 fn drop_artificials(&mut self, trace: &mut Trace) {
896 let mut i = 0;
897 while i < self.rows.len() {
898 if !self.artificial[self.basis[i]] {
899 i += 1;
900 continue;
901 }
902 let replacement =
903 (0..self.names.len()).find(|&j| !self.artificial[j] && !self.rows[i][j].is_zero());
904 match replacement {
905 Some(j) => {
906 self.pivot(i, j);
907 trace.push(Step::ArtificialPivotedOut {
908 row: i,
909 entering: j,
910 entering_name: self.names[j].clone(),
911 });
912 i += 1;
913 }
914 None => {
915 self.removed
918 .push(self.artificial_owner[self.basis[i]].expect("artificial column"));
919 self.rows.remove(i);
920 self.rhs.remove(i);
921 self.basis.remove(i);
922 trace.push(Step::RedundantRowRemoved { row: i });
923 }
924 }
925 }
926
927 let keep: Vec<usize> = (0..self.names.len())
928 .filter(|&j| !self.artificial[j])
929 .collect();
930 let mut new_index = vec![usize::MAX; self.names.len()];
931 for (new, &old) in keep.iter().enumerate() {
932 new_index[old] = new;
933 }
934 self.names = keep.iter().map(|&j| self.names[j].clone()).collect();
935 self.artificial = vec![false; keep.len()];
936 self.artificial_owner = vec![None; keep.len()];
937 for row in self.rows.iter_mut() {
938 *row = keep.iter().map(|&j| row[j].clone()).collect();
939 }
940 for b in self.basis.iter_mut() {
941 *b = new_index[*b];
942 }
943 self.z = vec![Coef::zero(); keep.len()];
944 self.z_rhs = Coef::zero();
945 }
946
947 fn sensitivity(&self, problem: &Problem) -> Option<Sensitivity> {
956 let mut removed = self.removed.clone();
957 let mut basis = Vec::with_capacity(self.basis.len());
958 for (i, &b) in self.basis.iter().enumerate() {
959 if !self.artificial[b] {
960 basis.push(b);
961 continue;
962 }
963 let redundant =
964 (0..self.names.len()).all(|j| self.artificial[j] || self.rows[i][j].is_zero());
965 if !redundant {
966 return self.rebased_sensitivity(problem);
967 }
968 removed.push(self.artificial_owner[b].expect("artificial column"));
969 }
970 let cells = Cells::FinalTableau {
971 artificial_columns: self.has_artificials(),
972 };
973 self.analyze(problem, &basis, &removed, cells)
974 }
975
976 fn rebased_sensitivity(&self, problem: &Problem) -> Option<Sensitivity> {
977 let mut t = self.clone();
978 let mut scratch = Trace::new(problem.sense);
979 t.drop_artificials(&mut scratch);
980 t.set_objective(problem.sense, "z", &problem.objective, &Coef::zero());
981 let options = Options::default().pivot_rule(PivotRule::Bland);
982 match t.optimize(Phase::Two, &options, &mut scratch) {
983 Ok(End::Optimal) => t.analyze(problem, &t.basis, &t.removed, Cells::None),
984 _ => None, }
986 }
987
988 fn analyze(
989 &self,
990 problem: &Problem,
991 basis: &[usize],
992 removed: &[usize],
993 cells: Cells,
994 ) -> Option<Sensitivity> {
995 let start = Tableau::standard_form(problem);
996 let form = StandardForm {
997 rows: &start.rows,
998 rhs: &start.rhs,
999 names: &start.names,
1000 artificial: &start.artificial,
1001 };
1002 sensitivity::analyze(problem, form, basis, removed, cells)
1003 }
1004
1005 fn values(&self, n: usize) -> Vec<Rational> {
1006 let mut values = vec![Rational::zero(); n];
1007 for (i, &b) in self.basis.iter().enumerate() {
1008 if b < n {
1009 values[b] = self.rhs[i].clone();
1010 }
1011 }
1012 values
1013 }
1014}