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::EventsToRegister;
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::PropagatorSpec;
20use pumpkin_core::propagation::ReadDomains;
21use pumpkin_core::propagation::RuntimeCheckers;
22use pumpkin_core::state::PropagationStatusCP;
23use pumpkin_core::variables::IntegerVariable;
24
25#[derive(Clone, Debug)]
27pub struct DivisionArgs<VA, VB, VC> {
28 pub numerator: VA,
29 pub denominator: VB,
30 pub rhs: VC,
31 pub constraint_tag: ConstraintTag,
32}
33
34const ID_NUMERATOR: LocalId = LocalId::from(0);
35const ID_DENOMINATOR: LocalId = LocalId::from(1);
36const ID_RHS: LocalId = LocalId::from(2);
37
38declare_inference_label!(Division);
39
40impl<VA, VB, VC> PropagatorConstructor for DivisionArgs<VA, VB, VC>
41where
42 VA: IntegerVariable + 'static,
43 VB: IntegerVariable + 'static,
44 VC: IntegerVariable + 'static,
45{
46 type PropagatorImpl = DivisionPropagator<VA, VB, VC>;
47
48 fn create(self, context: PropagatorConstructorContext) -> PropagatorSpec<Self::PropagatorImpl> {
49 let DivisionArgs {
50 numerator,
51 denominator,
52 rhs,
53 constraint_tag,
54 } = self;
55
56 pumpkin_assert_simple!(
57 !context.contains(&denominator, 0),
58 "Denominator cannot contain 0"
59 );
60
61 let registration = EventsToRegister::builder()
62 .add(&numerator, DomainEvents::BOUNDS, ID_NUMERATOR)
63 .add(&denominator, DomainEvents::BOUNDS, ID_DENOMINATOR)
64 .add(&rhs, DomainEvents::BOUNDS, ID_RHS)
65 .build();
66
67 let mut checkers = RuntimeCheckers::builder();
68 let inference_code = checkers.add_inference_checker(
69 constraint_tag,
70 Division,
71 IntegerDivisionChecker {
72 numerator: numerator.clone(),
73 denominator: denominator.clone(),
74 rhs: rhs.clone(),
75 },
76 );
77
78 let propagator = DivisionPropagator {
79 numerator,
80 denominator,
81 rhs,
82 inference_code,
83 };
84
85 PropagatorSpec {
86 registration,
87 checkers: checkers.build(),
88 propagator,
89 }
90 }
91}
92
93#[derive(Clone, Debug)]
100pub struct DivisionPropagator<VA, VB, VC> {
101 numerator: VA,
102 denominator: VB,
103 rhs: VC,
104 inference_code: InferenceCode,
105}
106
107impl<VA: 'static, VB: 'static, VC: 'static> Propagator for DivisionPropagator<VA, VB, VC>
108where
109 VA: IntegerVariable,
110 VB: IntegerVariable,
111 VC: IntegerVariable,
112{
113 fn priority(&self) -> Priority {
114 Priority::High
115 }
116
117 fn name(&self) -> &str {
118 "Division"
119 }
120
121 fn propagate_from_scratch(&self, context: PropagationContext) -> PropagationStatusCP {
122 perform_propagation(
123 context,
124 &self.numerator,
125 &self.denominator,
126 &self.rhs,
127 &self.inference_code,
128 )
129 }
130}
131
132fn perform_propagation<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
133 mut context: PropagationContext,
134 numerator: &VA,
135 denominator: &VB,
136 rhs: &VC,
137 inference_code: &InferenceCode,
138) -> PropagationStatusCP {
139 if context.lower_bound(denominator) < 0 && context.upper_bound(denominator) > 0 {
140 return Ok(());
144 }
145
146 let mut negated_numerator = &numerator.scaled(-1);
147 let mut numerator = &numerator.scaled(1);
148
149 let mut negated_denominator = &denominator.scaled(-1);
150 let mut denominator = &denominator.scaled(1);
151
152 if context.upper_bound(denominator) < 0 {
153 std::mem::swap(&mut numerator, &mut negated_numerator);
156 std::mem::swap(&mut denominator, &mut negated_denominator);
157 }
158
159 let negated_rhs = &rhs.scaled(-1);
160
161 propagate_signs(&mut context, numerator, denominator, rhs, inference_code)?;
164
165 if context.upper_bound(numerator) >= 0 && context.upper_bound(rhs) >= 0 {
168 propagate_upper_bounds(&mut context, numerator, denominator, rhs, inference_code)?;
169 }
170
171 if context.upper_bound(negated_numerator) >= 0 && context.upper_bound(negated_rhs) >= 0 {
174 propagate_upper_bounds(
175 &mut context,
176 negated_numerator,
177 denominator,
178 negated_rhs,
179 inference_code,
180 )?;
181 }
182
183 if context.lower_bound(numerator) >= 0 && context.lower_bound(rhs) >= 0 {
187 propagate_positive_domains(&mut context, numerator, denominator, rhs, inference_code)?;
188 }
189
190 if context.lower_bound(negated_numerator) >= 0 && context.lower_bound(negated_rhs) >= 0 {
194 propagate_positive_domains(
195 &mut context,
196 negated_numerator,
197 denominator,
198 negated_rhs,
199 inference_code,
200 )?;
201 }
202
203 Ok(())
204}
205
206fn propagate_positive_domains<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
217 context: &mut PropagationContext,
218 numerator: &VA,
219 denominator: &VB,
220 rhs: &VC,
221 inference_code: &InferenceCode,
222) -> PropagationStatusCP {
223 let rhs_min = context.lower_bound(rhs);
224 let rhs_max = context.upper_bound(rhs);
225 let numerator_min = context.lower_bound(numerator);
226 let numerator_max = context.upper_bound(numerator);
227 let denominator_min = context.lower_bound(denominator);
228 let denominator_max = context.upper_bound(denominator);
229
230 let new_min_rhs = numerator_min / denominator_max;
232 if rhs_min < new_min_rhs {
233 context.post(
234 predicate![rhs >= new_min_rhs],
235 (
236 conjunction!(
237 [numerator >= numerator_min]
238 & [denominator <= denominator_max]
239 & [denominator >= 1]
240 ),
241 inference_code,
242 ),
243 )?;
244 }
245
246 let new_min_numerator = denominator_min * rhs_min;
251 if numerator_min < new_min_numerator {
252 context.post(
253 predicate![numerator >= new_min_numerator],
254 (
255 conjunction!([denominator >= denominator_min] & [rhs >= rhs_min]),
256 inference_code,
257 ),
258 )?;
259 }
260
261 if rhs_min > 0 {
266 let new_max_denominator = numerator_max / rhs_min;
267 if denominator_max > new_max_denominator {
268 context.post(
269 predicate![denominator <= new_max_denominator],
270 (
271 conjunction!(
272 [numerator <= numerator_max]
273 & [numerator >= 0]
274 & [rhs >= rhs_min]
275 & [denominator >= 1]
276 ),
277 inference_code,
278 ),
279 )?;
280 }
281 }
282
283 let new_min_denominator = {
284 let dividend = numerator_min + 1;
286 let positive_divisor = rhs_max + 1;
287
288 let result = dividend / positive_divisor;
289 let adjust = result * positive_divisor < dividend;
290 result + adjust as i32
291 };
292
293 if denominator_min < new_min_denominator {
294 context.post(
295 predicate![denominator >= new_min_denominator],
296 (
297 conjunction!(
298 [numerator >= numerator_min]
299 & [rhs <= rhs_max]
300 & [rhs >= 0]
301 & [denominator >= 1]
302 ),
303 inference_code,
304 ),
305 )?;
306 }
307
308 Ok(())
309}
310
311fn propagate_upper_bounds<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
318 context: &mut PropagationContext,
319 numerator: &VA,
320 denominator: &VB,
321 rhs: &VC,
322 inference_code: &InferenceCode,
323) -> PropagationStatusCP {
324 let rhs_max = context.upper_bound(rhs);
325 let numerator_max = context.upper_bound(numerator);
326 let denominator_min = context.lower_bound(denominator);
327 let denominator_max = context.upper_bound(denominator);
328
329 let new_max_rhs = numerator_max / denominator_min;
332 if rhs_max > new_max_rhs {
333 context.post(
334 predicate![rhs <= new_max_rhs],
335 (
336 conjunction!([numerator <= numerator_max] & [denominator >= denominator_min]),
337 inference_code,
338 ),
339 )?;
340 }
341
342 let new_max_numerator = (rhs_max + 1) * denominator_max - 1;
348 if numerator_max > new_max_numerator {
349 context.post(
350 predicate![numerator <= new_max_numerator],
351 (
352 conjunction!(
353 [denominator <= denominator_max] & [denominator >= 1] & [rhs <= rhs_max]
354 ),
355 inference_code,
356 ),
357 )?;
358 }
359
360 Ok(())
361}
362
363fn propagate_signs<VA: IntegerVariable, VB: IntegerVariable, VC: IntegerVariable>(
370 context: &mut PropagationContext,
371 numerator: &VA,
372 denominator: &VB,
373 rhs: &VC,
374 inference_code: &InferenceCode,
375) -> PropagationStatusCP {
376 let rhs_min = context.lower_bound(rhs);
377 let rhs_max = context.upper_bound(rhs);
378 let numerator_min = context.lower_bound(numerator);
379 let numerator_max = context.upper_bound(numerator);
380
381 if numerator_min >= 0 && rhs_min < 0 {
384 context.post(
385 predicate![rhs >= 0],
386 (
387 conjunction!([numerator >= 0] & [denominator >= 1]),
388 inference_code,
389 ),
390 )?;
391 }
392
393 if numerator_min <= 0 && rhs_min > 0 {
395 context.post(
396 predicate![numerator >= 1],
397 (
398 conjunction!([rhs >= 1] & [denominator >= 1]),
399 inference_code,
400 ),
401 )?;
402 }
403
404 if numerator_max <= 0 && rhs_max > 0 {
406 context.post(
407 predicate![rhs <= 0],
408 (
409 conjunction!([numerator <= 0] & [denominator >= 1]),
410 inference_code,
411 ),
412 )?;
413 }
414
415 if numerator_max >= 0 && rhs_max < 0 {
417 context.post(
418 predicate![numerator <= -1],
419 (
420 conjunction!([rhs <= -1] & [denominator >= 1]),
421 inference_code,
422 ),
423 )?;
424 }
425
426 Ok(())
427}
428
429#[derive(Clone, Debug)]
430pub struct IntegerDivisionChecker<VA, VB, VC> {
431 pub numerator: VA,
432 pub denominator: VB,
433 pub rhs: VC,
434}
435
436impl<VA, VB, VC, Atomic> InferenceChecker<Atomic> for IntegerDivisionChecker<VA, VB, VC>
437where
438 Atomic: AtomicConstraint,
439 VA: CheckerVariable<Atomic>,
440 VB: CheckerVariable<Atomic>,
441 VC: CheckerVariable<Atomic>,
442{
443 fn check(
444 &self,
445 state: pumpkin_checking::VariableState<Atomic>,
446 _premises: &[Atomic],
447 _consequent: Option<&Atomic>,
448 ) -> bool {
449 let x1 = self.numerator.induced_lower_bound(&state);
455 let x2 = self.numerator.induced_upper_bound(&state);
456 let y1 = self.denominator.induced_lower_bound(&state);
457 let y2 = self.denominator.induced_upper_bound(&state);
458
459 assert!(
460 y2 < 0 || y1 > 0,
461 "Currentl, the checker does not contain inferences where the denominator spans 0"
462 );
463
464 let computed_c_lower: IntExt = *[
465 x1.div_ceil(y1),
466 x1.div_ceil(y2),
467 x2.div_ceil(y1),
468 x2.div_ceil(y2),
469 ]
470 .iter()
471 .flatten()
472 .min()
473 .expect("Expected at least one element to be defined");
474
475 let computed_c_upper: IntExt = *[
476 x1.div_floor(y1),
477 x1.div_floor(y2),
478 x2.div_floor(y1),
479 x2.div_floor(y2),
480 ]
481 .iter()
482 .flatten()
483 .max()
484 .expect("Expected at least one element to be defined");
485
486 let c_lower = self.rhs.induced_lower_bound(&state);
487 let c_upper = self.rhs.induced_upper_bound(&state);
488
489 computed_c_upper < c_lower || computed_c_lower > c_upper
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use pumpkin_core::state::State;
496
497 use super::*;
498
499 #[test]
500 fn detects_conflicts() {
501 let mut state = State::default();
502 let numerator = state.new_interval_variable(1, 1, None);
503 let denominator = state.new_interval_variable(2, 2, None);
504 let rhs = state.new_interval_variable(2, 2, None);
505 let constraint_tag = state.new_constraint_tag();
506
507 let _ = state.add_propagator(DivisionArgs {
508 numerator,
509 denominator,
510 rhs,
511 constraint_tag,
512 });
513
514 let _ = state.propagate_to_fixed_point().unwrap_err();
515 }
516
517 #[test]
518 fn checker_does_not_report_false_conflict_for_tight_but_valid_quotient() {
519 use pumpkin_checking::Comparison;
520 use pumpkin_checking::TestAtomic;
521 use pumpkin_checking::VariableState;
522
523 let premises = [
524 TestAtomic {
525 name: "numerator",
526 comparison: Comparison::Equal,
527 value: 7,
528 },
529 TestAtomic {
530 name: "denominator",
531 comparison: Comparison::GreaterEqual,
532 value: 2,
533 },
534 TestAtomic {
535 name: "denominator",
536 comparison: Comparison::LessEqual,
537 value: 3,
538 },
539 TestAtomic {
540 name: "rhs",
541 comparison: Comparison::Equal,
542 value: 3,
543 },
544 ];
545
546 let state = VariableState::prepare_for_conflict_check(premises, None)
547 .expect("no conflicting atomics");
548
549 let checker = IntegerDivisionChecker {
550 numerator: "numerator",
551 denominator: "denominator",
552 rhs: "rhs",
553 };
554
555 assert!(!checker.check(state, &premises, None));
558 }
559}