1use crate::fast_rational::FastRational;
13#[allow(unused_imports)]
14use crate::prelude::*;
15use core::fmt;
16use num_bigint::BigInt;
17use num_traits::{One, Signed, Zero};
18
19pub type VarId = u32;
21
22pub type ConstraintId = u32;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum BoundType {
28 Lower,
30 Upper,
32 Equal,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct Bound {
39 pub var: VarId,
41 pub bound_type: BoundType,
43 pub value: FastRational,
45}
46
47impl Bound {
48 pub fn lower(var: VarId, value: FastRational) -> Self {
50 Self {
51 var,
52 bound_type: BoundType::Lower,
53 value,
54 }
55 }
56
57 pub fn upper(var: VarId, value: FastRational) -> Self {
59 Self {
60 var,
61 bound_type: BoundType::Upper,
62 value,
63 }
64 }
65
66 pub fn equal(var: VarId, value: FastRational) -> Self {
68 Self {
69 var,
70 bound_type: BoundType::Equal,
71 value,
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum VarClass {
79 Basic,
81 NonBasic,
83}
84
85#[derive(Debug, Clone)]
87pub struct Row {
88 pub basic_var: VarId,
90 pub constant: FastRational,
92 pub coeffs: FxHashMap<VarId, FastRational>,
94}
95
96impl Row {
97 pub fn new(basic_var: VarId) -> Self {
99 Self {
100 basic_var,
101 constant: FastRational::zero(),
102 coeffs: FxHashMap::default(),
103 }
104 }
105
106 pub fn from_expr(
108 basic_var: VarId,
109 constant: FastRational,
110 coeffs: FxHashMap<VarId, FastRational>,
111 ) -> Self {
112 let mut row = Self {
113 basic_var,
114 constant,
115 coeffs: FxHashMap::default(),
116 };
117 for (var, coeff) in coeffs {
118 if !coeff.is_zero() {
119 row.coeffs.insert(var, coeff);
120 }
121 }
122 row
123 }
124
125 pub fn eval(&self, non_basic_values: &FxHashMap<VarId, FastRational>) -> FastRational {
127 let mut value = self.constant.clone();
128 for (var, coeff) in &self.coeffs {
129 if let Some(val) = non_basic_values.get(var) {
130 value += coeff * val;
131 }
132 }
133 value
134 }
135
136 pub fn add_row(&mut self, multiplier: &FastRational, other: &Row) {
139 if multiplier.is_zero() {
140 return;
141 }
142
143 self.constant += multiplier * &other.constant;
144
145 for (var, coeff) in &other.coeffs {
146 let new_coeff = self
147 .coeffs
148 .get(var)
149 .cloned()
150 .unwrap_or_else(FastRational::zero)
151 + multiplier * coeff;
152 if new_coeff.is_zero() {
153 self.coeffs.remove(var);
154 } else {
155 self.coeffs.insert(*var, new_coeff);
156 }
157 }
158 }
159
160 pub fn scale(&mut self, scalar: &FastRational) {
162 if scalar.is_zero() {
163 self.constant = FastRational::zero();
164 self.coeffs.clear();
165 return;
166 }
167
168 self.constant *= scalar;
169 for coeff in self.coeffs.values_mut() {
170 *coeff *= scalar;
171 }
172 }
173
174 pub fn substitute(&mut self, var: VarId, row: &Row) {
177 if let Some(coeff) = self.coeffs.remove(&var) {
178 self.add_row(&coeff, row);
180 }
181 }
182
183 pub fn is_valid(&self, basic_vars: &FxHashSet<VarId>) -> bool {
185 for var in self.coeffs.keys() {
186 if basic_vars.contains(var) {
187 return false;
188 }
189 }
190 true
191 }
192
193 pub fn normalize(&mut self) {
195 if self.coeffs.is_empty() {
196 return;
197 }
198
199 let mut gcd: Option<BigInt> = None;
201 for coeff in self.coeffs.values() {
202 if !coeff.is_zero() {
203 let num = coeff.numer().clone();
204 gcd = Some(match gcd {
205 None => num.abs(),
206 Some(g) => gcd_bigint(g, num.abs()),
207 });
208 }
209 }
210
211 if let Some(g) = gcd
212 && !g.is_one()
213 {
214 let divisor = FastRational::from_integer(g);
215 self.scale(&(FastRational::one() / divisor));
216 }
217 }
218}
219
220impl fmt::Display for Row {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 write!(f, "x{} = {}", self.basic_var, self.constant)?;
223 for (var, coeff) in &self.coeffs {
224 if coeff.is_positive() {
225 write!(f, " + {}*x{}", coeff, var)?;
226 } else {
227 write!(f, " - {}*x{}", -coeff, var)?;
228 }
229 }
230 Ok(())
231 }
232}
233
234fn gcd_bigint(mut a: BigInt, mut b: BigInt) -> BigInt {
236 while !b.is_zero() {
237 let t = &a % &b;
238 a = b;
239 b = t;
240 }
241 a.abs()
242}
243
244#[derive(Debug, Clone, PartialEq, Eq)]
246pub enum SimplexResult {
247 Sat,
249 Unsat,
251 Unbounded,
253 Unknown,
255}
256
257#[derive(Debug, Clone)]
259pub struct Conflict {
260 pub constraints: Vec<ConstraintId>,
262}
263
264#[derive(Debug, Clone)]
266pub struct SimplexTableau {
267 rows: FxHashMap<VarId, Row>,
269 basic_vars: FxHashSet<VarId>,
271 non_basic_vars: FxHashSet<VarId>,
273 assignment: FxHashMap<VarId, FastRational>,
275 lower_bounds: FxHashMap<VarId, FastRational>,
277 upper_bounds: FxHashMap<VarId, FastRational>,
279 var_to_constraints: FxHashMap<VarId, Vec<ConstraintId>>,
281 next_var_id: VarId,
283 use_blands_rule: bool,
285}
286
287impl SimplexTableau {
288 pub fn new() -> Self {
290 Self {
291 rows: FxHashMap::default(),
292 basic_vars: FxHashSet::default(),
293 non_basic_vars: FxHashSet::default(),
294 assignment: FxHashMap::default(),
295 lower_bounds: FxHashMap::default(),
296 upper_bounds: FxHashMap::default(),
297 var_to_constraints: FxHashMap::default(),
298 next_var_id: 0,
299 use_blands_rule: true,
300 }
301 }
302
303 pub fn fresh_var(&mut self) -> VarId {
305 let id = self.next_var_id;
306 self.next_var_id += 1;
307 self.non_basic_vars.insert(id);
308 self.assignment.insert(id, FastRational::zero());
309 id
310 }
311
312 pub fn add_var(&mut self, lower: Option<FastRational>, upper: Option<FastRational>) -> VarId {
314 let var = self.fresh_var();
315 if let Some(lb) = lower {
316 self.lower_bounds.insert(var, lb.clone());
317 self.assignment.insert(var, lb);
318 }
319 if let Some(ub) = upper {
320 self.upper_bounds.insert(var, ub);
321 }
322 var
323 }
324
325 pub fn add_row(&mut self, row: Row) -> Result<(), String> {
328 let basic_var = row.basic_var;
329
330 if self.basic_vars.contains(&basic_var) {
332 return Err(format!("Variable x{} is already basic", basic_var));
333 }
334
335 for var in row.coeffs.keys() {
337 if self.basic_vars.contains(var) {
338 return Err(format!("Variable x{} appears in RHS but is basic", var));
339 }
340 self.non_basic_vars.insert(*var);
341 }
342
343 self.non_basic_vars.remove(&basic_var);
345 self.basic_vars.insert(basic_var);
346
347 let value = row.eval(&self.assignment);
349 self.assignment.insert(basic_var, value);
350
351 self.rows.insert(basic_var, row);
352 Ok(())
353 }
354
355 pub fn add_bound(
357 &mut self,
358 var: VarId,
359 bound_type: BoundType,
360 value: FastRational,
361 constraint_id: ConstraintId,
362 ) -> Result<(), Conflict> {
363 self.var_to_constraints
364 .entry(var)
365 .or_default()
366 .push(constraint_id);
367
368 match bound_type {
369 BoundType::Lower => {
370 let current_lb = self.lower_bounds.get(&var);
371 if let Some(lb) = current_lb {
372 if &value > lb {
373 self.lower_bounds.insert(var, value.clone());
374 }
375 } else {
376 self.lower_bounds.insert(var, value.clone());
377 }
378
379 if let Some(ub) = self.upper_bounds.get(&var)
381 && &value > ub
382 {
383 return Err(Conflict {
384 constraints: vec![constraint_id],
385 });
386 }
387 }
388 BoundType::Upper => {
389 let current_ub = self.upper_bounds.get(&var);
390 if let Some(ub) = current_ub {
391 if &value < ub {
392 self.upper_bounds.insert(var, value.clone());
393 }
394 } else {
395 self.upper_bounds.insert(var, value.clone());
396 }
397
398 if let Some(lb) = self.lower_bounds.get(&var)
400 && &value < lb
401 {
402 return Err(Conflict {
403 constraints: vec![constraint_id],
404 });
405 }
406 }
407 BoundType::Equal => {
408 self.lower_bounds.insert(var, value.clone());
409 self.upper_bounds.insert(var, value.clone());
410
411 if let Some(lb) = self.lower_bounds.get(&var)
413 && &value < lb
414 {
415 return Err(Conflict {
416 constraints: vec![constraint_id],
417 });
418 }
419 if let Some(ub) = self.upper_bounds.get(&var)
420 && &value > ub
421 {
422 return Err(Conflict {
423 constraints: vec![constraint_id],
424 });
425 }
426 }
427 }
428
429 self.repair_non_basic_bound(var);
442
443 Ok(())
444 }
445
446 fn repair_non_basic_bound(&mut self, var: VarId) {
452 if self.basic_vars.contains(&var) {
453 return;
454 }
455 let Some(current) = self.assignment.get(&var).cloned() else {
456 return;
457 };
458
459 let mut new_value = current.clone();
460 if let Some(lb) = self.lower_bounds.get(&var)
461 && &new_value < lb
462 {
463 new_value = lb.clone();
464 }
465 if let Some(ub) = self.upper_bounds.get(&var)
466 && &new_value > ub
467 {
468 new_value = ub.clone();
469 }
470
471 if new_value != current {
472 self.assignment.insert(var, new_value);
473 self.update_assignment();
476 }
477 }
478
479 pub fn get_value(&self, var: VarId) -> Option<&FastRational> {
481 self.assignment.get(&var)
482 }
483
484 fn violates_bounds(&self, var: VarId) -> bool {
486 if let Some(val) = self.assignment.get(&var) {
487 if let Some(lb) = self.lower_bounds.get(&var)
488 && val < lb
489 {
490 return true;
491 }
492 if let Some(ub) = self.upper_bounds.get(&var)
493 && val > ub
494 {
495 return true;
496 }
497 }
498 false
499 }
500
501 fn find_violating_basic_var(&self) -> Option<VarId> {
503 if self.use_blands_rule {
504 self.basic_vars
506 .iter()
507 .filter(|&&var| self.violates_bounds(var))
508 .min()
509 .copied()
510 } else {
511 self.basic_vars
512 .iter()
513 .find(|&&var| self.violates_bounds(var))
514 .copied()
515 }
516 }
517
518 fn find_pivot_non_basic(
521 &self,
522 basic_var: VarId,
523 target_increase: bool,
524 ) -> Option<(VarId, bool)> {
525 let row = self.rows.get(&basic_var)?;
526
527 let mut candidates = Vec::new();
528
529 for (nb_var, coeff) in &row.coeffs {
530 let current_val = self.assignment.get(nb_var)?;
531 let lb = self.lower_bounds.get(nb_var);
532 let ub = self.upper_bounds.get(nb_var);
533
534 let can_increase = ub.is_none_or(|bound| bound > current_val);
536 let can_decrease = lb.is_none_or(|bound| bound < current_val);
537
538 let increases_basic = coeff.is_positive();
541
542 if target_increase {
543 if increases_basic && can_increase {
545 candidates.push((*nb_var, true));
546 } else if !increases_basic && can_decrease {
547 candidates.push((*nb_var, false));
548 }
549 } else {
550 if increases_basic && can_decrease {
552 candidates.push((*nb_var, false));
553 } else if !increases_basic && can_increase {
554 candidates.push((*nb_var, true));
555 }
556 }
557 }
558
559 if candidates.is_empty() {
560 return None;
561 }
562
563 if self.use_blands_rule {
565 candidates.sort_by_key(|(var, _)| *var);
566 }
567
568 Some(candidates[0])
569 }
570
571 pub fn pivot(&mut self, basic_var: VarId, non_basic_var: VarId) -> Result<(), String> {
574 let row = self
576 .rows
577 .get(&basic_var)
578 .ok_or_else(|| format!("No row for basic variable x{}", basic_var))?;
579
580 let coeff = row
582 .coeffs
583 .get(&non_basic_var)
584 .ok_or_else(|| {
585 format!(
586 "Non-basic variable x{} not in row for x{}",
587 non_basic_var, basic_var
588 )
589 })?
590 .clone();
591
592 if coeff.is_zero() {
593 return Err(format!("Coefficient of x{} is zero", non_basic_var));
594 }
595
596 let mut new_row = Row::new(non_basic_var);
601 new_row.constant = -&row.constant / &coeff;
602 new_row
603 .coeffs
604 .insert(basic_var, FastRational::one() / &coeff);
605
606 for (var, c) in &row.coeffs {
607 if var != &non_basic_var {
608 new_row.coeffs.insert(*var, -c / &coeff);
609 }
610 }
611
612 let rows_to_update: Vec<VarId> = self
614 .rows
615 .keys()
616 .filter(|&&v| v != basic_var)
617 .copied()
618 .collect();
619
620 for row_var in rows_to_update {
621 if let Some(r) = self.rows.get_mut(&row_var) {
622 r.substitute(non_basic_var, &new_row);
623 }
624 }
625
626 self.rows.remove(&basic_var);
628 self.rows.insert(non_basic_var, new_row);
629
630 self.basic_vars.remove(&basic_var);
632 self.basic_vars.insert(non_basic_var);
633 self.non_basic_vars.remove(&non_basic_var);
634 self.non_basic_vars.insert(basic_var);
635
636 self.update_assignment();
638
639 Ok(())
640 }
641
642 fn update_assignment(&mut self) {
644 for (basic_var, row) in &self.rows {
646 let value = row.eval(&self.assignment);
647 self.assignment.insert(*basic_var, value);
648 }
649 }
650
651 pub fn check(&mut self) -> Result<SimplexResult, Conflict> {
653 let max_iterations = 10000;
654 let mut iterations = 0;
655
656 while let Some(violating_var) = self.find_violating_basic_var() {
657 iterations += 1;
658 if iterations > max_iterations {
659 return Ok(SimplexResult::Unknown);
660 }
661
662 let current_val = self
663 .assignment
664 .get(&violating_var)
665 .cloned()
666 .ok_or_else(|| Conflict {
667 constraints: vec![],
668 })?;
669
670 let lb = self.lower_bounds.get(&violating_var);
671 let ub = self.upper_bounds.get(&violating_var);
672
673 let need_increase = lb.is_some_and(|l| ¤t_val < l);
675 let need_decrease = ub.is_some_and(|u| ¤t_val > u);
676
677 if !need_increase && !need_decrease {
678 continue;
679 }
680
681 if let Some((nb_var, _direction)) =
683 self.find_pivot_non_basic(violating_var, need_increase)
684 {
685 let target_value = if need_increase {
687 lb.cloned().unwrap_or_else(FastRational::zero)
688 } else {
689 ub.cloned().unwrap_or_else(FastRational::zero)
690 };
691
692 let row = self.rows.get(&violating_var).ok_or_else(|| Conflict {
694 constraints: vec![],
695 })?;
696 let coeff = row.coeffs.get(&nb_var).cloned().ok_or_else(|| Conflict {
697 constraints: vec![],
698 })?;
699
700 let delta = &target_value - ¤t_val;
701 let nb_delta = &delta / &coeff;
702 let current_nb = self
703 .assignment
704 .get(&nb_var)
705 .cloned()
706 .ok_or_else(|| Conflict {
707 constraints: vec![],
708 })?;
709 let new_nb = current_nb + nb_delta;
710
711 let new_nb = if let Some(lb) = self.lower_bounds.get(&nb_var) {
713 new_nb.max(lb.clone())
714 } else {
715 new_nb
716 };
717 let new_nb = if let Some(ub) = self.upper_bounds.get(&nb_var) {
718 new_nb.min(ub.clone())
719 } else {
720 new_nb
721 };
722
723 self.assignment.insert(nb_var, new_nb);
724 self.update_assignment();
725 } else {
726 let constraints = self
728 .var_to_constraints
729 .get(&violating_var)
730 .cloned()
731 .unwrap_or_default();
732 return Err(Conflict { constraints });
733 }
734 }
735
736 Ok(SimplexResult::Sat)
737 }
738
739 pub fn check_dual(&mut self) -> Result<SimplexResult, Conflict> {
751 let max_iterations = 10000;
752 let mut iterations = 0;
753
754 while let Some(leaving_var) = self.find_violating_basic_var() {
756 iterations += 1;
757 if iterations > max_iterations {
758 return Ok(SimplexResult::Unknown);
759 }
760
761 let row = match self.rows.get(&leaving_var) {
763 Some(r) => r.clone(),
764 None => continue,
765 };
766
767 let current_val = self
768 .assignment
769 .get(&leaving_var)
770 .cloned()
771 .unwrap_or_else(FastRational::zero);
772
773 let lb = self.lower_bounds.get(&leaving_var);
774 let ub = self.upper_bounds.get(&leaving_var);
775
776 let violated_lower = lb.is_some_and(|l| ¤t_val < l);
778 let violated_upper = ub.is_some_and(|u| ¤t_val > u);
779
780 if !violated_lower && !violated_upper {
781 continue;
782 }
783
784 let entering_var = self.find_entering_var_dual(&row, violated_lower)?;
786
787 self.pivot(leaving_var, entering_var)
789 .map_err(|_| Conflict {
790 constraints: self
791 .var_to_constraints
792 .get(&leaving_var)
793 .cloned()
794 .unwrap_or_default(),
795 })?;
796 }
797
798 Ok(SimplexResult::Sat)
799 }
800
801 fn find_entering_var_dual(&self, row: &Row, violated_lower: bool) -> Result<VarId, Conflict> {
808 let mut best_var = None;
809 let mut best_ratio: Option<FastRational> = None;
810
811 for (&nb_var, coeff) in &row.coeffs {
813 let sign_ok = if violated_lower {
817 coeff.is_negative()
818 } else {
819 coeff.is_positive()
820 };
821
822 if !sign_ok || coeff.is_zero() {
823 continue;
824 }
825
826 let ratio = coeff.abs();
829
830 match &best_ratio {
831 None => {
832 best_ratio = Some(ratio);
833 best_var = Some(nb_var);
834 }
835 Some(current_best) => {
836 if &ratio < current_best {
839 best_ratio = Some(ratio);
840 best_var = Some(nb_var);
841 } else if self.use_blands_rule && &ratio == current_best {
842 if best_var.is_none_or(|bv| nb_var < bv) {
845 best_var = Some(nb_var);
846 }
847 }
848 }
849 }
850 }
851
852 best_var.ok_or(Conflict {
853 constraints: vec![],
854 })
855 }
856
857 pub fn vars(&self) -> Vec<VarId> {
859 let mut vars: Vec<VarId> = self
860 .basic_vars
861 .iter()
862 .chain(self.non_basic_vars.iter())
863 .copied()
864 .collect();
865 vars.sort_unstable();
866 vars
867 }
868
869 pub fn basic_vars(&self) -> Vec<VarId> {
871 let mut vars: Vec<VarId> = self.basic_vars.iter().copied().collect();
872 vars.sort_unstable();
873 vars
874 }
875
876 pub fn non_basic_vars(&self) -> Vec<VarId> {
878 let mut vars: Vec<VarId> = self.non_basic_vars.iter().copied().collect();
879 vars.sort_unstable();
880 vars
881 }
882
883 pub fn num_rows(&self) -> usize {
885 self.rows.len()
886 }
887
888 pub fn num_vars(&self) -> usize {
890 self.basic_vars.len() + self.non_basic_vars.len()
891 }
892
893 pub fn set_blands_rule(&mut self, enable: bool) {
895 self.use_blands_rule = enable;
896 }
897
898 pub fn get_model(&self) -> Option<FxHashMap<VarId, FastRational>> {
901 for (var, val) in &self.assignment {
903 if let Some(lb) = self.lower_bounds.get(var)
904 && val < lb
905 {
906 return None;
907 }
908 if let Some(ub) = self.upper_bounds.get(var)
909 && val > ub
910 {
911 return None;
912 }
913 }
914 Some(self.assignment.clone())
915 }
916
917 pub fn is_feasible(&self) -> bool {
919 for (var, val) in &self.assignment {
920 if let Some(lb) = self.lower_bounds.get(var)
921 && val < lb
922 {
923 return false;
924 }
925 if let Some(ub) = self.upper_bounds.get(var)
926 && val > ub
927 {
928 return false;
929 }
930 }
931 true
932 }
933
934 pub fn find_violated_bound(&self) -> Option<VarId> {
936 for (var, val) in &self.assignment {
937 if let Some(lb) = self.lower_bounds.get(var)
938 && val < lb
939 {
940 return Some(*var);
941 }
942 if let Some(ub) = self.upper_bounds.get(var)
943 && val > ub
944 {
945 return Some(*var);
946 }
947 }
948 None
949 }
950
951 pub fn get_constraints(&self, var: VarId) -> Vec<ConstraintId> {
953 self.var_to_constraints
954 .get(&var)
955 .cloned()
956 .unwrap_or_default()
957 }
958
959 pub fn get_unsat_core(&self, conflict: &Conflict) -> Vec<ConstraintId> {
963 conflict.constraints.clone()
964 }
965
966 pub fn propagate(&self) -> Vec<(VarId, BoundType, FastRational)> {
969 let mut propagated = Vec::new();
970
971 for row in self.rows.values() {
974 let basic_var = row.basic_var;
975
976 let mut lower_bound = row.constant.clone();
979 let mut has_finite_lower = true;
980
981 for (var, coeff) in &row.coeffs {
982 if coeff.is_positive() {
983 if let Some(lb) = self.lower_bounds.get(var) {
985 lower_bound += coeff * lb;
986 } else {
987 has_finite_lower = false;
988 break;
989 }
990 } else {
991 if let Some(ub) = self.upper_bounds.get(var) {
993 lower_bound += coeff * ub;
994 } else {
995 has_finite_lower = false;
996 break;
997 }
998 }
999 }
1000
1001 if has_finite_lower {
1002 if let Some(current_lb) = self.lower_bounds.get(&basic_var) {
1004 if &lower_bound > current_lb {
1005 propagated.push((basic_var, BoundType::Lower, lower_bound.clone()));
1006 }
1007 } else {
1008 propagated.push((basic_var, BoundType::Lower, lower_bound.clone()));
1009 }
1010 }
1011
1012 let mut upper_bound = row.constant.clone();
1014 let mut has_finite_upper = true;
1015
1016 for (var, coeff) in &row.coeffs {
1017 if coeff.is_positive() {
1018 if let Some(ub) = self.upper_bounds.get(var) {
1020 upper_bound += coeff * ub;
1021 } else {
1022 has_finite_upper = false;
1023 break;
1024 }
1025 } else {
1026 if let Some(lb) = self.lower_bounds.get(var) {
1028 upper_bound += coeff * lb;
1029 } else {
1030 has_finite_upper = false;
1031 break;
1032 }
1033 }
1034 }
1035
1036 if has_finite_upper {
1037 if let Some(current_ub) = self.upper_bounds.get(&basic_var) {
1039 if &upper_bound < current_ub {
1040 propagated.push((basic_var, BoundType::Upper, upper_bound.clone()));
1041 }
1042 } else {
1043 propagated.push((basic_var, BoundType::Upper, upper_bound.clone()));
1044 }
1045 }
1046 }
1047
1048 propagated
1049 }
1050
1051 pub fn assert_constraint(
1054 &mut self,
1055 coeffs: FxHashMap<VarId, FastRational>,
1056 constant: FastRational,
1057 bound_type: BoundType,
1058 constraint_id: ConstraintId,
1059 ) -> Result<VarId, Conflict> {
1060 let slack_var = self.fresh_var();
1062
1063 let row = Row::from_expr(slack_var, constant, coeffs);
1065 self.add_row(row).map_err(|_| Conflict {
1066 constraints: vec![constraint_id],
1067 })?;
1068
1069 let zero = FastRational::zero();
1071 match bound_type {
1072 BoundType::Lower => {
1073 self.add_bound(slack_var, BoundType::Lower, zero, constraint_id)?;
1075 }
1076 BoundType::Upper => {
1077 self.add_bound(slack_var, BoundType::Upper, zero, constraint_id)?;
1079 }
1080 BoundType::Equal => {
1081 self.add_bound(slack_var, BoundType::Equal, zero.clone(), constraint_id)?;
1083 self.add_bound(slack_var, BoundType::Lower, zero.clone(), constraint_id)?;
1085 self.add_bound(slack_var, BoundType::Upper, zero, constraint_id)?;
1086 }
1087 }
1088
1089 Ok(slack_var)
1090 }
1091}
1092
1093impl Default for SimplexTableau {
1094 fn default() -> Self {
1095 Self::new()
1096 }
1097}
1098
1099impl fmt::Display for SimplexTableau {
1100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101 writeln!(f, "Simplex Tableau:")?;
1102 writeln!(f, " Basic variables: {:?}", self.basic_vars())?;
1103 writeln!(f, " Non-basic variables: {:?}", self.non_basic_vars())?;
1104 writeln!(f, " Rows:")?;
1105 for row in self.rows.values() {
1106 writeln!(f, " {}", row)?;
1107 }
1108 writeln!(f, " Assignment:")?;
1109 for var in self.vars() {
1110 if let Some(val) = self.assignment.get(&var) {
1111 write!(f, " x{} = {}", var, val)?;
1112 if let Some(lb) = self.lower_bounds.get(&var) {
1113 write!(f, " (>= {})", lb)?;
1114 }
1115 if let Some(ub) = self.upper_bounds.get(&var) {
1116 write!(f, " (<= {})", ub)?;
1117 }
1118 writeln!(f)?;
1119 }
1120 }
1121 Ok(())
1122 }
1123}
1124
1125#[cfg(test)]
1126mod tests {
1127 use super::*;
1128
1129 fn rat(n: i64) -> FastRational {
1130 FastRational::from_integer(BigInt::from(n))
1131 }
1132
1133 #[test]
1134 fn test_row_eval() {
1135 let mut row = Row::new(0);
1136 row.constant = rat(5);
1137 row.coeffs.insert(1, rat(2));
1138 row.coeffs.insert(2, rat(3));
1139
1140 let mut values = FxHashMap::default();
1141 values.insert(1, rat(1));
1142 values.insert(2, rat(2));
1143
1144 assert_eq!(row.eval(&values), rat(13));
1146 }
1147
1148 #[test]
1149 fn test_row_add() {
1150 let mut row1 = Row::new(0);
1151 row1.constant = rat(5);
1152 row1.coeffs.insert(1, rat(2));
1153
1154 let mut row2 = Row::new(0);
1155 row2.constant = rat(3);
1156 row2.coeffs.insert(1, rat(1));
1157 row2.coeffs.insert(2, rat(4));
1158
1159 row1.add_row(&rat(2), &row2);
1164 assert_eq!(row1.constant, rat(11));
1165 assert_eq!(row1.coeffs.get(&1), Some(&rat(4)));
1166 assert_eq!(row1.coeffs.get(&2), Some(&rat(8)));
1167 }
1168
1169 #[test]
1170 fn test_simplex_basic() {
1171 let mut tableau = SimplexTableau::new();
1172
1173 let x0 = tableau.fresh_var();
1178 let x1 = tableau.fresh_var();
1179 let x2 = tableau.fresh_var();
1180
1181 let mut row = Row::new(x2);
1183 row.constant = rat(10);
1184 row.coeffs.insert(x0, rat(-1));
1185 row.coeffs.insert(x1, rat(-1));
1186
1187 tableau.add_row(row).expect("test operation should succeed");
1188
1189 tableau
1191 .add_bound(x0, BoundType::Lower, rat(0), 0)
1192 .expect("test operation should succeed");
1193 tableau
1194 .add_bound(x1, BoundType::Lower, rat(0), 1)
1195 .expect("test operation should succeed");
1196 tableau
1197 .add_bound(x2, BoundType::Lower, rat(0), 2)
1198 .expect("test operation should succeed");
1199
1200 let result = tableau.check().expect("test operation should succeed");
1201 assert_eq!(result, SimplexResult::Sat);
1202 }
1203
1204 #[test]
1205 fn test_simplex_infeasible() {
1206 let mut tableau = SimplexTableau::new();
1207
1208 let x = tableau.fresh_var();
1209
1210 tableau
1212 .add_bound(x, BoundType::Lower, rat(5), 0)
1213 .expect("test operation should succeed");
1214 let result = tableau.add_bound(x, BoundType::Upper, rat(3), 1);
1215
1216 assert!(result.is_err());
1217 }
1218
1219 #[test]
1220 fn test_simplex_pivot() {
1221 let mut tableau = SimplexTableau::new();
1222
1223 let x0 = tableau.fresh_var();
1224 let x1 = tableau.fresh_var();
1225 let x2 = tableau.fresh_var();
1226
1227 let mut row = Row::new(x2);
1229 row.constant = rat(10);
1230 row.coeffs.insert(x0, rat(-2));
1231 row.coeffs.insert(x1, rat(-3));
1232
1233 tableau.add_row(row).expect("test operation should succeed");
1234
1235 tableau
1237 .pivot(x2, x0)
1238 .expect("test operation should succeed");
1239
1240 assert!(tableau.basic_vars.contains(&x0));
1242 assert!(tableau.non_basic_vars.contains(&x2));
1243
1244 let new_row = tableau.rows.get(&x0).expect("key should exist in map");
1245 assert_eq!(new_row.constant, rat(5));
1246 }
1247
1248 #[test]
1249 fn test_simplex_dual() {
1250 let mut tableau = SimplexTableau::new();
1251
1252 let x0 = tableau.fresh_var();
1258 let x1 = tableau.fresh_var();
1259 let x2 = tableau.fresh_var(); let mut row = Row::new(x2);
1263 row.constant = rat(10);
1264 row.coeffs.insert(x0, rat(-1));
1265 row.coeffs.insert(x1, rat(-1));
1266
1267 tableau.add_row(row).expect("test operation should succeed");
1268
1269 tableau
1271 .add_bound(x0, BoundType::Lower, rat(0), 0)
1272 .expect("test operation should succeed");
1273 tableau
1274 .add_bound(x1, BoundType::Lower, rat(0), 1)
1275 .expect("test operation should succeed");
1276 tableau
1277 .add_bound(x2, BoundType::Lower, rat(0), 2)
1278 .expect("test operation should succeed");
1279
1280 let result = tableau.check_dual().expect("test operation should succeed");
1282 assert_eq!(result, SimplexResult::Sat);
1283
1284 assert!(tableau.is_feasible());
1286 }
1287
1288 #[test]
1289 fn test_simplex_dual_with_bounds() {
1290 let mut tableau = SimplexTableau::new();
1291
1292 let x0 = tableau.fresh_var();
1294 let x1 = tableau.fresh_var();
1295 let x2 = tableau.fresh_var();
1296
1297 let mut row = Row::new(x2);
1299 row.constant = rat(10);
1300 row.coeffs.insert(x0, rat(-1));
1301 row.coeffs.insert(x1, rat(-1));
1302
1303 tableau.add_row(row).expect("test operation should succeed");
1304
1305 tableau
1307 .add_bound(x0, BoundType::Lower, rat(0), 0)
1308 .expect("test operation should succeed");
1309 tableau
1310 .add_bound(x0, BoundType::Upper, rat(5), 1)
1311 .expect("test operation should succeed");
1312 tableau
1313 .add_bound(x1, BoundType::Lower, rat(0), 2)
1314 .expect("test operation should succeed");
1315 tableau
1316 .add_bound(x1, BoundType::Upper, rat(5), 3)
1317 .expect("test operation should succeed");
1318 tableau
1319 .add_bound(x2, BoundType::Lower, rat(0), 4)
1320 .expect("test operation should succeed");
1321
1322 let result = tableau.check_dual().expect("test operation should succeed");
1324 assert_eq!(result, SimplexResult::Sat);
1325
1326 assert!(tableau.is_feasible());
1328 }
1329
1330 #[test]
1339 fn test_add_bound_repairs_non_basic_var_violating_own_bound() {
1340 let mut tableau = SimplexTableau::new();
1341
1342 let x = tableau.fresh_var();
1343 let y = tableau.fresh_var();
1344
1345 let mut row = Row::new(y);
1346 row.coeffs.insert(x, rat(1));
1347 tableau.add_row(row).expect("test operation should succeed");
1348
1349 tableau
1350 .add_bound(x, BoundType::Lower, rat(5), 0)
1351 .expect("x >= 5 alone is not a direct conflict");
1352
1353 assert_eq!(tableau.get_value(x), Some(&rat(5)));
1357 assert_eq!(
1358 tableau.get_value(y),
1359 Some(&rat(5)),
1360 "basic variable y=x must be recomputed when non-basic x is repaired"
1361 );
1362
1363 let result = tableau.add_bound(y, BoundType::Upper, rat(3), 1);
1367
1368 match result {
1369 Err(_) => {
1370 }
1373 Ok(()) => {
1374 let check_result = tableau.check();
1377 assert!(
1378 check_result.is_err(),
1379 "check() must not report the infeasible system \
1380 (x>=5, y<=3, y=x) as satisfiable: {:?}",
1381 check_result
1382 );
1383 }
1384 }
1385 }
1386
1387 #[test]
1392 fn test_add_bound_repairs_non_basic_var_stays_sat_when_consistent() {
1393 let mut tableau = SimplexTableau::new();
1394
1395 let x = tableau.fresh_var();
1396 let y = tableau.fresh_var();
1397
1398 let mut row = Row::new(y);
1399 row.coeffs.insert(x, rat(1));
1400 tableau.add_row(row).expect("test operation should succeed");
1401
1402 tableau
1404 .add_bound(y, BoundType::Upper, rat(100), 0)
1405 .expect("test operation should succeed");
1406 tableau
1407 .add_bound(x, BoundType::Lower, rat(5), 1)
1408 .expect("test operation should succeed");
1409
1410 assert_eq!(tableau.get_value(x), Some(&rat(5)));
1411 assert_eq!(tableau.get_value(y), Some(&rat(5)));
1412
1413 let result = tableau.check().expect("test operation should succeed");
1414 assert_eq!(result, SimplexResult::Sat);
1415 assert!(tableau.is_feasible());
1416 }
1417}