use std::{
cmp,
ops::{Neg, Not},
};
use crate::{
actions::{
InitActions, IntDecisionActions, IntPropCond, PostingActions, ReasoningContext,
ReasoningEngine,
},
constraints::{
BoolModelActions, BoolSolverActions, Constraint, IntModelActions, IntSolverActions,
Propagator, SimplificationStatus,
},
lower::{LoweringContext, LoweringError},
solver::{
Decision, IntLitMeaning, View, engine::Engine, queue::PriorityLevel,
view::boolean::BoolView,
},
};
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct IntAbsBounds<I1, I2, B> {
pub(crate) origin: I1,
pub(crate) abs: I2,
pub(crate) origin_positive: B,
}
impl<I1, I2> IntAbsBounds<I1, I2, Decision<bool>> {
pub(crate) fn post<E, I2Neg>(solver: &mut E, origin: I1, abs: I2)
where
E: PostingActions + ReasoningContext<Atom = View<bool>> + ?Sized,
I1: IntDecisionActions<E> + IntSolverActions<Engine>,
I2: IntSolverActions<Engine> + Neg<Output = I2Neg> + Into<I1>,
I2Neg: Into<I1>,
{
let BoolView::Lit(origin_positive) = origin.lit(solver, IntLitMeaning::GreaterEq(0)).0
else {
panic!("origin variable in absolute value constraint is known positive or negative");
};
solver.add_propagator(Box::new(Self {
origin,
abs,
origin_positive,
}));
}
}
impl<B, E, I1, I2, I2Neg> Constraint<E> for IntAbsBounds<I1, I2, B>
where
E: ReasoningEngine,
I1: IntModelActions<E>,
I2: IntModelActions<E> + Neg<Output = I2Neg> + Into<I1>,
I2Neg: Into<I1>,
B: BoolModelActions<E> + Not<Output = B>,
{
fn simplify(
&mut self,
ctx: &mut E::PropagationContext<'_>,
) -> Result<SimplificationStatus, E::Conflict> {
match self.origin_positive.val(ctx) {
Some(true) => {
self.origin.unify(ctx, self.abs.clone())?;
Ok(SimplificationStatus::Subsumed)
}
Some(false) => {
self.origin.unify(ctx, -self.abs.clone())?;
Ok(SimplificationStatus::Subsumed)
}
None => {
<Self as Propagator<E>>::propagate(self, ctx)?;
Ok(SimplificationStatus::NoFixpoint)
}
}
}
fn to_solver(&self, slv: &mut LoweringContext<'_>) -> Result<(), LoweringError> {
let origin = slv.solver_view(self.origin.clone().into());
let abs = slv.solver_view(self.abs.clone().into());
IntAbsBounds::post(slv, origin, abs);
Ok(())
}
}
impl<B, E, I1, I2> Propagator<E> for IntAbsBounds<I1, I2, B>
where
B: BoolSolverActions<E> + Not<Output = B>,
E: ReasoningEngine,
I1: IntSolverActions<E>,
I2: IntSolverActions<E>,
{
fn initialize(&mut self, ctx: &mut E::InitializationContext<'_>) {
ctx.set_priority(PriorityLevel::Highest);
self.origin.enqueue_when(ctx, IntPropCond::Bounds);
self.abs.enqueue_when(ctx, IntPropCond::Bounds);
}
#[tracing::instrument(
name = "int_abs_bounds",
target = "solver",
level = "trace",
skip(self, ctx)
)]
fn propagate(&mut self, ctx: &mut E::PropagationContext<'_>) -> Result<(), E::Conflict> {
let (lb, ub) = self.origin.bounds(ctx);
match self.origin_positive.val(ctx) {
Some(false) => {
self.abs
.tighten_min(ctx, -ub, |ctx: &mut E::PropagationContext<'_>| {
[self.origin.max_lit(ctx)]
})?;
self.abs
.tighten_max(ctx, -lb, |ctx: &mut E::PropagationContext<'_>| {
[
self.origin.min_lit(ctx),
(!self.origin_positive.clone()).into(),
]
})?;
let (lb, ub) = self.abs.bounds(ctx);
self.origin
.tighten_min(ctx, -ub, |ctx: &mut E::PropagationContext<'_>| {
[self.abs.max_lit(ctx)]
})?;
self.origin
.tighten_max(ctx, -lb, |ctx: &mut E::PropagationContext<'_>| {
[
self.abs.min_lit(ctx),
(!self.origin_positive.clone()).into(),
]
})?;
}
Some(true) => {
self.abs
.tighten_min(ctx, lb, |ctx: &mut E::PropagationContext<'_>| {
[self.origin.min_lit(ctx)]
})?;
self.abs
.tighten_max(ctx, ub, |ctx: &mut E::PropagationContext<'_>| {
[
self.origin.max_lit(ctx),
self.origin_positive.clone().into(),
]
})?;
let (lb, ub) = self.abs.bounds(ctx);
self.origin
.tighten_min(ctx, lb, |ctx: &mut E::PropagationContext<'_>| {
[self.abs.min_lit(ctx), self.origin_positive.clone().into()]
})?;
self.origin
.tighten_max(ctx, ub, |ctx: &mut E::PropagationContext<'_>| {
[self.abs.max_lit(ctx)]
})?;
}
None => {
let abs_max = cmp::max(ub, -lb);
self.abs
.tighten_max(ctx, abs_max, |ctx: &mut E::PropagationContext<'_>| {
[
self.origin.lit(ctx, IntLitMeaning::GreaterEq(-abs_max)),
self.origin.lit(ctx, IntLitMeaning::Less(abs_max + 1)),
]
})?;
let abs_ub = self.abs.max(ctx);
let ub_lit = self.abs.max_lit(ctx);
self.origin.tighten_min(ctx, -abs_ub, [ub_lit.clone()])?;
self.origin.tighten_max(ctx, abs_ub, [ub_lit])?;
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use expect_test::expect;
use tracing_test::traced_test;
use crate::{
constraints::int_abs::IntAbsBounds,
solver::{LiteralStrategy, Solver},
};
#[test]
#[traced_test]
fn test_int_abs_sat() {
let mut slv = Solver::default();
let a = slv
.new_int_decision(-3..=3)
.order_literals(LiteralStrategy::Eager)
.view();
let b = slv
.new_int_decision(-3..=3)
.order_literals(LiteralStrategy::Eager)
.view();
IntAbsBounds::post(&mut slv, a, b);
slv.expect_solutions(
&[a, b],
expect![[r#"
-3, 3
-2, 2
-1, 1
0, 0
1, 1
2, 2
3, 3"#]],
);
}
}