Skip to main content

pumpkin_propagators/propagators/arithmetic/
absolute_value.rs

1use pumpkin_checking::AtomicConstraint;
2use pumpkin_checking::CheckerVariable;
3use pumpkin_checking::InferenceChecker;
4use pumpkin_checking::IntExt;
5use pumpkin_core::conjunction;
6use pumpkin_core::declare_inference_label;
7use pumpkin_core::predicate;
8use pumpkin_core::proof::ConstraintTag;
9use pumpkin_core::proof::InferenceCode;
10use pumpkin_core::propagation::DomainEvents;
11use pumpkin_core::propagation::InferenceCheckers;
12use pumpkin_core::propagation::LocalId;
13use pumpkin_core::propagation::Priority;
14use pumpkin_core::propagation::PropagationContext;
15use pumpkin_core::propagation::Propagator;
16use pumpkin_core::propagation::PropagatorConstructor;
17use pumpkin_core::propagation::PropagatorConstructorContext;
18use pumpkin_core::propagation::ReadDomains;
19use pumpkin_core::results::PropagationStatusCP;
20use pumpkin_core::variables::IntegerVariable;
21
22declare_inference_label!(AbsoluteValue);
23
24#[derive(Clone, Debug)]
25pub struct AbsoluteValueArgs<VA, VB> {
26    pub signed: VA,
27    pub absolute: VB,
28    pub constraint_tag: ConstraintTag,
29}
30
31impl<VA, VB> PropagatorConstructor for AbsoluteValueArgs<VA, VB>
32where
33    VA: IntegerVariable + 'static,
34    VB: IntegerVariable + 'static,
35{
36    type PropagatorImpl = AbsoluteValuePropagator<VA, VB>;
37
38    fn add_inference_checkers(&self, mut checkers: InferenceCheckers<'_>) {
39        checkers.add_inference_checker(
40            InferenceCode::new(self.constraint_tag, AbsoluteValue),
41            Box::new(AbsoluteValueChecker {
42                signed: self.signed.clone(),
43                absolute: self.absolute.clone(),
44            }),
45        );
46    }
47
48    fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl {
49        let AbsoluteValueArgs {
50            signed,
51            absolute,
52            constraint_tag,
53        } = self;
54
55        context.register(signed.clone(), DomainEvents::BOUNDS, LocalId::from(0));
56        context.register(absolute.clone(), DomainEvents::BOUNDS, LocalId::from(1));
57
58        let inference_code = InferenceCode::new(constraint_tag, AbsoluteValue);
59
60        AbsoluteValuePropagator {
61            signed,
62            absolute,
63            inference_code,
64        }
65    }
66}
67
68/// Propagator for `absolute = |signed|`, where `absolute` and `signed` are integer variables.
69///
70/// The propagator is bounds consistent wrt signed. That means that if `signed \in {-2, -1, 1, 2}`,
71/// the propagator will not propagate `[absolute >= 1]`.
72#[derive(Clone, Debug)]
73pub struct AbsoluteValuePropagator<VA, VB> {
74    signed: VA,
75    absolute: VB,
76    inference_code: InferenceCode,
77}
78
79impl<VA, VB> Propagator for AbsoluteValuePropagator<VA, VB>
80where
81    VA: IntegerVariable + 'static,
82    VB: IntegerVariable + 'static,
83{
84    fn priority(&self) -> Priority {
85        Priority::High
86    }
87
88    fn name(&self) -> &str {
89        "IntAbs"
90    }
91
92    fn propagate_from_scratch(&self, mut context: PropagationContext) -> PropagationStatusCP {
93        // The bound of absolute may be tightened further during propagation, but it is at least
94        // zero at the root.
95        context.post(
96            predicate![self.absolute >= 0],
97            conjunction!(),
98            &self.inference_code,
99        )?;
100
101        // Propagating absolute value can be broken into a few cases:
102        // - `signed` is sign-fixed (i.e. `upper_bound <= 0` or `lower_bound >= 0`), in which case
103        //   the bounds of `signed` can be propagated to `absolute` (taking care of swapping bounds
104        //   when the `signed` is negative).
105        // - `signed` is not sign-fixed (i.e. `lower_bound <= 0` and `upper_bound >= 0`), in which
106        //   case the lower bound of `absolute` cannot be tightened without looking into specific
107        //   domain values for `signed`, which we don't do.
108        let signed_lb = context.lower_bound(&self.signed);
109        let signed_ub = context.upper_bound(&self.signed);
110
111        let signed_absolute_ub = i32::max(signed_lb.abs(), signed_ub.abs());
112
113        context.post(
114            predicate![self.absolute <= signed_absolute_ub],
115            conjunction!([self.signed >= signed_lb] & [self.signed <= signed_ub]),
116            &self.inference_code,
117        )?;
118
119        if signed_lb > 0 {
120            context.post(
121                predicate![self.absolute >= signed_lb],
122                conjunction!([self.signed >= signed_lb]),
123                &self.inference_code,
124            )?;
125        } else if signed_ub < 0 {
126            context.post(
127                predicate![self.absolute >= signed_ub.abs()],
128                conjunction!([self.signed <= signed_ub]),
129                &self.inference_code,
130            )?;
131        }
132
133        let absolute_ub = context.upper_bound(&self.absolute);
134        let absolute_lb = context.lower_bound(&self.absolute);
135        context.post(
136            predicate![self.signed >= -absolute_ub],
137            conjunction!([self.absolute <= absolute_ub]),
138            &self.inference_code,
139        )?;
140        context.post(
141            predicate![self.signed <= absolute_ub],
142            conjunction!([self.absolute <= absolute_ub]),
143            &self.inference_code,
144        )?;
145
146        if signed_ub <= 0 {
147            context.post(
148                predicate![self.signed <= -absolute_lb],
149                conjunction!([self.signed <= 0] & [self.absolute >= absolute_lb]),
150                &self.inference_code,
151            )?;
152        } else if signed_lb >= 0 {
153            context.post(
154                predicate![self.signed >= absolute_lb],
155                conjunction!([self.signed >= 0] & [self.absolute >= absolute_lb]),
156                &self.inference_code,
157            )?;
158        }
159
160        Ok(())
161    }
162}
163
164#[derive(Clone, Debug)]
165pub struct AbsoluteValueChecker<VA, VB> {
166    signed: VA,
167    absolute: VB,
168}
169
170impl<VA, VB, Atomic> InferenceChecker<Atomic> for AbsoluteValueChecker<VA, VB>
171where
172    VA: CheckerVariable<Atomic>,
173    VB: CheckerVariable<Atomic>,
174    Atomic: AtomicConstraint,
175{
176    fn check(
177        &self,
178        state: pumpkin_checking::VariableState<Atomic>,
179        _: &[Atomic],
180        _: Option<&Atomic>,
181    ) -> bool {
182        let signed_lower = self.signed.induced_lower_bound(&state);
183        let signed_upper = self.signed.induced_upper_bound(&state);
184        let absolute_lower = self.absolute.induced_lower_bound(&state);
185        let absolute_upper = self.absolute.induced_upper_bound(&state);
186
187        if absolute_lower < 0 {
188            // The absolute value cannot have negative values.
189            return true;
190        }
191
192        // Now we compute the interval for |signed| based on the domain of signed.
193        let (computed_signed_lower, computed_signed_upper) = if signed_lower >= 0 {
194            (signed_lower, signed_upper)
195        } else if signed_upper <= 0 {
196            (-signed_upper, -signed_lower)
197        } else if signed_lower < 0 && 0_i32 < signed_upper {
198            (IntExt::Int(0), std::cmp::max(-signed_lower, signed_upper))
199        } else {
200            unreachable!()
201        };
202
203        // The intervals should not match, otherwise there is no conflict.
204        computed_signed_lower != absolute_lower || computed_signed_upper != absolute_upper
205    }
206}
207
208#[allow(deprecated, reason = "Will be refactored")]
209#[cfg(test)]
210mod tests {
211    use pumpkin_core::TestSolver;
212
213    use super::*;
214
215    #[test]
216    fn absolute_bounds_are_propagated_at_initialise() {
217        let mut solver = TestSolver::default();
218
219        let signed = solver.new_variable(-3, 4);
220        let absolute = solver.new_variable(-2, 10);
221        let constraint_tag = solver.new_constraint_tag();
222
223        let _ = solver
224            .new_propagator(AbsoluteValueArgs {
225                signed,
226                absolute,
227                constraint_tag,
228            })
229            .expect("no empty domains");
230
231        solver.assert_bounds(absolute, 0, 4);
232    }
233
234    #[test]
235    fn signed_bounds_are_propagated_at_initialise() {
236        let mut solver = TestSolver::default();
237
238        let signed = solver.new_variable(-5, 5);
239        let absolute = solver.new_variable(0, 3);
240        let constraint_tag = solver.new_constraint_tag();
241
242        let _ = solver
243            .new_propagator(AbsoluteValueArgs {
244                signed,
245                absolute,
246                constraint_tag,
247            })
248            .expect("no empty domains");
249
250        solver.assert_bounds(signed, -3, 3);
251    }
252
253    #[test]
254    fn absolute_lower_bound_can_be_strictly_positive() {
255        let mut solver = TestSolver::default();
256
257        let signed = solver.new_variable(3, 6);
258        let absolute = solver.new_variable(0, 10);
259        let constraint_tag = solver.new_constraint_tag();
260
261        let _ = solver
262            .new_propagator(AbsoluteValueArgs {
263                signed,
264                absolute,
265                constraint_tag,
266            })
267            .expect("no empty domains");
268
269        solver.assert_bounds(absolute, 3, 6);
270    }
271
272    #[test]
273    fn strictly_negative_signed_value_can_propagate_lower_bound_on_absolute() {
274        let mut solver = TestSolver::default();
275
276        let signed = solver.new_variable(-5, -3);
277        let absolute = solver.new_variable(1, 5);
278        let constraint_tag = solver.new_constraint_tag();
279
280        let _ = solver
281            .new_propagator(AbsoluteValueArgs {
282                signed,
283                absolute,
284                constraint_tag,
285            })
286            .expect("no empty domains");
287
288        solver.assert_bounds(absolute, 3, 5);
289    }
290
291    #[test]
292    fn lower_bound_on_absolute_can_propagate_negative_upper_bound_on_signed() {
293        let mut solver = TestSolver::default();
294
295        let signed = solver.new_variable(-5, 0);
296        let absolute = solver.new_variable(1, 5);
297        let constraint_tag = solver.new_constraint_tag();
298
299        let _ = solver
300            .new_propagator(AbsoluteValueArgs {
301                signed,
302                absolute,
303                constraint_tag,
304            })
305            .expect("no empty domains");
306
307        solver.assert_bounds(signed, -5, -1);
308    }
309
310    #[test]
311    fn lower_bound_on_absolute_can_propagate_positive_lower_bound_on_signed() {
312        let mut solver = TestSolver::default();
313
314        let signed = solver.new_variable(1, 5);
315        let absolute = solver.new_variable(3, 5);
316        let constraint_tag = solver.new_constraint_tag();
317
318        let _ = solver
319            .new_propagator(AbsoluteValueArgs {
320                signed,
321                absolute,
322                constraint_tag,
323            })
324            .expect("no empty domains");
325
326        solver.assert_bounds(signed, 3, 5);
327    }
328}