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::state::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!(), &self.inference_code),
98 )?;
99
100 let signed_lb = context.lower_bound(&self.signed);
108 let signed_ub = context.upper_bound(&self.signed);
109
110 let signed_absolute_ub = i32::max(signed_lb.abs(), signed_ub.abs());
111
112 context.post(
113 predicate![self.absolute <= signed_absolute_ub],
114 (
115 conjunction!([self.signed >= signed_lb] & [self.signed <= signed_ub]),
116 &self.inference_code,
117 ),
118 )?;
119
120 if signed_lb > 0 {
121 context.post(
122 predicate![self.absolute >= signed_lb],
123 (
124 conjunction!([self.signed >= signed_lb]),
125 &self.inference_code,
126 ),
127 )?;
128 } else if signed_ub < 0 {
129 context.post(
130 predicate![self.absolute >= signed_ub.abs()],
131 (
132 conjunction!([self.signed <= signed_ub]),
133 &self.inference_code,
134 ),
135 )?;
136 }
137
138 let absolute_ub = context.upper_bound(&self.absolute);
139 let absolute_lb = context.lower_bound(&self.absolute);
140 context.post(
141 predicate![self.signed >= -absolute_ub],
142 (
143 conjunction!([self.absolute <= absolute_ub]),
144 &self.inference_code,
145 ),
146 )?;
147 context.post(
148 predicate![self.signed <= absolute_ub],
149 (
150 conjunction!([self.absolute <= absolute_ub]),
151 &self.inference_code,
152 ),
153 )?;
154
155 if signed_ub <= 0 {
156 context.post(
157 predicate![self.signed <= -absolute_lb],
158 (
159 conjunction!([self.signed <= 0] & [self.absolute >= absolute_lb]),
160 &self.inference_code,
161 ),
162 )?;
163 } else if signed_lb >= 0 {
164 context.post(
165 predicate![self.signed >= absolute_lb],
166 (
167 conjunction!([self.signed >= 0] & [self.absolute >= absolute_lb]),
168 &self.inference_code,
169 ),
170 )?;
171 }
172
173 Ok(())
174 }
175}
176
177#[derive(Clone, Debug)]
178pub struct AbsoluteValueChecker<VA, VB> {
179 signed: VA,
180 absolute: VB,
181}
182
183impl<VA, VB, Atomic> InferenceChecker<Atomic> for AbsoluteValueChecker<VA, VB>
184where
185 VA: CheckerVariable<Atomic>,
186 VB: CheckerVariable<Atomic>,
187 Atomic: AtomicConstraint,
188{
189 fn check(
190 &self,
191 state: pumpkin_checking::VariableState<Atomic>,
192 _: &[Atomic],
193 _: Option<&Atomic>,
194 ) -> bool {
195 let signed_lower = self.signed.induced_lower_bound(&state);
196 let signed_upper = self.signed.induced_upper_bound(&state);
197 let absolute_lower = self.absolute.induced_lower_bound(&state);
198 let absolute_upper = self.absolute.induced_upper_bound(&state);
199
200 if absolute_lower < 0 {
201 return true;
203 }
204
205 let (computed_signed_lower, computed_signed_upper) = if signed_lower >= 0 {
207 (signed_lower, signed_upper)
208 } else if signed_upper <= 0 {
209 (-signed_upper, -signed_lower)
210 } else if signed_lower < 0 && 0_i32 < signed_upper {
211 (IntExt::Int(0), std::cmp::max(-signed_lower, signed_upper))
212 } else {
213 unreachable!()
214 };
215
216 computed_signed_lower != absolute_lower || computed_signed_upper != absolute_upper
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use pumpkin_core::state::State;
224
225 use super::*;
226 use crate::StateExt;
227
228 #[test]
229 fn absolute_bounds_are_propagated_at_initialise() {
230 let mut state = State::default();
231
232 let signed = state.new_interval_variable(-3, 4, None);
233 let absolute = state.new_interval_variable(-2, 10, None);
234 let constraint_tag = state.new_constraint_tag();
235
236 let _ = state.add_propagator(AbsoluteValueArgs {
237 signed,
238 absolute,
239 constraint_tag,
240 });
241 state.propagate_to_fixed_point().expect("no empty domains");
242
243 state.assert_bounds(absolute, 0, 4);
244 }
245
246 #[test]
247 fn signed_bounds_are_propagated_at_initialise() {
248 let mut state = State::default();
249
250 let signed = state.new_interval_variable(-5, 5, None);
251 let absolute = state.new_interval_variable(0, 3, None);
252 let constraint_tag = state.new_constraint_tag();
253
254 let _ = state.add_propagator(AbsoluteValueArgs {
255 signed,
256 absolute,
257 constraint_tag,
258 });
259 state.propagate_to_fixed_point().expect("no empty domains");
260
261 state.assert_bounds(signed, -3, 3);
262 }
263
264 #[test]
265 fn absolute_lower_bound_can_be_strictly_positive() {
266 let mut state = State::default();
267
268 let signed = state.new_interval_variable(3, 6, None);
269 let absolute = state.new_interval_variable(0, 10, None);
270 let constraint_tag = state.new_constraint_tag();
271
272 let _ = state.add_propagator(AbsoluteValueArgs {
273 signed,
274 absolute,
275 constraint_tag,
276 });
277 state.propagate_to_fixed_point().expect("no empty domains");
278
279 state.assert_bounds(absolute, 3, 6);
280 }
281
282 #[test]
283 fn strictly_negative_signed_value_can_propagate_lower_bound_on_absolute() {
284 let mut state = State::default();
285
286 let signed = state.new_interval_variable(-5, -3, None);
287 let absolute = state.new_interval_variable(1, 5, None);
288 let constraint_tag = state.new_constraint_tag();
289
290 let _ = state.add_propagator(AbsoluteValueArgs {
291 signed,
292 absolute,
293 constraint_tag,
294 });
295 state.propagate_to_fixed_point().expect("no empty domains");
296
297 state.assert_bounds(absolute, 3, 5);
298 }
299
300 #[test]
301 fn lower_bound_on_absolute_can_propagate_negative_upper_bound_on_signed() {
302 let mut state = State::default();
303
304 let signed = state.new_interval_variable(-5, 0, None);
305 let absolute = state.new_interval_variable(1, 5, None);
306 let constraint_tag = state.new_constraint_tag();
307
308 let _ = state.add_propagator(AbsoluteValueArgs {
309 signed,
310 absolute,
311 constraint_tag,
312 });
313 state.propagate_to_fixed_point().expect("no empty domains");
314
315 state.assert_bounds(signed, -5, -1);
316 }
317
318 #[test]
319 fn lower_bound_on_absolute_can_propagate_positive_lower_bound_on_signed() {
320 let mut state = State::default();
321
322 let signed = state.new_interval_variable(1, 5, None);
323 let absolute = state.new_interval_variable(3, 5, None);
324 let constraint_tag = state.new_constraint_tag();
325
326 let _ = state.add_propagator(AbsoluteValueArgs {
327 signed,
328 absolute,
329 constraint_tag,
330 });
331 state.propagate_to_fixed_point().expect("no empty domains");
332
333 state.assert_bounds(signed, 3, 5);
334 }
335}