pumpkin_propagators/propagators/arithmetic/
absolute_value.rs1use 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#[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 context.post(
96 predicate![self.absolute >= 0],
97 conjunction!(),
98 &self.inference_code,
99 )?;
100
101 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 return true;
190 }
191
192 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 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}