Skip to content

Commit 304af55

Browse files
committed
Add integer equality constraint for when unification is impossible
Resolves #149
1 parent 718c4ce commit 304af55

File tree

4 files changed

+90
-5
lines changed

4 files changed

+90
-5
lines changed

crates/huub/src/constraints/int_linear.rs

+49-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ use crate::{
2121
BoolDecision, BoolFormula, Conjunction, IntDecision, IntVal,
2222
};
2323

24+
/// Representation of an integer equality constraint that cannot be unified.
25+
///
26+
/// This constraint enforces that two integer decisions take the same value.
27+
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
28+
pub(crate) struct IntEq {
29+
/// The two integer decisions that must be equal.
30+
pub(crate) vars: [IntDecision; 2],
31+
}
32+
2433
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
2534
/// Representation of an integer linear constraint within a model.
2635
///
@@ -43,7 +52,7 @@ pub struct IntLinear {
4352
pub type IntLinearLessEqBounds = IntLinearLessEqBoundsImpl<0>;
4453

4554
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46-
/// Value consistent propagator for the `int_lin_le` or `int_lin_le_imp`
55+
/// Bounds consistent propagator for the `int_lin_le` or `int_lin_le_imp`
4756
/// constraint.
4857
///
4958
/// `R` should be `0` if the propagator is not refied, or `1` if it is. Other
@@ -104,6 +113,45 @@ pub(crate) enum Reification {
104113
ReifiedBy(BoolDecision),
105114
}
106115

116+
impl<S: SimplificationActions> Constraint<S> for IntEq {
117+
fn simplify(&mut self, actions: &mut S) -> Result<SimplificationStatus, ReformulationError> {
118+
let (lb_0, ub_0) = actions.get_int_bounds(self.vars[0]);
119+
let (lb_1, ub_1) = actions.get_int_bounds(self.vars[1]);
120+
121+
if lb_0 == ub_0 {
122+
actions.set_int_val(self.vars[1], lb_0)?;
123+
return Ok(SimplificationStatus::Subsumed);
124+
}
125+
if lb_1 == ub_1 {
126+
actions.set_int_val(self.vars[0], lb_1)?;
127+
return Ok(SimplificationStatus::Subsumed);
128+
}
129+
if lb_0 >= lb_1 {
130+
actions.set_int_lower_bound(self.vars[1], lb_0)?;
131+
}
132+
if lb_1 >= lb_0 {
133+
actions.set_int_lower_bound(self.vars[0], lb_1)?;
134+
}
135+
if ub_0 <= ub_1 {
136+
actions.set_int_upper_bound(self.vars[1], ub_0)?;
137+
}
138+
if ub_1 <= ub_0 {
139+
actions.set_int_upper_bound(self.vars[0], ub_1)?;
140+
}
141+
Ok(SimplificationStatus::Fixpoint)
142+
}
143+
144+
fn to_solver(&self, actions: &mut dyn ReformulationActions) -> Result<(), ReformulationError> {
145+
let lin = IntLinear {
146+
terms: vec![self.vars[0], -self.vars[1]],
147+
operator: LinOperator::Equal,
148+
rhs: 0,
149+
reif: None,
150+
};
151+
<IntLinear as Constraint<S>>::to_solver(&lin, actions)
152+
}
153+
}
154+
107155
impl IntLinear {
108156
/// Change the integer linear constraint to be implied by the given Boolean
109157
/// decision variable.

crates/huub/src/lib.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use crate::{
5454
int_array_minimum::IntArrayMinimum,
5555
int_div::IntDiv,
5656
int_in_set::IntInSetReif,
57-
int_linear::{IntLinear, LinOperator},
57+
int_linear::{IntEq, IntLinear, LinOperator},
5858
int_pow::IntPow,
5959
int_table::IntTable,
6060
int_times::IntTimes,
@@ -971,6 +971,7 @@ impl Model {
971971
ConstraintStore::DisjunctiveStrict(c) => c.simplify(self),
972972
ConstraintStore::IntAbs(c) => c.simplify(self),
973973
ConstraintStore::IntDiv(c) => c.simplify(self),
974+
ConstraintStore::IntEq(c) => c.simplify(self),
974975
ConstraintStore::IntLinear(c) => c.simplify(self),
975976
ConstraintStore::IntPow(c) => c.simplify(self),
976977
ConstraintStore::IntTimes(c) => c.simplify(self),
@@ -1055,6 +1056,9 @@ impl Model {
10551056
ConstraintStore::IntDiv(con) => {
10561057
<IntDiv as Constraint<Model>>::initialize(con, &mut ctx);
10571058
}
1059+
ConstraintStore::IntEq(con) => {
1060+
<IntEq as Constraint<Model>>::initialize(con, &mut ctx);
1061+
}
10581062
ConstraintStore::IntLinear(con) => {
10591063
<IntLinear as Constraint<Model>>::initialize(con, &mut ctx);
10601064
}
@@ -1256,6 +1260,12 @@ impl AddAssign<IntDiv> for Model {
12561260
}
12571261
}
12581262

1263+
impl AddAssign<IntEq> for Model {
1264+
fn add_assign(&mut self, constraint: IntEq) {
1265+
self.add_constraint(ConstraintStore::IntEq(constraint));
1266+
}
1267+
}
1268+
12591269
impl AddAssign<IntInSetReif> for Model {
12601270
fn add_assign(&mut self, constraint: IntInSetReif) {
12611271
self.add_constraint(ConstraintStore::IntInSetReif(constraint));
@@ -1853,7 +1863,8 @@ impl SimplificationActions for Model {
18531863
} else if can_define_x {
18541864
((x_t, x_i), (y_t, y_i))
18551865
} else {
1856-
todo!()
1866+
*self += IntEq { vars: [x, y] };
1867+
return Ok(());
18571868
};
18581869

18591870
// Perform the transformation and add the aliasing domain to x:

crates/huub/src/reformulate.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ use crate::{
4242
trail::TrailedInt,
4343
BoolView, BoolViewInner, IntView, IntViewInner, View,
4444
},
45-
BoolDecision, BoolFormula, Decision, IntDecision, IntLitMeaning, IntSetVal, IntVal, Model,
46-
Solver,
45+
BoolDecision, BoolFormula, Decision, IntDecision, IntEq, IntLitMeaning, IntSetVal, IntVal,
46+
Model, Solver,
4747
};
4848

4949
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
@@ -99,6 +99,7 @@ pub(crate) enum ConstraintStore {
9999
IntArrayMinimum(IntArrayMinimum),
100100
IntDecisionArrayElement(IntDecisionArrayElement),
101101
IntDiv(IntDiv),
102+
IntEq(IntEq),
102103
IntInSetReif(IntInSetReif),
103104
IntLinear(IntLinear),
104105
IntPow(IntPow),
@@ -266,6 +267,9 @@ impl ConstraintStore {
266267
ConstraintStore::IntDiv(con) => {
267268
<IntDiv as Constraint<Model>>::to_solver(con, &mut actions)
268269
}
270+
ConstraintStore::IntEq(con) => {
271+
<IntEq as Constraint<Model>>::to_solver(con, &mut actions)
272+
}
269273
ConstraintStore::IntInSetReif(con) => {
270274
<IntInSetReif as Constraint<Model>>::to_solver(con, &mut actions)
271275
}

crates/huub/src/tests.rs

+22
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,28 @@ fn it_works() {
2828
);
2929
}
3030

31+
#[test]
32+
fn unification_impossible() {
33+
let mut prb = Model::default();
34+
let a = prb.new_int_var((1..=5).into());
35+
let b = prb.new_int_var((1..=2).into());
36+
37+
let lin = (a * 2 - b * 5).eq(0);
38+
prb += lin;
39+
40+
let (mut slv, map): (Solver, _) = prb.to_solver(&InitConfig::default()).unwrap();
41+
let a = map.get_int(&mut slv, a);
42+
let b = map.get_int(&mut slv, b);
43+
44+
assert_eq!(
45+
slv.solve(|value| {
46+
assert_eq!(value(a.into()), Value::Int(5));
47+
assert_eq!(value(b.into()), Value::Int(2));
48+
}),
49+
SolveResult::Satisfied
50+
);
51+
}
52+
3153
impl Model {
3254
pub(crate) fn assert_unsatisfiable(&mut self) {
3355
let err: Result<(Solver, _), _> = self.to_solver(&InitConfig::default());

0 commit comments

Comments
 (0)