1use crate::error::Error;
2use crate::num::{Coef, Rational};
3use crate::problem::{Expansion, Problem, Relation, Sense, Sign};
4use crate::sensitivity::{self, Cells, Sensitivity, StandardForm};
5use crate::trace::{
6 DualRatioTest, NamedValue, Phase, RatioTest, Snapshot, Step, Termination, Trace,
7};
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 DualSimplex,
30}
31
32#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34#[cfg_attr(feature = "serde", serde(rename_all = "snake_case"))]
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36#[non_exhaustive]
37pub enum PivotRule {
38 Dantzig,
42 Bland,
45}
46
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
56#[cfg_attr(feature = "serde", serde(default))]
57#[derive(Clone, Debug, PartialEq, Eq)]
58#[non_exhaustive]
59pub struct Options {
60 pub pivot_rule: PivotRule,
61 pub max_iterations: usize,
63}
64
65impl Default for Options {
66 fn default() -> Self {
67 Options {
68 pivot_rule: PivotRule::Dantzig,
69 max_iterations: 10_000,
70 }
71 }
72}
73
74impl Options {
75 pub fn pivot_rule(mut self, rule: PivotRule) -> Self {
76 self.pivot_rule = rule;
77 self
78 }
79
80 pub fn max_iterations(mut self, n: usize) -> Self {
81 self.max_iterations = n;
82 self
83 }
84}
85
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
91#[cfg_attr(feature = "serde", serde(tag = "status", rename_all = "snake_case"))]
92#[derive(Clone, Debug, PartialEq, Eq)]
93pub enum Status {
94 Optimal {
96 objective: Rational,
97 values: Vec<Rational>,
98 },
99 Infeasible,
100 Unbounded,
101}
102
103#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
110#[derive(Clone, Debug, PartialEq, Eq)]
111#[non_exhaustive]
112pub struct Solution {
113 #[cfg_attr(feature = "serde", serde(flatten))]
114 pub status: Status,
115 pub problem: Problem,
117 #[cfg_attr(
121 feature = "serde",
122 serde(default, skip_serializing_if = "Option::is_none")
123 )]
124 pub sensitivity: Option<Sensitivity>,
125 pub trace: Trace,
126}
127
128pub fn solve(problem: &Problem, method: Method, options: &Options) -> Result<Solution, Error> {
146 problem.validate()?;
147 let mut trace = Trace::new(problem.sense);
148 let expansion = problem.expand();
151 let columns = &expansion.problem;
152 let sense = problem.sense;
153 let costs = &columns.objective;
154
155 if method == Method::DualSimplex {
156 check_dual_feasible(problem)?;
157 let mut t = Tableau::dual_form(columns);
158 t.set_objective(sense, "z", costs, &Coef::zero());
159 let end = t.optimize_dual(options, &mut trace)?;
160 return Ok(finish(&t, problem, &expansion, end, trace));
161 }
162
163 let mut t = Tableau::standard_form(columns);
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, &expansion, 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 && (0..problem.num_vars()).all(|j| problem.sign(j) == old.sign(j))
227 && problem.constraints.len() >= m
228 && problem.constraints[..m] == old.constraints[..];
229 if !same_prefix {
230 return Err(Error::InvalidProblem(
231 "the extended problem must be the previous problem with constraints appended".into(),
232 ));
233 }
234 for (i, c) in problem.constraints.iter().enumerate().skip(m) {
235 if c.relation == Relation::Eq {
236 return Err(Error::NotDualFeasible(format!(
237 "constraint {} is an equality; added constraints must be <= or >=",
238 i + 1
239 )));
240 }
241 }
242
243 let mut trace = Trace::new(problem.sense);
244 let expansion = problem.expand();
245 let mut t = Tableau::from_basis(&old.expand().problem, sensitivity)?;
246 t.append_constraints(&expansion.problem, m, &mut trace);
247 let end = t.optimize_dual(options, &mut trace)?;
248 Ok(finish(&t, problem, &expansion, end, trace))
249}
250
251fn check_dual_feasible(problem: &Problem) -> Result<(), Error> {
256 for (i, c) in problem.constraints.iter().enumerate() {
257 if c.relation == Relation::Eq {
258 return Err(Error::NotDualFeasible(format!(
259 "constraint {} is an equality, which has no slack",
260 i + 1
261 )));
262 }
263 }
264 let (wrong, needs) = match problem.sense {
265 Sense::Maximize => (
266 Rational::is_positive as fn(&Rational) -> bool,
267 "non-positive",
268 ),
269 Sense::Minimize => (
270 Rational::is_negative as fn(&Rational) -> bool,
271 "non-negative",
272 ),
273 };
274 let when = match problem.sense {
275 Sense::Maximize => "maximizing",
276 Sense::Minimize => "minimizing",
277 };
278 for (j, c) in problem.objective.iter().enumerate() {
279 let name = problem.var_name(j);
280 match problem.sign(j) {
281 Sign::Free if !c.is_zero() => {
282 return Err(Error::NotDualFeasible(format!(
283 "{name} is free and has cost {c}; its columns {name}+ and {name}- have opposite costs, so one of them always has the wrong sign unless the cost is 0"
284 )));
285 }
286 Sign::NonPositive if wrong(&-c) => {
287 return Err(Error::NotDualFeasible(format!(
288 "{name} is non-positive and has cost {c}, so its column {name}- costs {}, but column costs must be {needs} when {when}",
289 -c
290 )));
291 }
292 Sign::NonNegative if wrong(c) => {
293 return Err(Error::NotDualFeasible(format!(
294 "{name} has cost {c}, but the costs must be {needs} when {when}"
295 )));
296 }
297 _ => {}
298 }
299 }
300 Ok(())
301}
302
303fn finish(
306 t: &Tableau,
307 problem: &Problem,
308 expansion: &Expansion,
309 end: End,
310 mut trace: Trace,
311) -> Solution {
312 let sensitivity = match end {
313 End::Optimal => t.sensitivity(problem, expansion),
314 _ => None,
315 };
316 let (status, termination) = match end {
317 End::Optimal => {
318 let columns = t.values(expansion.problem.num_vars());
319 let status = Status::Optimal {
320 objective: t.z_rhs.c.clone(),
321 values: expansion.combine(&columns),
322 };
323 (status, Termination::Optimal)
324 }
325 End::Infeasible => (Status::Infeasible, Termination::Infeasible),
326 End::Unbounded => (Status::Unbounded, Termination::Unbounded),
327 };
328 trace.push(Step::Finished { termination });
329 Solution {
330 status,
331 problem: problem.clone(),
332 sensitivity,
333 trace,
334 }
335}
336
337enum End {
338 Optimal,
339 Infeasible,
340 Unbounded,
341}
342
343#[derive(Clone)]
344struct Tableau {
345 sense: Sense,
347 objective_label: &'static str,
348 names: Vec<String>,
349 artificial: Vec<bool>,
350 artificial_owner: Vec<Option<usize>>,
352 removed: Vec<usize>,
354 rows: Vec<Vec<Rational>>,
355 rhs: Vec<Rational>,
356 basis: Vec<usize>,
357 z: Vec<Coef>,
358 z_rhs: Coef,
359}
360
361impl Tableau {
362 fn standard_form(problem: &Problem) -> Self {
366 let n = problem.num_vars();
367 let normalized: Vec<(Vec<Rational>, Relation, Rational)> = problem
368 .constraints
369 .iter()
370 .map(|c| {
371 if c.rhs.is_negative() {
372 let flipped = match c.relation {
373 Relation::Le => Relation::Ge,
374 Relation::Ge => Relation::Le,
375 Relation::Eq => Relation::Eq,
376 };
377 (
378 c.coeffs.iter().map(|v| -v.clone()).collect(),
379 flipped,
380 -c.rhs.clone(),
381 )
382 } else {
383 (c.coeffs.clone(), c.relation, c.rhs.clone())
384 }
385 })
386 .collect();
387
388 let mut names: Vec<String> = (0..n).map(|j| problem.var_name(j)).collect();
389 let mut slack_col = vec![None; normalized.len()];
390 for (i, (_, relation, _)) in normalized.iter().enumerate() {
391 if *relation != Relation::Eq {
392 slack_col[i] = Some(names.len());
393 names.push(format!("s{}", i + 1));
394 }
395 }
396 let first_artificial = names.len();
397 let mut art_col = vec![None; normalized.len()];
398 let mut artificial_owner = vec![None; first_artificial];
399 for (i, (_, relation, _)) in normalized.iter().enumerate() {
400 if *relation != Relation::Le {
401 art_col[i] = Some(names.len());
402 artificial_owner.push(Some(i));
403 names.push(format!("a{}", i + 1));
404 }
405 }
406
407 let width = names.len();
408 let mut rows = Vec::with_capacity(normalized.len());
409 let mut rhs = Vec::with_capacity(normalized.len());
410 let mut basis = Vec::with_capacity(normalized.len());
411 for (i, (coeffs, relation, b)) in normalized.into_iter().enumerate() {
412 let mut row = vec![Rational::zero(); width];
413 row[..n].clone_from_slice(&coeffs);
414 if let Some(s) = slack_col[i] {
415 row[s] = if relation == Relation::Le {
416 Rational::one()
417 } else {
418 -Rational::one()
419 };
420 }
421 if let Some(a) = art_col[i] {
422 row[a] = Rational::one();
423 }
424 basis.push(
425 art_col[i]
426 .or(slack_col[i])
427 .expect("every row has a basic column"),
428 );
429 rows.push(row);
430 rhs.push(b);
431 }
432
433 Tableau {
434 sense: problem.sense,
435 objective_label: "z",
436 artificial: (0..width).map(|j| j >= first_artificial).collect(),
437 artificial_owner,
438 removed: Vec::new(),
439 names,
440 rows,
441 rhs,
442 basis,
443 z: vec![Coef::zero(); width],
444 z_rhs: Coef::zero(),
445 }
446 }
447
448 fn dual_form(problem: &Problem) -> Self {
452 let n = problem.num_vars();
453 let m = problem.constraints.len();
454 let width = n + m;
455 let mut names: Vec<String> = (0..n).map(|j| problem.var_name(j)).collect();
456 names.extend((0..m).map(|i| format!("s{}", i + 1)));
457
458 let mut rows = Vec::with_capacity(m);
459 let mut rhs = Vec::with_capacity(m);
460 for (i, c) in problem.constraints.iter().enumerate() {
461 let mut row = vec![Rational::zero(); width];
462 let (coeffs, b) = match c.relation {
463 Relation::Le => (c.coeffs.clone(), c.rhs.clone()),
464 Relation::Ge => (
465 c.coeffs.iter().map(|v| -v.clone()).collect(),
466 -c.rhs.clone(),
467 ),
468 Relation::Eq => unreachable!("rejected by check_dual_feasible"),
469 };
470 row[..n].clone_from_slice(&coeffs);
471 row[n + i] = Rational::one();
472 rows.push(row);
473 rhs.push(b);
474 }
475
476 Tableau {
477 sense: problem.sense,
478 objective_label: "z",
479 artificial: vec![false; width],
480 artificial_owner: vec![None; width],
481 removed: Vec::new(),
482 names,
483 rows,
484 rhs,
485 basis: (n..width).collect(),
486 z: vec![Coef::zero(); width],
487 z_rhs: Coef::zero(),
488 }
489 }
490
491 fn from_basis(problem: &Problem, sensitivity: &Sensitivity) -> Result<Self, Error> {
495 let mismatch =
496 || Error::InvalidProblem("the previous solution does not belong to its problem".into());
497 let mut t = Tableau::standard_form(problem);
498 let real = t
499 .artificial
500 .iter()
501 .position(|&a| a)
502 .unwrap_or(t.names.len());
503 t.names.truncate(real);
504 t.artificial.truncate(real);
505 t.artificial_owner.truncate(real);
506 for row in t.rows.iter_mut() {
507 row.truncate(real);
508 }
509 t.z = vec![Coef::zero(); real];
510
511 if sensitivity.constraints.len() != t.rows.len() {
512 return Err(mismatch());
513 }
514 t.removed = (0..t.rows.len())
515 .filter(|&i| sensitivity.constraints[i].redundant)
516 .collect();
517 for &i in t.removed.iter().rev() {
518 t.rows.remove(i);
519 t.rhs.remove(i);
520 }
521
522 let basis: Vec<usize> = sensitivity
523 .basic_variables
524 .iter()
525 .map(|name| t.names.iter().position(|n| n == name).ok_or_else(mismatch))
526 .collect::<Result<_, _>>()?;
527 if basis.len() != t.rows.len() {
528 return Err(mismatch());
529 }
530 let mut assigned = vec![false; t.rows.len()];
534 let mut row_of = Vec::with_capacity(basis.len());
535 for &j in &basis {
536 let i = (0..t.rows.len())
537 .find(|&i| !assigned[i] && !t.rows[i][j].is_zero())
538 .ok_or_else(mismatch)?;
539 t.pivot(i, j);
540 assigned[i] = true;
541 row_of.push(i);
542 }
543 t.rows = row_of.iter().map(|&i| t.rows[i].clone()).collect();
544 t.rhs = row_of.iter().map(|&i| t.rhs[i].clone()).collect();
545 t.basis = basis;
546 t.set_objective(problem.sense, "z", &problem.objective, &Coef::zero());
547 Ok(t)
548 }
549
550 fn append_constraints(&mut self, problem: &Problem, from: usize, trace: &mut Trace) {
554 let n = problem.num_vars();
555 for (i, c) in problem.constraints.iter().enumerate().skip(from) {
556 let (coeffs, mut b): (Vec<Rational>, Rational) = match c.relation {
557 Relation::Le => (c.coeffs.clone(), c.rhs.clone()),
558 Relation::Ge => (
559 c.coeffs.iter().map(|v| -v.clone()).collect(),
560 -c.rhs.clone(),
561 ),
562 Relation::Eq => unreachable!("rejected by reoptimize"),
563 };
564 self.names.push(format!("s{}", i + 1));
565 self.artificial.push(false);
566 self.artificial_owner.push(None);
567 self.z.push(Coef::zero());
568 for row in self.rows.iter_mut() {
569 row.push(Rational::zero());
570 }
571 let width = self.names.len();
572 let mut row = vec![Rational::zero(); width];
573 row[..n].clone_from_slice(&coeffs);
574 row[width - 1] = Rational::one();
575 for (r, &j) in self.basis.iter().enumerate() {
576 let factor = row[j].clone();
577 if factor.is_zero() {
578 continue;
579 }
580 for (v, p) in row.iter_mut().zip(&self.rows[r]) {
581 *v -= &factor * p;
582 }
583 b -= &factor * &self.rhs[r];
584 }
585 let satisfied = !b.is_negative();
586 self.rows.push(row);
587 self.rhs.push(b);
588 self.basis.push(width - 1);
589 trace.push(Step::ConstraintAdded {
590 constraint: i,
591 row: self.rows.len() - 1,
592 satisfied,
593 });
594 }
595 }
596
597 fn has_artificials(&self) -> bool {
598 self.artificial.iter().any(|&a| a)
599 }
600
601 fn snapshot(&self) -> Snapshot {
602 Snapshot {
603 sense: self.sense,
604 objective_label: self.objective_label.to_string(),
605 columns: self.names.clone(),
606 basis: self.basis.clone(),
607 rows: self.rows.clone(),
608 rhs: self.rhs.clone(),
609 z: self.z.clone(),
610 z_rhs: self.z_rhs.clone(),
611 }
612 }
613
614 fn set_objective(
618 &mut self,
619 sense: Sense,
620 label: &'static str,
621 costs: &[Rational],
622 artificial_cost: &Coef,
623 ) {
624 self.sense = sense;
625 self.objective_label = label;
626 self.z = (0..self.names.len())
627 .map(|j| {
628 if j < costs.len() {
629 -Coef::constant(costs[j].clone())
630 } else if self.artificial[j] {
631 -artificial_cost.clone()
632 } else {
633 Coef::zero()
634 }
635 })
636 .collect();
637 self.z_rhs = Coef::zero();
638 for i in 0..self.rows.len() {
639 let factor = self.z[self.basis[i]].clone();
640 if factor.is_zero() {
641 continue;
642 }
643 for (zj, aij) in self.z.iter_mut().zip(&self.rows[i]) {
644 zj.sub_mul(&factor, aij);
645 }
646 self.z_rhs.sub_mul(&factor, &self.rhs[i]);
647 }
648 }
649
650 fn pivot(&mut self, row: usize, col: usize) {
651 let pivot = self.rows[row][col].clone();
652 for v in self.rows[row].iter_mut() {
653 *v /= &pivot;
654 }
655 self.rhs[row] /= &pivot;
656
657 let pivot_row = self.rows[row].clone();
658 let pivot_rhs = self.rhs[row].clone();
659 for i in 0..self.rows.len() {
660 if i == row || self.rows[i][col].is_zero() {
661 continue;
662 }
663 let factor = self.rows[i][col].clone();
664 for (v, p) in self.rows[i].iter_mut().zip(&pivot_row) {
665 *v -= &factor * p;
666 }
667 self.rhs[i] -= &factor * &pivot_rhs;
668 }
669
670 let factor = self.z[col].clone();
671 if !factor.is_zero() {
672 for (zj, p) in self.z.iter_mut().zip(&pivot_row) {
673 zj.sub_mul(&factor, p);
674 }
675 self.z_rhs.sub_mul(&factor, &pivot_rhs);
676 }
677 self.basis[row] = col;
678 }
679
680 fn entering(&self, rule: PivotRule) -> Option<usize> {
690 let sense = self.sense;
691 let improves = |j: &usize| match sense {
692 Sense::Maximize => self.z[*j].is_negative(),
693 Sense::Minimize => self.z[*j].is_positive(),
694 };
695 let improves_penalty = |j: &usize| match sense {
696 Sense::Maximize => self.z[*j].m.is_negative(),
697 Sense::Minimize => self.z[*j].m.is_positive(),
698 };
699 let mut candidates = (0..self.z.len()).filter(improves);
700 match rule {
701 PivotRule::Bland => (0..self.z.len())
702 .find(improves_penalty)
703 .or_else(|| candidates.next()),
704 PivotRule::Dantzig => candidates.min_by(|&a, &b| match sense {
706 Sense::Maximize => self.z[a].cmp(&self.z[b]),
707 Sense::Minimize => self.z[b].cmp(&self.z[a]),
708 }),
709 }
710 }
711
712 fn ratio_test(&self, col: usize) -> (Vec<RatioTest>, Option<usize>) {
714 let ratios: Vec<RatioTest> = (0..self.rows.len())
715 .map(|i| {
716 let a = &self.rows[i][col];
717 let ratio = if a.is_positive() {
718 Some(&self.rhs[i] / a)
719 } else {
720 None
721 };
722 RatioTest { row: i, ratio }
723 })
724 .collect();
725 let leaving = ratios
726 .iter()
727 .filter_map(|r| r.ratio.as_ref().map(|v| (v, self.basis[r.row], r.row)))
728 .min()
729 .map(|(_, _, row)| row);
730 (ratios, leaving)
731 }
732
733 fn optimize(
735 &mut self,
736 phase: Phase,
737 options: &Options,
738 trace: &mut Trace,
739 ) -> Result<End, Error> {
740 let mut iteration = 0;
741 loop {
742 trace.push(Step::Tableau {
743 phase,
744 iteration,
745 snapshot: self.snapshot(),
746 });
747
748 let Some(entering) = self.entering(options.pivot_rule) else {
749 return Ok(End::Optimal);
750 };
751 let (ratios, leaving_row) = self.ratio_test(entering);
752 let Some(leaving_row) = leaving_row else {
753 return Ok(End::Unbounded);
754 };
755 if iteration == options.max_iterations {
756 return Err(Error::IterationLimit(options.max_iterations));
757 }
758
759 let leaving = self.basis[leaving_row];
760 trace.push(Step::Pivot {
761 phase,
762 iteration,
763 entering,
764 entering_name: self.names[entering].clone(),
765 leaving_row,
766 leaving,
767 leaving_name: self.names[leaving].clone(),
768 pivot_value: self.rows[leaving_row][entering].clone(),
769 ratios,
770 });
771 self.pivot(leaving_row, entering);
772 iteration += 1;
773 }
774 }
775
776 fn optimize_dual(&mut self, options: &Options, trace: &mut Trace) -> Result<End, Error> {
779 let phase = Phase::Dual;
780 let mut iteration = 0;
781 loop {
782 trace.push(Step::Tableau {
783 phase,
784 iteration,
785 snapshot: self.snapshot(),
786 });
787
788 let Some(leaving_row) = self.leaving_row(options.pivot_rule) else {
789 return Ok(End::Optimal);
790 };
791 let leaving = self.basis[leaving_row];
792 let (ratios, entering) = self.dual_ratio_test(leaving_row);
793 let Some(entering) = entering else {
794 trace.push(Step::InfeasibleRow {
795 row: leaving_row,
796 basic: NamedValue::new(
797 self.names[leaving].clone(),
798 self.rhs[leaving_row].clone(),
799 ),
800 });
801 return Ok(End::Infeasible);
802 };
803 if iteration == options.max_iterations {
804 return Err(Error::IterationLimit(options.max_iterations));
805 }
806
807 trace.push(Step::DualPivot {
808 phase,
809 iteration,
810 leaving_row,
811 leaving,
812 leaving_name: self.names[leaving].clone(),
813 entering,
814 entering_name: self.names[entering].clone(),
815 pivot_value: self.rows[leaving_row][entering].clone(),
816 ratios,
817 });
818 self.pivot(leaving_row, entering);
819 iteration += 1;
820 }
821 }
822
823 fn leaving_row(&self, rule: PivotRule) -> Option<usize> {
827 let negative = (0..self.rows.len()).filter(|&i| self.rhs[i].is_negative());
828 match rule {
829 PivotRule::Dantzig => negative.min_by_key(|&i| (self.rhs[i].clone(), self.basis[i])),
830 PivotRule::Bland => negative.min_by_key(|&i| self.basis[i]),
831 }
832 }
833
834 fn dual_ratio_test(&self, row: usize) -> (Vec<DualRatioTest>, Option<usize>) {
839 let ratios: Vec<DualRatioTest> = (0..self.names.len())
840 .map(|j| {
841 let a = &self.rows[row][j];
842 let ratio = a.is_negative().then(|| {
843 debug_assert!(self.z[j].m.is_zero(), "dual simplex with a symbolic M");
844 self.z[j].c.abs() / a.abs()
845 });
846 DualRatioTest { column: j, ratio }
847 })
848 .collect();
849 let entering = ratios
850 .iter()
851 .filter_map(|r| r.ratio.as_ref().map(|v| (v, r.column)))
852 .min()
853 .map(|(_, j)| j);
854 (ratios, entering)
855 }
856
857 fn positive_artificials(&self) -> Vec<NamedValue> {
859 (0..self.rows.len())
860 .filter(|&i| self.artificial[self.basis[i]] && self.rhs[i].is_positive())
861 .map(|i| NamedValue::new(self.names[self.basis[i]].clone(), self.rhs[i].clone()))
862 .collect()
863 }
864
865 fn single_phase(
867 &mut self,
868 sense: Sense,
869 costs: &[Rational],
870 artificial_cost: Coef,
871 options: &Options,
872 trace: &mut Trace,
873 ) -> Result<End, Error> {
874 self.set_objective(sense, "z", costs, &artificial_cost);
875 let end = self.optimize(Phase::Single, options, trace)?;
876
877 let positive = self.positive_artificials();
883 if positive.is_empty() {
884 return Ok(end);
885 }
886 trace.push(Step::ArtificialRemainsPositive {
887 artificials: positive,
888 unbounded_direction: matches!(end, End::Unbounded),
889 });
890 Ok(End::Infeasible)
891 }
892
893 fn two_phase(
894 &mut self,
895 sense: Sense,
896 costs: &[Rational],
897 options: &Options,
898 trace: &mut Trace,
899 ) -> Result<End, Error> {
900 let zeros = vec![Rational::zero(); costs.len()];
902 self.set_objective(
903 Sense::Minimize,
904 "r",
905 &zeros,
906 &Coef::constant(Rational::one()),
907 );
908 self.optimize(Phase::One, options, trace)?;
910
911 let artificial_sum = self.z_rhs.c.clone();
912 let feasible = artificial_sum.is_zero();
913 trace.push(Step::PhaseOneFinished {
914 artificial_sum,
915 feasible,
916 });
917 if !feasible {
918 return Ok(End::Infeasible);
919 }
920
921 self.drop_artificials(trace);
922 self.set_objective(sense, "z", costs, &Coef::zero());
923 self.optimize(Phase::Two, options, trace)
924 }
925
926 fn drop_artificials(&mut self, trace: &mut Trace) {
929 let mut i = 0;
930 while i < self.rows.len() {
931 if !self.artificial[self.basis[i]] {
932 i += 1;
933 continue;
934 }
935 let replacement =
936 (0..self.names.len()).find(|&j| !self.artificial[j] && !self.rows[i][j].is_zero());
937 match replacement {
938 Some(j) => {
939 self.pivot(i, j);
940 trace.push(Step::ArtificialPivotedOut {
941 row: i,
942 entering: j,
943 entering_name: self.names[j].clone(),
944 });
945 i += 1;
946 }
947 None => {
948 self.removed
951 .push(self.artificial_owner[self.basis[i]].expect("artificial column"));
952 self.rows.remove(i);
953 self.rhs.remove(i);
954 self.basis.remove(i);
955 trace.push(Step::RedundantRowRemoved { row: i });
956 }
957 }
958 }
959
960 let keep: Vec<usize> = (0..self.names.len())
961 .filter(|&j| !self.artificial[j])
962 .collect();
963 let mut new_index = vec![usize::MAX; self.names.len()];
964 for (new, &old) in keep.iter().enumerate() {
965 new_index[old] = new;
966 }
967 self.names = keep.iter().map(|&j| self.names[j].clone()).collect();
968 self.artificial = vec![false; keep.len()];
969 self.artificial_owner = vec![None; keep.len()];
970 for row in self.rows.iter_mut() {
971 *row = keep.iter().map(|&j| row[j].clone()).collect();
972 }
973 for b in self.basis.iter_mut() {
974 *b = new_index[*b];
975 }
976 self.z = vec![Coef::zero(); keep.len()];
977 self.z_rhs = Coef::zero();
978 }
979
980 fn sensitivity(&self, original: &Problem, expansion: &Expansion) -> Option<Sensitivity> {
989 let mut removed = self.removed.clone();
990 let mut basis = Vec::with_capacity(self.basis.len());
991 for (i, &b) in self.basis.iter().enumerate() {
992 if !self.artificial[b] {
993 basis.push(b);
994 continue;
995 }
996 let redundant =
997 (0..self.names.len()).all(|j| self.artificial[j] || self.rows[i][j].is_zero());
998 if !redundant {
999 return self.rebased_sensitivity(original, expansion);
1000 }
1001 removed.push(self.artificial_owner[b].expect("artificial column"));
1002 }
1003 let cells = Cells::FinalTableau {
1004 artificial_columns: self.has_artificials(),
1005 };
1006 self.analyze(original, expansion, &basis, &removed, cells)
1007 }
1008
1009 fn rebased_sensitivity(
1010 &self,
1011 original: &Problem,
1012 expansion: &Expansion,
1013 ) -> Option<Sensitivity> {
1014 let columns = &expansion.problem;
1015 let mut t = self.clone();
1016 let mut scratch = Trace::new(columns.sense);
1017 t.drop_artificials(&mut scratch);
1018 t.set_objective(columns.sense, "z", &columns.objective, &Coef::zero());
1019 let options = Options::default().pivot_rule(PivotRule::Bland);
1020 match t.optimize(Phase::Two, &options, &mut scratch) {
1021 Ok(End::Optimal) => t.analyze(original, expansion, &t.basis, &t.removed, Cells::None),
1022 _ => None, }
1024 }
1025
1026 fn analyze(
1027 &self,
1028 original: &Problem,
1029 expansion: &Expansion,
1030 basis: &[usize],
1031 removed: &[usize],
1032 cells: Cells,
1033 ) -> Option<Sensitivity> {
1034 let start = Tableau::standard_form(&expansion.problem);
1035 let form = StandardForm {
1036 rows: &start.rows,
1037 rhs: &start.rhs,
1038 names: &start.names,
1039 artificial: &start.artificial,
1040 };
1041 sensitivity::analyze(original, expansion, form, basis, removed, cells)
1042 }
1043
1044 fn values(&self, n: usize) -> Vec<Rational> {
1045 let mut values = vec![Rational::zero(); n];
1046 for (i, &b) in self.basis.iter().enumerate() {
1047 if b < n {
1048 values[b] = self.rhs[i].clone();
1049 }
1050 }
1051 values
1052 }
1053}