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::EventsToRegister;
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::PropagatorSpec;
19use pumpkin_core::propagation::ReadDomains;
20use pumpkin_core::propagation::RuntimeCheckers;
21use pumpkin_core::state::PropagationStatusCP;
22use pumpkin_core::variables::IntegerVariable;
23
24declare_inference_label!(AbsoluteValue);
25
26#[derive(Clone, Debug)]
27pub struct AbsoluteValueArgs<VA, VB> {
28 pub signed: VA,
29 pub absolute: VB,
30 pub constraint_tag: ConstraintTag,
31}
32
33impl<VA, VB> PropagatorConstructor for AbsoluteValueArgs<VA, VB>
34where
35 VA: IntegerVariable + 'static,
36 VB: IntegerVariable + 'static,
37{
38 type PropagatorImpl = AbsoluteValuePropagator<VA, VB>;
39
40 fn create(self, _: PropagatorConstructorContext) -> PropagatorSpec<Self::PropagatorImpl> {
41 let AbsoluteValueArgs {
42 signed,
43 absolute,
44 constraint_tag,
45 } = self;
46
47 let registration = EventsToRegister::builder()
48 .add(&signed, DomainEvents::BOUNDS, LocalId::from(0))
49 .add(&absolute, DomainEvents::BOUNDS, LocalId::from(1))
50 .build();
51
52 let mut checkers = RuntimeCheckers::builder();
53 let inference_code = checkers.add_inference_checker(
54 constraint_tag,
55 AbsoluteValue,
56 AbsoluteValueChecker {
57 signed: signed.clone(),
58 absolute: absolute.clone(),
59 },
60 );
61
62 let propagator = AbsoluteValuePropagator {
63 signed,
64 absolute,
65 inference_code,
66 };
67
68 PropagatorSpec {
69 registration,
70 checkers: checkers.build(),
71 propagator,
72 }
73 }
74}
75
76#[derive(Clone, Debug)]
81pub struct AbsoluteValuePropagator<VA, VB> {
82 signed: VA,
83 absolute: VB,
84 inference_code: InferenceCode,
85}
86
87impl<VA, VB> Propagator for AbsoluteValuePropagator<VA, VB>
88where
89 VA: IntegerVariable + 'static,
90 VB: IntegerVariable + 'static,
91{
92 fn priority(&self) -> Priority {
93 Priority::High
94 }
95
96 fn name(&self) -> &str {
97 "IntAbs"
98 }
99
100 fn propagate_from_scratch(&self, mut context: PropagationContext) -> PropagationStatusCP {
101 context.post(
104 predicate![self.absolute >= 0],
105 (conjunction!(), &self.inference_code),
106 )?;
107
108 let signed_lb = context.lower_bound(&self.signed);
116 let signed_ub = context.upper_bound(&self.signed);
117
118 let signed_absolute_ub = i32::max(signed_lb.abs(), signed_ub.abs());
119
120 context.post(
121 predicate![self.absolute <= signed_absolute_ub],
122 (
123 conjunction!([self.signed >= signed_lb] & [self.signed <= signed_ub]),
124 &self.inference_code,
125 ),
126 )?;
127
128 if signed_lb > 0 {
129 context.post(
130 predicate![self.absolute >= signed_lb],
131 (
132 conjunction!([self.signed >= signed_lb]),
133 &self.inference_code,
134 ),
135 )?;
136 } else if signed_ub < 0 {
137 context.post(
138 predicate![self.absolute >= signed_ub.abs()],
139 (
140 conjunction!([self.signed <= signed_ub]),
141 &self.inference_code,
142 ),
143 )?;
144 }
145
146 let absolute_ub = context.upper_bound(&self.absolute);
147 let absolute_lb = context.lower_bound(&self.absolute);
148 context.post(
149 predicate![self.signed >= -absolute_ub],
150 (
151 conjunction!([self.absolute <= absolute_ub]),
152 &self.inference_code,
153 ),
154 )?;
155 context.post(
156 predicate![self.signed <= absolute_ub],
157 (
158 conjunction!([self.absolute <= absolute_ub]),
159 &self.inference_code,
160 ),
161 )?;
162
163 if signed_ub <= 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 } else if signed_lb >= 0 {
172 context.post(
173 predicate![self.signed >= absolute_lb],
174 (
175 conjunction!([self.signed >= 0] & [self.absolute >= absolute_lb]),
176 &self.inference_code,
177 ),
178 )?;
179 }
180
181 Ok(())
182 }
183}
184
185#[derive(Clone, Debug)]
186pub struct AbsoluteValueChecker<VA, VB> {
187 signed: VA,
188 absolute: VB,
189}
190
191impl<VA, VB, Atomic> InferenceChecker<Atomic> for AbsoluteValueChecker<VA, VB>
192where
193 VA: CheckerVariable<Atomic>,
194 VB: CheckerVariable<Atomic>,
195 Atomic: AtomicConstraint,
196{
197 fn check(
198 &self,
199 state: pumpkin_checking::VariableState<Atomic>,
200 _: &[Atomic],
201 _: Option<&Atomic>,
202 ) -> bool {
203 let signed_lower = self.signed.induced_lower_bound(&state);
204 let signed_upper = self.signed.induced_upper_bound(&state);
205 let absolute_lower = self.absolute.induced_lower_bound(&state);
206 let absolute_upper = self.absolute.induced_upper_bound(&state);
207
208 if absolute_lower < 0 {
209 return true;
211 }
212
213 let (computed_signed_lower, computed_signed_upper) = if signed_lower >= 0 {
215 (signed_lower, signed_upper)
216 } else if signed_upper <= 0 {
217 (-signed_upper, -signed_lower)
218 } else if signed_lower < 0 && 0_i32 < signed_upper {
219 (IntExt::Int(0), std::cmp::max(-signed_lower, signed_upper))
220 } else {
221 unreachable!()
222 };
223
224 computed_signed_lower != absolute_lower || computed_signed_upper != absolute_upper
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use pumpkin_core::state::State;
232
233 use super::*;
234 use crate::StateExt;
235
236 #[test]
237 fn absolute_bounds_are_propagated_at_initialise() {
238 let mut state = State::default();
239
240 let signed = state.new_interval_variable(-3, 4, None);
241 let absolute = state.new_interval_variable(-2, 10, None);
242 let constraint_tag = state.new_constraint_tag();
243
244 let _ = state.add_propagator(AbsoluteValueArgs {
245 signed,
246 absolute,
247 constraint_tag,
248 });
249 state.propagate_to_fixed_point().expect("no empty domains");
250
251 state.assert_bounds(absolute, 0, 4);
252 }
253
254 #[test]
255 fn signed_bounds_are_propagated_at_initialise() {
256 let mut state = State::default();
257
258 let signed = state.new_interval_variable(-5, 5, None);
259 let absolute = state.new_interval_variable(0, 3, None);
260 let constraint_tag = state.new_constraint_tag();
261
262 let _ = state.add_propagator(AbsoluteValueArgs {
263 signed,
264 absolute,
265 constraint_tag,
266 });
267 state.propagate_to_fixed_point().expect("no empty domains");
268
269 state.assert_bounds(signed, -3, 3);
270 }
271
272 #[test]
273 fn absolute_lower_bound_can_be_strictly_positive() {
274 let mut state = State::default();
275
276 let signed = state.new_interval_variable(3, 6, None);
277 let absolute = state.new_interval_variable(0, 10, None);
278 let constraint_tag = state.new_constraint_tag();
279
280 let _ = state.add_propagator(AbsoluteValueArgs {
281 signed,
282 absolute,
283 constraint_tag,
284 });
285 state.propagate_to_fixed_point().expect("no empty domains");
286
287 state.assert_bounds(absolute, 3, 6);
288 }
289
290 #[test]
291 fn strictly_negative_signed_value_can_propagate_lower_bound_on_absolute() {
292 let mut state = State::default();
293
294 let signed = state.new_interval_variable(-5, -3, None);
295 let absolute = state.new_interval_variable(1, 5, None);
296 let constraint_tag = state.new_constraint_tag();
297
298 let _ = state.add_propagator(AbsoluteValueArgs {
299 signed,
300 absolute,
301 constraint_tag,
302 });
303 state.propagate_to_fixed_point().expect("no empty domains");
304
305 state.assert_bounds(absolute, 3, 5);
306 }
307
308 #[test]
309 fn lower_bound_on_absolute_can_propagate_negative_upper_bound_on_signed() {
310 let mut state = State::default();
311
312 let signed = state.new_interval_variable(-5, 0, None);
313 let absolute = state.new_interval_variable(1, 5, None);
314 let constraint_tag = state.new_constraint_tag();
315
316 let _ = state.add_propagator(AbsoluteValueArgs {
317 signed,
318 absolute,
319 constraint_tag,
320 });
321 state.propagate_to_fixed_point().expect("no empty domains");
322
323 state.assert_bounds(signed, -5, -1);
324 }
325
326 #[test]
327 fn lower_bound_on_absolute_can_propagate_positive_lower_bound_on_signed() {
328 let mut state = State::default();
329
330 let signed = state.new_interval_variable(1, 5, None);
331 let absolute = state.new_interval_variable(3, 5, None);
332 let constraint_tag = state.new_constraint_tag();
333
334 let _ = state.add_propagator(AbsoluteValueArgs {
335 signed,
336 absolute,
337 constraint_tag,
338 });
339 state.propagate_to_fixed_point().expect("no empty domains");
340
341 state.assert_bounds(signed, 3, 5);
342 }
343}