1use pumpkin_checking::AtomicConstraint;
2use pumpkin_checking::CheckerVariable;
3use pumpkin_checking::InferenceChecker;
4use pumpkin_checking::IntExt;
5use pumpkin_core::asserts::pumpkin_assert_simple;
6use pumpkin_core::conjunction;
7use pumpkin_core::declare_inference_label;
8use pumpkin_core::predicate;
9use pumpkin_core::proof::ConstraintTag;
10use pumpkin_core::proof::InferenceCode;
11use pumpkin_core::propagation::DomainEvents;
12use pumpkin_core::propagation::InferenceCheckers;
13use pumpkin_core::propagation::LocalId;
14use pumpkin_core::propagation::Priority;
15use pumpkin_core::propagation::PropagationContext;
16use pumpkin_core::propagation::Propagator;
17use pumpkin_core::propagation::PropagatorConstructor;
18use pumpkin_core::propagation::PropagatorConstructorContext;
19use pumpkin_core::propagation::ReadDomains;
20use pumpkin_core::state::PropagationStatusCP;
21use pumpkin_core::variables::IntegerVariable;
22
23#[derive(Clone, Debug)]
25pub struct DivisionArgs<VA, VB, VC> {
26 pub numerator: VA,
27 pub denominator: VB,
28 pub rhs: VC,
29 pub constraint_tag: ConstraintTag,
30}
31
32const ID_NUMERATOR: LocalId = LocalId::from(0);
33const ID_DENOMINATOR: LocalId = LocalId::from(1);
34const ID_RHS: LocalId = LocalId::from(2);
35
36declare_inference_label!(Division);
37
38impl<VA, VB, VC> PropagatorConstructor for DivisionArgs<VA, VB, VC>
39where
40 VA: IntegerVariable + 'static,
41 VB: IntegerVariable + 'static,
42 VC: IntegerVariable + 'static,
43{
44 type PropagatorImpl = DivisionPropagator<VA, VB, VC>;
45
46 fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl {
47 let DivisionArgs {
48 numerator,
49 denominator,
50 rhs,
51 constraint_tag,
52 } = self;
53
54 pumpkin_assert_simple!(
55 !context.contains(&denominator, 0),
56 "Denominator cannot contain 0"
57 );
58
59 context.register(numerator.clone(), DomainEvents::BOUNDS, ID_NUMERATOR);
60 context.register(denominator.clone(), DomainEvents::BOUNDS, ID_DENOMINATOR);
61 context.register(rhs.clone(), DomainEvents::BOUNDS, ID_RHS);
62
63 let inference_code = InferenceCode::new(constraint_tag, Division);
64
65 DivisionPropagator {
66 numerator,
67 denominator,
68 rhs,
69 inference_code,
70 }
71 }
72
73 fn add_inference_checkers(&self, mut checkers: InferenceCheckers<'_>) {
74 checkers.add_inference_checker(
75 InferenceCode::new(self.constraint_tag, Division),
76 Box::new(IntegerDivisionChecker {
77 numerator: self.numerator.clone(),
78 denominator: self.denominator.clone(),
79 rhs: self.rhs.clone(),
80 }),
81 );
82 }
83}
84
85#[derive(Clone, Debug)]
92pub struct DivisionPropagator<VA, VB, VC> {
93 numerator: VA,
94 denominator: VB,
95 rhs: VC,
96 inference_code: InferenceCode,
97}
98
99impl<VA: 'static, VB: 'static, VC: 'static> Propagator for DivisionPropagator<VA, VB, VC>
100where
101 VA: IntegerVariable,
102 VB: IntegerVariable,
103 VC: IntegerVariable,
104{
105 fn priority(&self) -> Priority {
106 Priority::High
107 }
108
109 fn name(&self) -> &str {
110 "Division"
111 }
112
113 fn propagate_from_scratch(&self, context: PropagationContext) -> PropagationStatusCP {
114 perform_propagation(
115 context,
116 &self.numerator,
117 &self.denominator,
118 &self.rhs,
119 &self.inference_code,
120 )
121 }
122}
123
124fn perform_propagation<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
125 mut context: PropagationContext,
126 numerator: &VA,
127 denominator: &VB,
128 rhs: &VC,
129 inference_code: &InferenceCode,
130) -> PropagationStatusCP {
131 if context.lower_bound(denominator) < 0 && context.upper_bound(denominator) > 0 {
132 return Ok(());
136 }
137
138 let mut negated_numerator = &numerator.scaled(-1);
139 let mut numerator = &numerator.scaled(1);
140
141 let mut negated_denominator = &denominator.scaled(-1);
142 let mut denominator = &denominator.scaled(1);
143
144 if context.upper_bound(denominator) < 0 {
145 std::mem::swap(&mut numerator, &mut negated_numerator);
148 std::mem::swap(&mut denominator, &mut negated_denominator);
149 }
150
151 let negated_rhs = &rhs.scaled(-1);
152
153 propagate_signs(&mut context, numerator, denominator, rhs, inference_code)?;
156
157 if context.upper_bound(numerator) >= 0 && context.upper_bound(rhs) >= 0 {
160 propagate_upper_bounds(&mut context, numerator, denominator, rhs, inference_code)?;
161 }
162
163 if context.upper_bound(negated_numerator) >= 0 && context.upper_bound(negated_rhs) >= 0 {
166 propagate_upper_bounds(
167 &mut context,
168 negated_numerator,
169 denominator,
170 negated_rhs,
171 inference_code,
172 )?;
173 }
174
175 if context.lower_bound(numerator) >= 0 && context.lower_bound(rhs) >= 0 {
179 propagate_positive_domains(&mut context, numerator, denominator, rhs, inference_code)?;
180 }
181
182 if context.lower_bound(negated_numerator) >= 0 && context.lower_bound(negated_rhs) >= 0 {
186 propagate_positive_domains(
187 &mut context,
188 negated_numerator,
189 denominator,
190 negated_rhs,
191 inference_code,
192 )?;
193 }
194
195 Ok(())
196}
197
198fn propagate_positive_domains<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
209 context: &mut PropagationContext,
210 numerator: &VA,
211 denominator: &VB,
212 rhs: &VC,
213 inference_code: &InferenceCode,
214) -> PropagationStatusCP {
215 let rhs_min = context.lower_bound(rhs);
216 let rhs_max = context.upper_bound(rhs);
217 let numerator_min = context.lower_bound(numerator);
218 let numerator_max = context.upper_bound(numerator);
219 let denominator_min = context.lower_bound(denominator);
220 let denominator_max = context.upper_bound(denominator);
221
222 let new_min_rhs = numerator_min / denominator_max;
224 if rhs_min < new_min_rhs {
225 context.post(
226 predicate![rhs >= new_min_rhs],
227 (
228 conjunction!(
229 [numerator >= numerator_min]
230 & [denominator <= denominator_max]
231 & [denominator >= 1]
232 ),
233 inference_code,
234 ),
235 )?;
236 }
237
238 let new_min_numerator = denominator_min * rhs_min;
243 if numerator_min < new_min_numerator {
244 context.post(
245 predicate![numerator >= new_min_numerator],
246 (
247 conjunction!([denominator >= denominator_min] & [rhs >= rhs_min]),
248 inference_code,
249 ),
250 )?;
251 }
252
253 if rhs_min > 0 {
258 let new_max_denominator = numerator_max / rhs_min;
259 if denominator_max > new_max_denominator {
260 context.post(
261 predicate![denominator <= new_max_denominator],
262 (
263 conjunction!(
264 [numerator <= numerator_max]
265 & [numerator >= 0]
266 & [rhs >= rhs_min]
267 & [denominator >= 1]
268 ),
269 inference_code,
270 ),
271 )?;
272 }
273 }
274
275 let new_min_denominator = {
276 let dividend = numerator_min + 1;
278 let positive_divisor = rhs_max + 1;
279
280 let result = dividend / positive_divisor;
281 let adjust = result * positive_divisor < dividend;
282 result + adjust as i32
283 };
284
285 if denominator_min < new_min_denominator {
286 context.post(
287 predicate![denominator >= new_min_denominator],
288 (
289 conjunction!(
290 [numerator >= numerator_min]
291 & [rhs <= rhs_max]
292 & [rhs >= 0]
293 & [denominator >= 1]
294 ),
295 inference_code,
296 ),
297 )?;
298 }
299
300 Ok(())
301}
302
303fn propagate_upper_bounds<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
310 context: &mut PropagationContext,
311 numerator: &VA,
312 denominator: &VB,
313 rhs: &VC,
314 inference_code: &InferenceCode,
315) -> PropagationStatusCP {
316 let rhs_max = context.upper_bound(rhs);
317 let numerator_max = context.upper_bound(numerator);
318 let denominator_min = context.lower_bound(denominator);
319 let denominator_max = context.upper_bound(denominator);
320
321 let new_max_rhs = numerator_max / denominator_min;
324 if rhs_max > new_max_rhs {
325 context.post(
326 predicate![rhs <= new_max_rhs],
327 (
328 conjunction!([numerator <= numerator_max] & [denominator >= denominator_min]),
329 inference_code,
330 ),
331 )?;
332 }
333
334 let new_max_numerator = (rhs_max + 1) * denominator_max - 1;
340 if numerator_max > new_max_numerator {
341 context.post(
342 predicate![numerator <= new_max_numerator],
343 (
344 conjunction!(
345 [denominator <= denominator_max] & [denominator >= 1] & [rhs <= rhs_max]
346 ),
347 inference_code,
348 ),
349 )?;
350 }
351
352 Ok(())
353}
354
355fn propagate_signs<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
362 context: &mut PropagationContext,
363 numerator: &VA,
364 denominator: &VB,
365 rhs: &VC,
366 inference_code: &InferenceCode,
367) -> PropagationStatusCP {
368 let rhs_min = context.lower_bound(rhs);
369 let rhs_max = context.upper_bound(rhs);
370 let numerator_min = context.lower_bound(numerator);
371 let numerator_max = context.upper_bound(numerator);
372
373 if numerator_min >= 0 && rhs_min < 0 {
376 context.post(
377 predicate![rhs >= 0],
378 (
379 conjunction!([numerator >= 0] & [denominator >= 1]),
380 inference_code,
381 ),
382 )?;
383 }
384
385 if numerator_min <= 0 && rhs_min > 0 {
387 context.post(
388 predicate![numerator >= 1],
389 (
390 conjunction!([rhs >= 1] & [denominator >= 1]),
391 inference_code,
392 ),
393 )?;
394 }
395
396 if numerator_max <= 0 && rhs_max > 0 {
398 context.post(
399 predicate![rhs <= 0],
400 (
401 conjunction!([numerator <= 0] & [denominator >= 1]),
402 inference_code,
403 ),
404 )?;
405 }
406
407 if numerator_max >= 0 && rhs_max < 0 {
409 context.post(
410 predicate![numerator <= -1],
411 (
412 conjunction!([rhs <= -1] & [denominator >= 1]),
413 inference_code,
414 ),
415 )?;
416 }
417
418 Ok(())
419}
420
421#[derive(Clone, Debug)]
422pub struct IntegerDivisionChecker<VA, VB, VC> {
423 pub numerator: VA,
424 pub denominator: VB,
425 pub rhs: VC,
426}
427
428impl<VA, VB, VC, Atomic> InferenceChecker<Atomic> for IntegerDivisionChecker<VA, VB, VC>
429where
430 Atomic: AtomicConstraint,
431 VA: CheckerVariable<Atomic>,
432 VB: CheckerVariable<Atomic>,
433 VC: CheckerVariable<Atomic>,
434{
435 fn check(
436 &self,
437 state: pumpkin_checking::VariableState<Atomic>,
438 _premises: &[Atomic],
439 _consequent: Option<&Atomic>,
440 ) -> bool {
441 let x1 = self.numerator.induced_lower_bound(&state);
447 let x2 = self.numerator.induced_upper_bound(&state);
448 let y1 = self.denominator.induced_lower_bound(&state);
449 let y2 = self.denominator.induced_upper_bound(&state);
450
451 assert!(
452 y2 < 0 || y1 > 0,
453 "Currentl, the checker does not contain inferences where the denominator spans 0"
454 );
455
456 let computed_c_lower: IntExt = *[
457 x1.div_ceil(y1),
458 x1.div_ceil(y2),
459 x2.div_ceil(y1),
460 x2.div_ceil(y2),
461 ]
462 .iter()
463 .flatten()
464 .min()
465 .expect("Expected at least one element to be defined");
466
467 let computed_c_upper: IntExt = *[
468 x1.div_floor(y1),
469 x1.div_floor(y2),
470 x2.div_floor(y1),
471 x2.div_floor(y2),
472 ]
473 .iter()
474 .flatten()
475 .min()
476 .expect("Expected at least one element to be defined");
477
478 let c_lower = self.rhs.induced_lower_bound(&state);
479 let c_upper = self.rhs.induced_upper_bound(&state);
480
481 computed_c_upper < c_lower || computed_c_lower > c_upper
482 }
483}
484
485#[cfg(test)]
486mod tests {
487 use pumpkin_core::state::State;
488
489 use super::*;
490
491 #[test]
492 fn detects_conflicts() {
493 let mut state = State::default();
494 let numerator = state.new_interval_variable(1, 1, None);
495 let denominator = state.new_interval_variable(2, 2, None);
496 let rhs = state.new_interval_variable(2, 2, None);
497 let constraint_tag = state.new_constraint_tag();
498
499 let _ = state.add_propagator(DivisionArgs {
500 numerator,
501 denominator,
502 rhs,
503 constraint_tag,
504 });
505
506 let _ = state.propagate_to_fixed_point().unwrap_err();
507 }
508}