Skip to content

Commit

Permalink
Add int_abs bounds consistent propagator
Browse files Browse the repository at this point in the history
  • Loading branch information
Dekker1 committed Sep 3, 2024
1 parent d89953b commit b7d8415
Show file tree
Hide file tree
Showing 4 changed files with 213 additions and 2 deletions.
33 changes: 33 additions & 0 deletions crates/huub/src/model/constraint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use crate::{
array_int_minimum::ArrayIntMinimumBounds,
array_var_int_element::ArrayVarIntElementBounds,
disjunctive_stict::DisjunctiveStrictEdgeFinding,
int_abs::IntAbsBounds,
int_div::IntDivBounds,
int_lin_le::{IntLinearLessEqBounds, IntLinearLessEqImpBounds},
int_lin_ne::{IntLinearNotEqImpValue, IntLinearNotEqValue},
Expand All @@ -38,6 +39,7 @@ pub enum Constraint {
ArrayVarBoolElement(Vec<BoolExpr>, IntView, BoolExpr),
ArrayVarIntElement(Vec<IntView>, IntView, IntView),
DisjunctiveStrict(Vec<IntView>, Vec<IntVal>),
IntAbs(IntView, IntView),

Check warning on line 42 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L42

Added line #L42 was not covered by tests
IntDiv(IntView, IntView, IntView),
IntLinEq(Vec<IntView>, IntVal),
IntLinEqImp(Vec<IntView>, IntVal, BoolExpr),
Expand Down Expand Up @@ -182,6 +184,12 @@ impl Constraint {
))?;
Ok(())
}
Constraint::IntAbs(origin, abs) => {
let origin = origin.to_arg(slv, map);
let abs = abs.to_arg(slv, map);
slv.add_propagator(IntAbsBounds::prepare(origin, abs))?;
Ok(())
}

Check warning on line 192 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L187-L192

Added lines #L187 - L192 were not covered by tests
Constraint::IntDiv(numerator, denominator, result) => {
let numerator = numerator.to_arg(slv, map);
let denominator = denominator.to_arg(slv, map);
Expand Down Expand Up @@ -485,6 +493,31 @@ impl Model {
}
Some(Constraint::DisjunctiveStrict(starts, durations))
}
Constraint::IntAbs(origin, abs) => {
let lb = self.get_int_lower_bound(&origin);
let ub = self.get_int_upper_bound(&origin);
if ub < 0 {
self.set_int_lower_bound(&abs, -ub, con)?;
self.set_int_upper_bound(&abs, -lb, con)?;
} else if lb >= 0 {
self.set_int_lower_bound(&abs, lb, con)?;
self.set_int_upper_bound(&abs, ub, con)?;

Check warning on line 504 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L496-L504

Added lines #L496 - L504 were not covered by tests
} else {
self.set_int_lower_bound(&abs, 0, con)?;
let abs_max = ub.max(-lb);
self.set_int_upper_bound(&abs, abs_max, con)?;

Check warning on line 508 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L506-L508

Added lines #L506 - L508 were not covered by tests
}
let abs_ub = ub.abs();
debug_assert!(abs_ub >= 0);
self.set_int_lower_bound(&origin, -abs_ub, con)?;
self.set_int_upper_bound(&abs, abs_ub, con)?;
if lb >= 0 {

Check warning on line 514 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L510-L514

Added lines #L510 - L514 were not covered by tests
// TODO: Unify
Some(Constraint::IntLinEq(vec![origin, -abs], 0))

Check warning on line 516 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L516

Added line #L516 was not covered by tests
} else {
Some(Constraint::IntAbs(origin, abs))

Check warning on line 518 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L518

Added line #L518 was not covered by tests
}
}

Check warning on line 520 in crates/huub/src/model/constraint.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/model/constraint.rs#L520

