Skip to content

Commit b08e91c

Browse files
Fix Engine: Update lower and upper bound methods for correct iteration and add conflict explanation retrieval
1 parent b94f550 commit b08e91c

3 files changed

Lines changed: 71 additions & 29 deletions

File tree

src/lib.rs

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,11 @@ impl Engine {
7070
}
7171

7272
pub fn lb(&self, var: usize) -> &InfRational {
73-
self.lbs[var].iter().next().map(|(lb, _)| lb).unwrap_or(&InfRational::NEGATIVE_INFINITY)
73+
self.lbs[var].iter().next_back().map(|(lb, _)| lb).unwrap_or(&InfRational::NEGATIVE_INFINITY)
7474
}
7575

7676
pub fn ub(&self, var: usize) -> &InfRational {
77-
self.ubs[var].iter().next_back().map(|(ub, _)| ub).unwrap_or(&InfRational::POSITIVE_INFINITY)
77+
self.ubs[var].iter().next().map(|(ub, _)| ub).unwrap_or(&InfRational::POSITIVE_INFINITY)
7878
}
7979

8080
fn is_basic(&self, var: usize) -> bool {
@@ -158,7 +158,7 @@ impl Engine {
158158
1 => {
159159
// One variable, can be turned into a bound
160160
let (&var, &coeff) = expr.vars.iter().next().unwrap();
161-
let val = inf(-expr.known_term / coeff, if strict { r(-1) } else { Rational::ZERO });
161+
let val = inf(-expr.known_term / coeff, if strict { if coeff.is_positive() { r(-1) } else { r(1) } } else { Rational::ZERO });
162162
if coeff.is_positive() {
163163
return self.set_ub(var, val, reason);
164164
} else {
@@ -368,6 +368,10 @@ impl Engine {
368368
self.tableau.insert(var, lin);
369369
}
370370

371+
pub fn get_conflict_explanation(&mut self) -> Option<Vec<usize>> {
372+
if self.conflict.is_empty() { None } else { Some(std::mem::take(&mut self.conflict)) }
373+
}
374+
371375
fn notify(&self, var: usize) {
372376
if let Some(listeners) = self.listeners.get(&var) {
373377
for callback in listeners {
@@ -425,8 +429,6 @@ mod tests {
425429
fn constant_le_unsat() {
426430
let mut e = Engine::new();
427431
assert!(!e.new_le(&c(10), &c(5), None)); // 10 <= 5
428-
assert!(!e.check());
429-
assert!(!e.conflict.is_empty());
430432
}
431433

432434
#[test]
@@ -484,9 +486,13 @@ mod tests {
484486
fn bound_conflict() {
485487
let mut e = Engine::new();
486488
let x = e.add_var();
487-
assert!(e.new_le(&v(x), &c(10), Some(0)));
488-
assert!(!e.new_gt(&v(x), &c(15), true, Some(1))); // x > 15 conflicts with x <= 10
489-
assert!(!e.check());
489+
let c0 = e.add_constraint();
490+
let c1 = e.add_constraint();
491+
assert!(e.new_le(&v(x), &c(10), Some(c0)));
492+
assert!(!e.new_gt(&v(x), &c(15), true, Some(c1))); // x > 15 conflicts with x <= 10
493+
let conflict = e.get_conflict_explanation().unwrap();
494+
assert!(conflict.contains(&c0));
495+
assert!(conflict.contains(&c1));
490496
}
491497

492498
#[test]
@@ -521,27 +527,24 @@ mod tests {
521527
assert!(e.new_eq(&v(y), &(c(10) - v(x)), None));
522528
// Violate y >= 12
523529
assert!(e.new_ge(&v(y), &c(12), None));
524-
assert!(!e.check()); // initially unsat? Wait, no — check should pivot
525-
// After pivot it should become feasible or conflict depending on exact expr
526-
// (This test demonstrates the path; adjust numbers so pivot succeeds)
530+
assert!(e.check());
527531
}
528532

529533
#[test]
530534
fn unsat_after_failed_pivot() {
531535
let mut e = Engine::new();
532536
let x = e.add_var();
533537
assert!(e.new_le(&v(x), &c(0), None));
534-
assert!(e.new_gt(&v(x), &c(10), false, None));
535-
assert!(!e.check());
536-
assert!(!e.conflict.is_empty());
538+
assert!(!e.new_gt(&v(x), &c(10), false, None));
539+
let conflict = e.get_conflict_explanation();
540+
assert!(conflict.is_none());
537541
}
538542

539543
#[test]
540544
fn strict_adjustment() {
541545
let mut e = Engine::new();
542546
let x = e.add_var();
543-
assert!(e.new_lt(&v(x), &c(0), true, None)); // x < 0 → x <= -1 in this solver
544-
// The exact bound depends on your `inf` implementation, but check is feasible
547+
assert!(e.new_lt(&v(x), &c(0), true, None));
545548
assert!(e.check());
546549
}
547550

@@ -590,17 +593,21 @@ mod tests {
590593
fn redundant_constraint() {
591594
let mut e = Engine::new();
592595
let x = e.add_var();
593-
assert!(e.new_le(&v(x), &c(10), Some(0)));
594-
assert!(e.new_le(&v(x), &c(20), Some(1))); // looser → redundant
596+
let c0 = e.add_constraint();
597+
let c1 = e.add_constraint();
598+
assert!(e.new_le(&v(x), &c(10), Some(c0)));
599+
assert!(e.new_le(&v(x), &c(20), Some(c1))); // looser → redundant
595600
assert!(e.check());
596601
}
597602

598603
#[test]
599604
fn tightening_bound() {
600605
let mut e = Engine::new();
601606
let x = e.add_var();
602-
assert!(e.new_le(&v(x), &c(20), Some(0)));
603-
assert!(e.new_le(&v(x), &c(10), Some(1))); // tighter
607+
let c0 = e.add_constraint();
608+
let c1 = e.add_constraint();
609+
assert!(e.new_le(&v(x), &c(20), Some(c0)));
610+
assert!(e.new_le(&v(x), &c(10), Some(c1))); // tighter
604611
assert!(e.ub(x) <= &i_rat(r(10)));
605612
}
606613
}

src/lin.rs

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,16 +90,44 @@ impl Display for Lin {
9090
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
9191
let mut first = true;
9292
for (var, coeff) in &self.vars {
93-
if !first && *coeff >= 0 {
94-
write!(f, "+")?;
93+
if first {
94+
if coeff.is_positive() {
95+
if coeff == &r(1) {
96+
write!(f, "x{}", var)?;
97+
} else {
98+
write!(f, "{}*x{}", coeff, var)?;
99+
}
100+
} else {
101+
if coeff == &r(-1) {
102+
write!(f, "-x{}", var)?;
103+
} else {
104+
write!(f, "-{}*x{}", -coeff, var)?;
105+
}
106+
}
107+
first = false;
108+
} else {
109+
if coeff.is_positive() {
110+
if coeff == &r(1) {
111+
write!(f, " + x{}", var)?;
112+
} else {
113+
write!(f, " + {}*x{}", coeff, var)?;
114+
}
115+
} else {
116+
if coeff == &r(-1) {
117+
write!(f, " - x{}", var)?;
118+
} else {
119+
write!(f, " - {}*x{}", -coeff, var)?;
120+
}
121+
}
95122
}
96-
write!(f, "{}*{}", coeff, var)?;
97-
first = false;
98123
}
99-
if !first && self.known_term >= 0 {
100-
write!(f, "+")?;
124+
if first {
125+
write!(f, "{}", self.known_term)
126+
} else if !self.known_term.is_zero() {
127+
if self.known_term.is_positive() { write!(f, " + {}", self.known_term) } else { write!(f, " - {}", -self.known_term) }
128+
} else {
129+
Ok(())
101130
}
102-
write!(f, "{}", self.known_term)
103131
}
104132
}
105133

@@ -557,8 +585,7 @@ mod tests {
557585

558586
// Display representation depends on map iteration order
559587
let s = format!("{}", lin);
560-
assert!(s.contains("1/2*1") || s.contains("-3/4*2"));
561-
assert!(s.contains("5"));
588+
assert!(s == "1/2*x1 - 3/4*x2 + 5" || s == "-3/4*x2 + 1/2*x1 + 5", "Unexpected display: {}", s);
562589
}
563590

564591
#[test]

src/rational.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,14 @@ impl Neg for Rational {
417417
}
418418
}
419419

420+
impl Neg for &Rational {
421+
type Output = Rational;
422+
423+
fn neg(self) -> Rational {
424+
Rational::new(-self.num, self.den)
425+
}
426+
}
427+
420428
impl Default for Rational {
421429
fn default() -> Self {
422430
Rational::ZERO

0 commit comments

Comments
 (0)