1use num_bigint::BigInt;
13use num_rational::BigRational;
14use num_traits::{One, Signed, Zero};
15use rustc_hash::{FxHashMap, FxHashSet};
16use std::fmt;
17
18pub type VarId = u32;
20
21pub type ConstraintId = u32;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum BoundType {
27 Lower,
29 Upper,
31 Equal,
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct Bound {
38 pub var: VarId,
40 pub bound_type: BoundType,
42 pub value: BigRational,
44}
45
46impl Bound {
47 pub fn lower(var: VarId, value: BigRational) -> Self {
49 Self {
50 var,
51 bound_type: BoundType::Lower,
52 value,
53 }
54 }
55
56 pub fn upper(var: VarId, value: BigRational) -> Self {
58 Self {
59 var,
60 bound_type: BoundType::Upper,
61 value,
62 }
63 }
64
65 pub fn equal(var: VarId, value: BigRational) -> Self {
67 Self {
68 var,
69 bound_type: BoundType::Equal,
70 value,
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
77pub enum VarClass {
78 Basic,
80 NonBasic,
82}
83
84#[derive(Debug, Clone)]
86pub struct Row {
87 pub basic_var: VarId,
89 pub constant: BigRational,
91 pub coeffs: FxHashMap<VarId, BigRational>,
93}
94
95impl Row {
96 pub fn new(basic_var: VarId) -> Self {
98 Self {
99 basic_var,
100 constant: BigRational::zero(),
101 coeffs: FxHashMap::default(),
102 }
103 }
104
105 pub fn from_expr(
107 basic_var: VarId,
108 constant: BigRational,
109 coeffs: FxHashMap<VarId, BigRational>,
110 ) -> Self {
111 let mut row = Self {
112 basic_var,
113 constant,
114 coeffs: FxHashMap::default(),
115 };
116 for (var, coeff) in coeffs {
117 if !coeff.is_zero() {
118 row.coeffs.insert(var, coeff);
119 }
120 }
121 row
122 }
123
124 pub fn eval(&self, non_basic_values: &FxHashMap<VarId, BigRational>) -> BigRational {
126 let mut value = self.constant.clone();
127 for (var, coeff) in &self.coeffs {
128 if let Some(val) = non_basic_values.get(var) {
129 value += coeff * val;
130 }
131 }
132 value
133 }
134
135 pub fn add_row(&mut self, multiplier: &BigRational, other: &Row) {
138 if multiplier.is_zero() {
139 return;
140 }
141
142 self.constant += multiplier * &other.constant;
143
144 for (var, coeff) in &other.coeffs {
145 let new_coeff = self
146 .coeffs
147 .get(var)
148 .cloned()
149 .unwrap_or_else(BigRational::zero)
150 + multiplier * coeff;
151 if new_coeff.is_zero() {
152 self.coeffs.remove(var);
153 } else {
154 self.coeffs.insert(*var, new_coeff);
155 }
156 }
157 }
158
159 pub fn scale(&mut self, scalar: &BigRational) {
161 if scalar.is_zero() {
162 self.constant = BigRational::zero();
163 self.coeffs.clear();
164 return;
165 }
166
167 self.constant *= scalar;
168 for coeff in self.coeffs.values_mut() {
169 *coeff *= scalar;
170 }
171 }
172
173 pub fn substitute(&mut self, var: VarId, row: &Row) {
176 if let Some(coeff) = self.coeffs.remove(&var) {
177 self.add_row(&coeff, row);
179 }
180 }
181
182 pub fn is_valid(&self, basic_vars: &FxHashSet<VarId>) -> bool {
184 for var in self.coeffs.keys() {
185 if basic_vars.contains(var) {
186 return false;
187 }
188 }
189 true
190 }
191
192 pub fn normalize(&mut self) {
194 if self.coeffs.is_empty() {
195 return;
196 }
197
198 let mut gcd: Option<BigInt> = None;
200 for coeff in self.coeffs.values() {
201 if !coeff.is_zero() {
202 let num = coeff.numer().clone();
203 gcd = Some(match gcd {
204 None => num.abs(),
205 Some(g) => gcd_bigint(g, num.abs()),
206 });
207 }
208 }
209
210 if let Some(g) = gcd
211 && !g.is_one()
212 {
213 let divisor = BigRational::from_integer(g);
214 self.scale(&(BigRational::one() / divisor));
215 }
216 }
217}
218
219impl fmt::Display for Row {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 write!(f, "x{} = {}", self.basic_var, self.constant)?;
222 for (var, coeff) in &self.coeffs {
223 if coeff.is_positive() {
224 write!(f, " + {}*x{}", coeff, var)?;
225 } else {
226 write!(f, " - {}*x{}", -coeff, var)?;
227 }
228 }
229 Ok(())
230 }
231}
232
233fn gcd_bigint(mut a: BigInt, mut b: BigInt) -> BigInt {
235 while !b.is_zero() {
236 let t = &a % &b;
237 a = b;
238 b = t;
239 }
240 a.abs()
241}
242
243#[derive(Debug, Clone, PartialEq, Eq)]
245pub enum SimplexResult {
246 Sat,
248 Unsat,
250 Unbounded,
252 Unknown,
254}
255
256#[derive(Debug, Clone)]
258pub struct Conflict {
259 pub constraints: Vec<ConstraintId>,
261}
262
263#[derive(Debug, Clone)]
265pub struct SimplexTableau {
266 rows: FxHashMap<VarId, Row>,
268 basic_vars: FxHashSet<VarId>,
270 non_basic_vars: FxHashSet<VarId>,
272 assignment: FxHashMap<VarId, BigRational>,
274 lower_bounds: FxHashMap<VarId, BigRational>,
276 upper_bounds: FxHashMap<VarId, BigRational>,
278 var_to_constraints: FxHashMap<VarId, Vec<ConstraintId>>,
280 next_var_id: VarId,
282 use_blands_rule: bool,
284}
285
286impl SimplexTableau {
287 pub fn new() -> Self {
289 Self {
290 rows: FxHashMap::default(),
291 basic_vars: FxHashSet::default(),
292 non_basic_vars: FxHashSet::default(),
293 assignment: FxHashMap::default(),
294 lower_bounds: FxHashMap::default(),
295 upper_bounds: FxHashMap::default(),
296 var_to_constraints: FxHashMap::default(),
297 next_var_id: 0,
298 use_blands_rule: true,
299 }
300 }
301
302 pub fn fresh_var(&mut self) -> VarId {
304 let id = self.next_var_id;
305 self.next_var_id += 1;
306 self.non_basic_vars.insert(id);
307 self.assignment.insert(id, BigRational::zero());
308 id
309 }
310
311 pub fn add_var(&mut self, lower: Option<BigRational>, upper: Option<BigRational>) -> VarId {
313 let var = self.fresh_var();
314 if let Some(lb) = lower {
315 self.lower_bounds.insert(var, lb.clone());
316 self.assignment.insert(var, lb);
317 }
318 if let Some(ub) = upper {
319 self.upper_bounds.insert(var, ub);
320 }
321 var
322 }
323
324 pub fn add_row(&mut self, row: Row) -> Result<(), String> {
327 let basic_var = row.basic_var;
328
329 if self.basic_vars.contains(&basic_var) {
331 return Err(format!("Variable x{} is already basic", basic_var));
332 }
333
334 for var in row.coeffs.keys() {
336 if self.basic_vars.contains(var) {
337 return Err(format!("Variable x{} appears in RHS but is basic", var));
338 }
339 self.non_basic_vars.insert(*var);
340 }
341
342 self.non_basic_vars.remove(&basic_var);
344 self.basic_vars.insert(basic_var);
345
346 let value = row.eval(&self.assignment);
348 self.assignment.insert(basic_var, value);
349
350 self.rows.insert(basic_var, row);
351 Ok(())
352 }
353
354 pub fn add_bound(
356 &mut self,
357 var: VarId,
358 bound_type: BoundType,
359 value: BigRational,
360 constraint_id: ConstraintId,
361 ) -> Result<(), Conflict> {
362 self.var_to_constraints
363 .entry(var)
364 .or_default()
365 .push(constraint_id);
366
367 match bound_type {
368 BoundType::Lower => {
369 let current_lb = self.lower_bounds.get(&var);
370 if let Some(lb) = current_lb {
371 if &value > lb {
372 self.lower_bounds.insert(var, value.clone());
373 }
374 } else {
375 self.lower_bounds.insert(var, value.clone());
376 }
377
378 if let Some(ub) = self.upper_bounds.get(&var)
380 && &value > ub
381 {
382 return Err(Conflict {
383 constraints: vec![constraint_id],
384 });
385 }
386 }
387 BoundType::Upper => {
388 let current_ub = self.upper_bounds.get(&var);
389 if let Some(ub) = current_ub {
390 if &value < ub {
391 self.upper_bounds.insert(var, value.clone());
392 }
393 } else {
394 self.upper_bounds.insert(var, value.clone());
395 }
396
397 if let Some(lb) = self.lower_bounds.get(&var)
399 && &value < lb
400 {
401 return Err(Conflict {
402 constraints: vec![constraint_id],
403 });
404 }
405 }
406 BoundType::Equal => {
407 self.lower_bounds.insert(var, value.clone());
408 self.upper_bounds.insert(var, value.clone());
409
410 if let Some(lb) = self.lower_bounds.get(&var)
412 && &value < lb
413 {
414 return Err(Conflict {
415 constraints: vec![constraint_id],
416 });
417 }
418 if let Some(ub) = self.upper_bounds.get(&var)
419 && &value > ub
420 {
421 return Err(Conflict {
422 constraints: vec![constraint_id],
423 });
424 }
425 }
426 }
427
428 Ok(())
429 }
430
431 pub fn get_value(&self, var: VarId) -> Option<&BigRational> {
433 self.assignment.get(&var)
434 }
435
436 fn violates_bounds(&self, var: VarId) -> bool {
438 if let Some(val) = self.assignment.get(&var) {
439 if let Some(lb) = self.lower_bounds.get(&var)
440 && val < lb
441 {
442 return true;
443 }
444 if let Some(ub) = self.upper_bounds.get(&var)
445 && val > ub
446 {
447 return true;
448 }
449 }
450 false
451 }
452
453 fn find_violating_basic_var(&self) -> Option<VarId> {
455 if self.use_blands_rule {
456 self.basic_vars
458 .iter()
459 .filter(|&&var| self.violates_bounds(var))
460 .min()
461 .copied()
462 } else {
463 self.basic_vars
464 .iter()
465 .find(|&&var| self.violates_bounds(var))
466 .copied()
467 }
468 }
469
470 fn find_pivot_non_basic(
473 &self,
474 basic_var: VarId,
475 target_increase: bool,
476 ) -> Option<(VarId, bool)> {
477 let row = self.rows.get(&basic_var)?;
478
479 let mut candidates = Vec::new();
480
481 for (nb_var, coeff) in &row.coeffs {
482 let current_val = self.assignment.get(nb_var)?;
483 let lb = self.lower_bounds.get(nb_var);
484 let ub = self.upper_bounds.get(nb_var);
485
486 let can_increase = ub.is_none_or(|bound| bound > current_val);
488 let can_decrease = lb.is_none_or(|bound| bound < current_val);
489
490 let increases_basic = coeff.is_positive();
493
494 if target_increase {
495 if increases_basic && can_increase {
497 candidates.push((*nb_var, true));
498 } else if !increases_basic && can_decrease {
499 candidates.push((*nb_var, false));
500 }
501 } else {
502 if increases_basic && can_decrease {
504 candidates.push((*nb_var, false));
505 } else if !increases_basic && can_increase {
506 candidates.push((*nb_var, true));
507 }
508 }
509 }
510
511 if candidates.is_empty() {
512 return None;
513 }
514
515 if self.use_blands_rule {
517 candidates.sort_by_key(|(var, _)| *var);
518 }
519
520 Some(candidates[0])
521 }
522
523 pub fn pivot(&mut self, basic_var: VarId, non_basic_var: VarId) -> Result<(), String> {
526 let row = self
528 .rows
529 .get(&basic_var)
530 .ok_or_else(|| format!("No row for basic variable x{}", basic_var))?;
531
532 let coeff = row
534 .coeffs
535 .get(&non_basic_var)
536 .ok_or_else(|| {
537 format!(
538 "Non-basic variable x{} not in row for x{}",
539 non_basic_var, basic_var
540 )
541 })?
542 .clone();
543
544 if coeff.is_zero() {
545 return Err(format!("Coefficient of x{} is zero", non_basic_var));
546 }
547
548 let mut new_row = Row::new(non_basic_var);
553 new_row.constant = -&row.constant / &coeff;
554 new_row
555 .coeffs
556 .insert(basic_var, BigRational::one() / &coeff);
557
558 for (var, c) in &row.coeffs {
559 if var != &non_basic_var {
560 new_row.coeffs.insert(*var, -c / &coeff);
561 }
562 }
563
564 let rows_to_update: Vec<VarId> = self
566 .rows
567 .keys()
568 .filter(|&&v| v != basic_var)
569 .copied()
570 .collect();
571
572 for row_var in rows_to_update {
573 if let Some(r) = self.rows.get_mut(&row_var) {
574 r.substitute(non_basic_var, &new_row);
575 }
576 }
577
578 self.rows.remove(&basic_var);
580 self.rows.insert(non_basic_var, new_row);
581
582 self.basic_vars.remove(&basic_var);
584 self.basic_vars.insert(non_basic_var);
585 self.non_basic_vars.remove(&non_basic_var);
586 self.non_basic_vars.insert(basic_var);
587
588 self.update_assignment();
590
591 Ok(())
592 }
593
594 fn update_assignment(&mut self) {
596 for (basic_var, row) in &self.rows {
598 let value = row.eval(&self.assignment);
599 self.assignment.insert(*basic_var, value);
600 }
601 }
602
603 pub fn check(&mut self) -> Result<SimplexResult, Conflict> {
605 let max_iterations = 10000;
606 let mut iterations = 0;
607
608 while let Some(violating_var) = self.find_violating_basic_var() {
609 iterations += 1;
610 if iterations > max_iterations {
611 return Ok(SimplexResult::Unknown);
612 }
613
614 let current_val = self
615 .assignment
616 .get(&violating_var)
617 .cloned()
618 .ok_or_else(|| Conflict {
619 constraints: vec![],
620 })?;
621
622 let lb = self.lower_bounds.get(&violating_var);
623 let ub = self.upper_bounds.get(&violating_var);
624
625 let need_increase = lb.is_some_and(|l| ¤t_val < l);
627 let need_decrease = ub.is_some_and(|u| ¤t_val > u);
628
629 if !need_increase && !need_decrease {
630 continue;
631 }
632
633 if let Some((nb_var, _direction)) =
635 self.find_pivot_non_basic(violating_var, need_increase)
636 {
637 let target_value = if need_increase {
639 lb.cloned().unwrap_or_else(BigRational::zero)
640 } else {
641 ub.cloned().unwrap_or_else(BigRational::zero)
642 };
643
644 let row = self.rows.get(&violating_var).ok_or_else(|| Conflict {
646 constraints: vec![],
647 })?;
648 let coeff = row.coeffs.get(&nb_var).cloned().ok_or_else(|| Conflict {
649 constraints: vec![],
650 })?;
651
652 let delta = &target_value - ¤t_val;
653 let nb_delta = &delta / &coeff;
654 let current_nb = self
655 .assignment
656 .get(&nb_var)
657 .cloned()
658 .ok_or_else(|| Conflict {
659 constraints: vec![],
660 })?;
661 let new_nb = current_nb + nb_delta;
662
663 let new_nb = if let Some(lb) = self.lower_bounds.get(&nb_var) {
665 new_nb.max(lb.clone())
666 } else {
667 new_nb
668 };
669 let new_nb = if let Some(ub) = self.upper_bounds.get(&nb_var) {
670 new_nb.min(ub.clone())
671 } else {
672 new_nb
673 };
674
675 self.assignment.insert(nb_var, new_nb);
676 self.update_assignment();
677 } else {
678 let constraints = self
680 .var_to_constraints
681 .get(&violating_var)
682 .cloned()
683 .unwrap_or_default();
684 return Err(Conflict { constraints });
685 }
686 }
687
688 Ok(SimplexResult::Sat)
689 }
690
691 pub fn check_dual(&mut self) -> Result<SimplexResult, Conflict> {
703 let max_iterations = 10000;
704 let mut iterations = 0;
705
706 while let Some(leaving_var) = self.find_violating_basic_var() {
708 iterations += 1;
709 if iterations > max_iterations {
710 return Ok(SimplexResult::Unknown);
711 }
712
713 let row = match self.rows.get(&leaving_var) {
715 Some(r) => r.clone(),
716 None => continue,
717 };
718
719 let current_val = self
720 .assignment
721 .get(&leaving_var)
722 .cloned()
723 .unwrap_or_else(BigRational::zero);
724
725 let lb = self.lower_bounds.get(&leaving_var);
726 let ub = self.upper_bounds.get(&leaving_var);
727
728 let violated_lower = lb.is_some_and(|l| ¤t_val < l);
730 let violated_upper = ub.is_some_and(|u| ¤t_val > u);
731
732 if !violated_lower && !violated_upper {
733 continue;
734 }
735
736 let entering_var = self.find_entering_var_dual(&row, violated_lower)?;
738
739 self.pivot(leaving_var, entering_var)
741 .map_err(|_| Conflict {
742 constraints: self
743 .var_to_constraints
744 .get(&leaving_var)
745 .cloned()
746 .unwrap_or_default(),
747 })?;
748 }
749
750 Ok(SimplexResult::Sat)
751 }
752
753 fn find_entering_var_dual(&self, row: &Row, violated_lower: bool) -> Result<VarId, Conflict> {
760 let mut best_var = None;
761 let mut best_ratio: Option<BigRational> = None;
762
763 for (&nb_var, coeff) in &row.coeffs {
765 let sign_ok = if violated_lower {
769 coeff.is_negative()
770 } else {
771 coeff.is_positive()
772 };
773
774 if !sign_ok || coeff.is_zero() {
775 continue;
776 }
777
778 let ratio = coeff.abs();
781
782 match &best_ratio {
783 None => {
784 best_ratio = Some(ratio);
785 best_var = Some(nb_var);
786 }
787 Some(current_best) => {
788 if &ratio < current_best {
791 best_ratio = Some(ratio);
792 best_var = Some(nb_var);
793 } else if self.use_blands_rule && &ratio == current_best {
794 if best_var.is_none_or(|bv| nb_var < bv) {
797 best_var = Some(nb_var);
798 }
799 }
800 }
801 }
802 }
803
804 best_var.ok_or(Conflict {
805 constraints: vec![],
806 })
807 }
808
809 pub fn vars(&self) -> Vec<VarId> {
811 let mut vars: Vec<VarId> = self
812 .basic_vars
813 .iter()
814 .chain(self.non_basic_vars.iter())
815 .copied()
816 .collect();
817 vars.sort_unstable();
818 vars
819 }
820
821 pub fn basic_vars(&self) -> Vec<VarId> {
823 let mut vars: Vec<VarId> = self.basic_vars.iter().copied().collect();
824 vars.sort_unstable();
825 vars
826 }
827
828 pub fn non_basic_vars(&self) -> Vec<VarId> {
830 let mut vars: Vec<VarId> = self.non_basic_vars.iter().copied().collect();
831 vars.sort_unstable();
832 vars
833 }
834
835 pub fn num_rows(&self) -> usize {
837 self.rows.len()
838 }
839
840 pub fn num_vars(&self) -> usize {
842 self.basic_vars.len() + self.non_basic_vars.len()
843 }
844
845 pub fn set_blands_rule(&mut self, enable: bool) {
847 self.use_blands_rule = enable;
848 }
849
850 pub fn get_model(&self) -> Option<FxHashMap<VarId, BigRational>> {
853 for (var, val) in &self.assignment {
855 if let Some(lb) = self.lower_bounds.get(var)
856 && val < lb
857 {
858 return None;
859 }
860 if let Some(ub) = self.upper_bounds.get(var)
861 && val > ub
862 {
863 return None;
864 }
865 }
866 Some(self.assignment.clone())
867 }
868
869 pub fn is_feasible(&self) -> bool {
871 for (var, val) in &self.assignment {
872 if let Some(lb) = self.lower_bounds.get(var)
873 && val < lb
874 {
875 return false;
876 }
877 if let Some(ub) = self.upper_bounds.get(var)
878 && val > ub
879 {
880 return false;
881 }
882 }
883 true
884 }
885
886 pub fn find_violated_bound(&self) -> Option<VarId> {
888 for (var, val) in &self.assignment {
889 if let Some(lb) = self.lower_bounds.get(var)
890 && val < lb
891 {
892 return Some(*var);
893 }
894 if let Some(ub) = self.upper_bounds.get(var)
895 && val > ub
896 {
897 return Some(*var);
898 }
899 }
900 None
901 }
902
903 pub fn get_constraints(&self, var: VarId) -> Vec<ConstraintId> {
905 self.var_to_constraints
906 .get(&var)
907 .cloned()
908 .unwrap_or_default()
909 }
910
911 pub fn get_unsat_core(&self, conflict: &Conflict) -> Vec<ConstraintId> {
915 conflict.constraints.clone()
916 }
917
918 pub fn propagate(&self) -> Vec<(VarId, BoundType, BigRational)> {
921 let mut propagated = Vec::new();
922
923 for row in self.rows.values() {
926 let basic_var = row.basic_var;
927
928 let mut lower_bound = row.constant.clone();
931 let mut has_finite_lower = true;
932
933 for (var, coeff) in &row.coeffs {
934 if coeff.is_positive() {
935 if let Some(lb) = self.lower_bounds.get(var) {
937 lower_bound += coeff * lb;
938 } else {
939 has_finite_lower = false;
940 break;
941 }
942 } else {
943 if let Some(ub) = self.upper_bounds.get(var) {
945 lower_bound += coeff * ub;
946 } else {
947 has_finite_lower = false;
948 break;
949 }
950 }
951 }
952
953 if has_finite_lower {
954 if let Some(current_lb) = self.lower_bounds.get(&basic_var) {
956 if &lower_bound > current_lb {
957 propagated.push((basic_var, BoundType::Lower, lower_bound.clone()));
958 }
959 } else {
960 propagated.push((basic_var, BoundType::Lower, lower_bound.clone()));
961 }
962 }
963
964 let mut upper_bound = row.constant.clone();
966 let mut has_finite_upper = true;
967
968 for (var, coeff) in &row.coeffs {
969 if coeff.is_positive() {
970 if let Some(ub) = self.upper_bounds.get(var) {
972 upper_bound += coeff * ub;
973 } else {
974 has_finite_upper = false;
975 break;
976 }
977 } else {
978 if let Some(lb) = self.lower_bounds.get(var) {
980 upper_bound += coeff * lb;
981 } else {
982 has_finite_upper = false;
983 break;
984 }
985 }
986 }
987
988 if has_finite_upper {
989 if let Some(current_ub) = self.upper_bounds.get(&basic_var) {
991 if &upper_bound < current_ub {
992 propagated.push((basic_var, BoundType::Upper, upper_bound.clone()));
993 }
994 } else {
995 propagated.push((basic_var, BoundType::Upper, upper_bound.clone()));
996 }
997 }
998 }
999
1000 propagated
1001 }
1002
1003 pub fn assert_constraint(
1006 &mut self,
1007 coeffs: FxHashMap<VarId, BigRational>,
1008 constant: BigRational,
1009 bound_type: BoundType,
1010 constraint_id: ConstraintId,
1011 ) -> Result<VarId, Conflict> {
1012 let slack_var = self.fresh_var();
1014
1015 let row = Row::from_expr(slack_var, constant, coeffs);
1017 self.add_row(row).map_err(|_| Conflict {
1018 constraints: vec![constraint_id],
1019 })?;
1020
1021 let zero = BigRational::zero();
1023 match bound_type {
1024 BoundType::Lower => {
1025 self.add_bound(slack_var, BoundType::Lower, zero, constraint_id)?;
1027 }
1028 BoundType::Upper => {
1029 self.add_bound(slack_var, BoundType::Upper, zero, constraint_id)?;
1031 }
1032 BoundType::Equal => {
1033 self.add_bound(slack_var, BoundType::Equal, zero.clone(), constraint_id)?;
1035 self.add_bound(slack_var, BoundType::Lower, zero.clone(), constraint_id)?;
1037 self.add_bound(slack_var, BoundType::Upper, zero, constraint_id)?;
1038 }
1039 }
1040
1041 Ok(slack_var)
1042 }
1043}
1044
1045impl Default for SimplexTableau {
1046 fn default() -> Self {
1047 Self::new()
1048 }
1049}
1050
1051impl fmt::Display for SimplexTableau {
1052 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1053 writeln!(f, "Simplex Tableau:")?;
1054 writeln!(f, " Basic variables: {:?}", self.basic_vars())?;
1055 writeln!(f, " Non-basic variables: {:?}", self.non_basic_vars())?;
1056 writeln!(f, " Rows:")?;
1057 for row in self.rows.values() {
1058 writeln!(f, " {}", row)?;
1059 }
1060 writeln!(f, " Assignment:")?;
1061 for var in self.vars() {
1062 if let Some(val) = self.assignment.get(&var) {
1063 write!(f, " x{} = {}", var, val)?;
1064 if let Some(lb) = self.lower_bounds.get(&var) {
1065 write!(f, " (>= {})", lb)?;
1066 }
1067 if let Some(ub) = self.upper_bounds.get(&var) {
1068 write!(f, " (<= {})", ub)?;
1069 }
1070 writeln!(f)?;
1071 }
1072 }
1073 Ok(())
1074 }
1075}
1076
1077#[cfg(test)]
1078mod tests {
1079 use super::*;
1080
1081 fn rat(n: i64) -> BigRational {
1082 BigRational::from_integer(BigInt::from(n))
1083 }
1084
1085 #[test]
1086 fn test_row_eval() {
1087 let mut row = Row::new(0);
1088 row.constant = rat(5);
1089 row.coeffs.insert(1, rat(2));
1090 row.coeffs.insert(2, rat(3));
1091
1092 let mut values = FxHashMap::default();
1093 values.insert(1, rat(1));
1094 values.insert(2, rat(2));
1095
1096 assert_eq!(row.eval(&values), rat(13));
1098 }
1099
1100 #[test]
1101 fn test_row_add() {
1102 let mut row1 = Row::new(0);
1103 row1.constant = rat(5);
1104 row1.coeffs.insert(1, rat(2));
1105
1106 let mut row2 = Row::new(0);
1107 row2.constant = rat(3);
1108 row2.coeffs.insert(1, rat(1));
1109 row2.coeffs.insert(2, rat(4));
1110
1111 row1.add_row(&rat(2), &row2);
1116 assert_eq!(row1.constant, rat(11));
1117 assert_eq!(row1.coeffs.get(&1), Some(&rat(4)));
1118 assert_eq!(row1.coeffs.get(&2), Some(&rat(8)));
1119 }
1120
1121 #[test]
1122 fn test_simplex_basic() {
1123 let mut tableau = SimplexTableau::new();
1124
1125 let x0 = tableau.fresh_var();
1130 let x1 = tableau.fresh_var();
1131 let x2 = tableau.fresh_var();
1132
1133 let mut row = Row::new(x2);
1135 row.constant = rat(10);
1136 row.coeffs.insert(x0, rat(-1));
1137 row.coeffs.insert(x1, rat(-1));
1138
1139 tableau.add_row(row).unwrap();
1140
1141 tableau.add_bound(x0, BoundType::Lower, rat(0), 0).unwrap();
1143 tableau.add_bound(x1, BoundType::Lower, rat(0), 1).unwrap();
1144 tableau.add_bound(x2, BoundType::Lower, rat(0), 2).unwrap();
1145
1146 let result = tableau.check().unwrap();
1147 assert_eq!(result, SimplexResult::Sat);
1148 }
1149
1150 #[test]
1151 fn test_simplex_infeasible() {
1152 let mut tableau = SimplexTableau::new();
1153
1154 let x = tableau.fresh_var();
1155
1156 tableau.add_bound(x, BoundType::Lower, rat(5), 0).unwrap();
1158 let result = tableau.add_bound(x, BoundType::Upper, rat(3), 1);
1159
1160 assert!(result.is_err());
1161 }
1162
1163 #[test]
1164 fn test_simplex_pivot() {
1165 let mut tableau = SimplexTableau::new();
1166
1167 let x0 = tableau.fresh_var();
1168 let x1 = tableau.fresh_var();
1169 let x2 = tableau.fresh_var();
1170
1171 let mut row = Row::new(x2);
1173 row.constant = rat(10);
1174 row.coeffs.insert(x0, rat(-2));
1175 row.coeffs.insert(x1, rat(-3));
1176
1177 tableau.add_row(row).unwrap();
1178
1179 tableau.pivot(x2, x0).unwrap();
1181
1182 assert!(tableau.basic_vars.contains(&x0));
1184 assert!(tableau.non_basic_vars.contains(&x2));
1185
1186 let new_row = tableau.rows.get(&x0).unwrap();
1187 assert_eq!(new_row.constant, rat(5));
1188 }
1189
1190 #[test]
1191 fn test_simplex_dual() {
1192 let mut tableau = SimplexTableau::new();
1193
1194 let x0 = tableau.fresh_var();
1200 let x1 = tableau.fresh_var();
1201 let x2 = tableau.fresh_var(); let mut row = Row::new(x2);
1205 row.constant = rat(10);
1206 row.coeffs.insert(x0, rat(-1));
1207 row.coeffs.insert(x1, rat(-1));
1208
1209 tableau.add_row(row).unwrap();
1210
1211 tableau.add_bound(x0, BoundType::Lower, rat(0), 0).unwrap();
1213 tableau.add_bound(x1, BoundType::Lower, rat(0), 1).unwrap();
1214 tableau.add_bound(x2, BoundType::Lower, rat(0), 2).unwrap();
1215
1216 let result = tableau.check_dual().unwrap();
1218 assert_eq!(result, SimplexResult::Sat);
1219
1220 assert!(tableau.is_feasible());
1222 }
1223
1224 #[test]
1225 fn test_simplex_dual_with_bounds() {
1226 let mut tableau = SimplexTableau::new();
1227
1228 let x0 = tableau.fresh_var();
1230 let x1 = tableau.fresh_var();
1231 let x2 = tableau.fresh_var();
1232
1233 let mut row = Row::new(x2);
1235 row.constant = rat(10);
1236 row.coeffs.insert(x0, rat(-1));
1237 row.coeffs.insert(x1, rat(-1));
1238
1239 tableau.add_row(row).unwrap();
1240
1241 tableau.add_bound(x0, BoundType::Lower, rat(0), 0).unwrap();
1243 tableau.add_bound(x0, BoundType::Upper, rat(5), 1).unwrap();
1244 tableau.add_bound(x1, BoundType::Lower, rat(0), 2).unwrap();
1245 tableau.add_bound(x1, BoundType::Upper, rat(5), 3).unwrap();
1246 tableau.add_bound(x2, BoundType::Lower, rat(0), 4).unwrap();
1247
1248 let result = tableau.check_dual().unwrap();
1250 assert_eq!(result, SimplexResult::Sat);
1251
1252 assert!(tableau.is_feasible());
1254 }
1255}