Added line #L520 was not covered by tests
Constraint::IntDiv(num, denom, res) => {
self.diff_int_domain(&denom, &RangeList::from(0..=0), con)?;
Some(Constraint::IntDiv(num, denom, res))
Expand Down
1 change: 1 addition & 0 deletions crates/huub/src/propagator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub(crate) mod array_int_minimum;
pub(crate) mod array_var_int_element;
pub(crate) mod conflict;
pub(crate) mod disjunctive_stict;
pub(crate) mod int_abs;
pub(crate) mod int_div;
pub(crate) mod int_event;
pub(crate) mod int_lin_le;
Expand Down
179 changes: 179 additions & 0 deletions crates/huub/src/propagator/int_abs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use std::iter::once;

use crate::{
actions::{
explanation::ExplanationActions, initialization::InitializationActions,
trailing::TrailingActions,
},
propagator::{conflict::Conflict, int_event::IntEvent, PropagationActions, Propagator},
solver::{
engine::queue::PriorityLevel,
poster::{BoxedPropagator, Poster, QueuePreferences},
},
IntView, LitMeaning, ReformulationError,
};

#[derive(Debug, Clone, PartialEq, Eq, Hash)]

Check warning on line 16 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L16

Added line #L16 was not covered by tests
/// Bounds propagator for one integer variable being the absolute value of another
pub(crate) struct IntAbsBounds {
/// The integer variable whose absolute value is being taken
origin: IntView,

Check warning on line 20 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L20

Added line #L20 was not covered by tests
/// Whether the bounds of the origin variable have changed since the last propagation
orig_changed: bool,

Check warning on line 22 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L22

Added line #L22 was not covered by tests
/// The integer variable representing the absolute value
abs: IntView,

Check warning on line 24 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L24

Added line #L24 was not covered by tests
/// Whether the upper bound of the absolute value has changed since the last propagation
abs_changed: bool,

Check warning on line 26 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L26

Added line #L26 was not covered by tests
}

struct IntAbsBoundsPoster {
origin: IntView,
abs: IntView,
}

impl IntAbsBounds {
pub(crate) fn prepare(origin: IntView, abs: IntView) -> impl Poster {
IntAbsBoundsPoster { origin, abs }
}
}

impl<P, E, T> Propagator<P, E, T> for IntAbsBounds
where
P: PropagationActions,
E: ExplanationActions,
T: TrailingActions,
{
fn notify_event(&mut self, data: u32, _: &IntEvent, _: &mut T) -> bool {
match data {
1 => self.orig_changed = true,
2 => self.abs_changed = true,
_ => unreachable!("unexpected event data"),

Check warning on line 50 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L50

Added line #L50 was not covered by tests
};
true
}

fn notify_backtrack(&mut self, _new_level: usize) {
self.orig_changed = false;
self.abs_changed = false;
}

#[tracing::instrument(name = "int_abs", level = "trace", skip(self, actions))]
fn propagate(&mut self, actions: &mut P) -> Result<(), Conflict> {
let (lb, ub) = actions.get_int_bounds(self.origin);

if self.orig_changed {
self.orig_changed = false;
// If we know that the origin is negative, then just negate the bounds
if ub < 0 {
actions.set_int_upper_bound(self.abs, -lb, |a: &mut P| {
vec![
a.get_int_lower_bound_lit(self.origin),
a.get_int_lit(self.origin, LitMeaning::Less(0)),
]
})?;
actions.set_int_lower_bound(self.abs, -ub, |a: &mut P| {
once(a.get_int_upper_bound_lit(self.origin))
})?;
} else if lb >= 0 {
// If we know that the origin is positive, then the bounds are the same
actions.set_int_lower_bound(self.abs, lb, |a: &mut P| {
once(a.get_int_lower_bound_lit(self.origin))
})?;
actions.set_int_upper_bound(self.abs, ub, |a: &mut P| {
vec![
a.get_int_upper_bound_lit(self.origin),
a.get_int_lit(self.origin, LitMeaning::GreaterEq(0)),
]
})?;
} else {
// If the origin can be either positive or negative, then the bounds are the maximum of the absolute values
let abs_max = ub.max(-lb);
actions.set_int_upper_bound(self.abs, abs_max, |a: &mut P| {
vec![
a.get_int_lit(self.origin, LitMeaning::GreaterEq(-abs_max)),
a.get_int_lit(self.origin, LitMeaning::Less(abs_max + 1)),

Check warning on line 94 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L92-L94

Added lines #L92 - L94 were not covered by tests
]
})?;

Check warning on line 96 in crates/huub/src/propagator/int_abs.rs

View check run for this annotation

Codecov / codecov/patch

crates/huub/src/propagator/int_abs.rs#L96

Added line #L96 was not covered by tests
}
}

if self.abs_changed {
self.abs_changed = false;
let ub = actions.get_int_upper_bound(self.abs);
let ub_lit = actions.get_int_upper_bound_lit(self.abs);
actions.set_int_lower_bound(self.origin, -ub, ub_lit)?;
actions.set_int_upper_bound(self.origin, ub, ub_lit)?;
}

Ok(())
}
}

impl Poster for IntAbsBoundsPoster {
fn post<I: InitializationActions + ?Sized>(
self,
actions: &mut I,
) -> Result<(BoxedPropagator, QueuePreferences), ReformulationError> {
// Subscribe to both bounds of the origin variable
actions.subscribe_int(self.origin, IntEvent::Bounds, 1);
// Subscribe only to the upper bound of the absolute value variable
actions.subscribe_int(self.abs, IntEvent::UpperBound, 2);

Ok((
Box::new(IntAbsBounds {
origin: self.origin,
orig_changed: false,
abs: self.abs,
abs_changed: false,
}),
QueuePreferences {
enqueue_on_post: false,
priority: PriorityLevel::Highest,
},
))
}
}

#[cfg(test)]
mod tests {
use expect_test::expect;
use pindakaas::{solver::cadical::Cadical, Cnf};
use rangelist::RangeList;
use tracing_test::traced_test;

use crate::{
propagator::int_abs::IntAbsBounds,
solver::engine::int_var::{EncodingType, IntVar},
Solver,
};

#[test]
#[traced_test]
fn test_int_abs_sat() {
let mut slv: Solver<Cadical> = Cnf::default().into();
let a = IntVar::new_in(
&mut slv,
(-3..=3).into(),
EncodingType::Eager,
EncodingType::Lazy,
);
let b = IntVar::new_in(
&mut slv,
RangeList::from_iter([-3..=3]),
EncodingType::Eager,
EncodingType::Lazy,
);
slv.add_propagator(IntAbsBounds::prepare(a, b)).unwrap();
slv.expect_solutions(
&[a, b],
expect![[r#"
-3, 3
-2, 2
-1, 1
0, 0
1, 1
2, 2
3, 3"#]],
);
}
}
2 changes: 0 additions & 2 deletions share/minizinc/huub/redefinitions.mzn
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ predicate bool_lt(var bool: a, var bool: b) = not a /\ b;
predicate bool_lt_imp(var bool: a, var bool: b, var bool: r) = r -> (not a /\ b);
predicate bool_lt_reif(var bool: a, var bool: b, var bool: r) = r = (not a /\ b);

predicate int_abs(var int: a, var int: b) = b = [a,-a][1 + (a < 0)];

predicate int_eq_imp(var int: a, var int: b, var bool: r);
predicate int_le_imp(var int: a, var int: b, var bool: r);
predicate int_ne_imp(var int: a, var int: b, var bool: r);
Expand Down

0 comments on commit b7d8415

Please sign in to comment.