1use pumpkin_checking::AtomicConstraint;
2use pumpkin_checking::BoxedChecker;
3use pumpkin_checking::CheckerVariable;
4use pumpkin_checking::InferenceChecker;
5
6use crate::engine::PropagationStatusCP;
7use crate::engine::notifications::OpaqueDomainEvent;
8use crate::predicates::Predicate;
9use crate::propagation::DomainEvents;
10use crate::propagation::Domains;
11use crate::propagation::EnqueueDecision;
12use crate::propagation::ExplanationContext;
13use crate::propagation::LazyExplanation;
14use crate::propagation::LocalId;
15use crate::propagation::NotificationContext;
16use crate::propagation::Priority;
17use crate::propagation::PropagationContext;
18use crate::propagation::Propagator;
19use crate::propagation::PropagatorConstructor;
20use crate::propagation::PropagatorConstructorContext;
21use crate::propagation::PropagatorSpec;
22use crate::propagation::ReadDomains;
23use crate::propagation::RuntimeCheckers;
24use crate::pumpkin_assert_simple;
25use crate::state::Conflict;
26use crate::variables::Literal;
27
28#[derive(Clone, Debug)]
30pub struct ReifiedPropagatorArgs<WrappedArgs> {
31 pub propagator: WrappedArgs,
32 pub reification_literal: Literal,
33}
34
35impl<WrappedArgs, WrappedPropagator> PropagatorConstructor for ReifiedPropagatorArgs<WrappedArgs>
36where
37 WrappedArgs: PropagatorConstructor<PropagatorImpl = WrappedPropagator>,
38 WrappedPropagator: Propagator + Clone,
39{
40 type PropagatorImpl = ReifiedPropagator<WrappedPropagator>;
41
42 fn create(
43 self,
44 mut context: PropagatorConstructorContext,
45 ) -> PropagatorSpec<Self::PropagatorImpl> {
46 let ReifiedPropagatorArgs {
47 propagator,
48 reification_literal,
49 } = self;
50
51 let PropagatorSpec {
52 mut registration,
53 propagator,
54 checkers,
55 } = propagator.create(context.reborrow());
56
57 let reification_literal_id = registration
60 .iter()
61 .map(|(_, _, lid)| lid)
62 .max()
63 .expect("cannot reify propagators that do not register all variables immediately")
64 .successor();
65
66 registration.add(
67 &self.reification_literal,
68 DomainEvents::BOUNDS,
69 reification_literal_id,
70 );
71
72 let mut wrapped_checkers = RuntimeCheckers::empty();
73 for (inference_code, checker) in checkers.into_iter() {
74 let _ = wrapped_checkers.add_inference_checker(
75 inference_code.tag(),
76 inference_code.label(),
77 ReifiedChecker {
78 inner: checker,
79 reification_literal,
80 },
81 );
82 }
83
84 let name = format!("Reified({})", propagator.name());
85
86 let propagator = ReifiedPropagator {
87 propagator,
88 reification_literal,
89 reification_literal_id,
90 name,
91 reason_buffer: vec![],
92 };
93
94 PropagatorSpec {
95 registration,
96 checkers: wrapped_checkers,
97 propagator,
98 }
99 }
100}
101
102#[derive(Clone, Debug)]
110pub struct ReifiedPropagator<WrappedPropagator> {
111 propagator: WrappedPropagator,
112 reification_literal: Literal,
113 name: String,
115 reification_literal_id: LocalId,
118
119 reason_buffer: Vec<Predicate>,
121}
122
123impl<WrappedPropagator: Propagator + Clone> Propagator for ReifiedPropagator<WrappedPropagator> {
124 fn notify(
125 &mut self,
126 mut context: NotificationContext,
127 local_id: LocalId,
128 event: OpaqueDomainEvent,
129 ) -> EnqueueDecision {
130 if local_id < self.reification_literal_id {
131 let decision = self.propagator.notify(context.reborrow(), local_id, event);
132 self.filter_enqueue_decision(context, decision)
133 } else {
134 pumpkin_assert_simple!(local_id == self.reification_literal_id);
135 EnqueueDecision::Enqueue
136 }
137 }
138
139 fn notify_backtrack(&mut self, context: Domains, local_id: LocalId, event: OpaqueDomainEvent) {
140 if local_id < self.reification_literal_id {
141 self.propagator.notify_backtrack(context, local_id, event)
142 } else {
143 pumpkin_assert_simple!(local_id == self.reification_literal_id);
144 }
145 }
146
147 fn priority(&self) -> Priority {
148 self.propagator.priority()
149 }
150
151 fn synchronise(&mut self, context: NotificationContext<'_>) {
152 self.propagator.synchronise(context);
153 }
154
155 fn propagate(&mut self, mut context: PropagationContext) -> PropagationStatusCP {
156 self.propagate_reification(&mut context)?;
157
158 if context.evaluate_literal(self.reification_literal) == Some(true) {
159 context.with_reification(self.reification_literal);
160
161 let result = self.propagator.propagate(context);
162
163 self.map_propagation_status(result)?;
164 }
165
166 Ok(())
167 }
168
169 fn name(&self) -> &str {
170 &self.name
171 }
172
173 fn propagate_from_scratch(&self, mut context: PropagationContext) -> PropagationStatusCP {
174 self.propagate_reification(&mut context)?;
175
176 if context.evaluate_literal(self.reification_literal) == Some(true) {
177 context.with_reification(self.reification_literal);
178
179 let result = self.propagator.propagate_from_scratch(context);
180
181 self.map_propagation_status(result)?;
182 }
183
184 Ok(())
185 }
186
187 fn lazy_explanation(&mut self, code: u64, context: ExplanationContext) -> LazyExplanation<'_> {
188 let inner = self.propagator.lazy_explanation(code, context);
189 let inference_code = inner.inference_code;
190
191 self.reason_buffer.clear();
192 self.reason_buffer
193 .push(self.reification_literal.get_true_predicate());
194 self.reason_buffer.extend(inner.predicates);
195
196 LazyExplanation {
197 predicates: self.reason_buffer.as_slice(),
198 inference_code,
199 }
200 }
201}
202
203impl<Prop: Propagator + Clone> ReifiedPropagator<Prop> {
204 fn map_propagation_status(&self, mut status: PropagationStatusCP) -> PropagationStatusCP {
205 if let Err(Conflict::Propagator(ref mut conflict)) = status {
206 conflict
207 .conjunction
208 .push(self.reification_literal.get_true_predicate());
209 }
210 status
211 }
212
213 fn propagate_reification(&self, context: &mut PropagationContext<'_>) -> PropagationStatusCP
214 where
215 Prop: Propagator,
216 {
217 if context.evaluate_literal(self.reification_literal) == Some(true) {
218 return Ok(());
219 }
220
221 if let Some(conflict) = self.propagator.detect_inconsistency(context.domains()) {
222 context.post(
223 self.reification_literal.get_false_predicate(),
224 (conflict.conjunction, &conflict.inference_code),
225 )?;
226 }
227
228 Ok(())
229 }
230
231 fn filter_enqueue_decision(
232 &mut self,
233 mut context: NotificationContext<'_>,
234 decision: EnqueueDecision,
235 ) -> EnqueueDecision {
236 if decision == EnqueueDecision::Skip {
237 return EnqueueDecision::Skip;
239 }
240
241 if context.evaluate_literal(self.reification_literal) == Some(true) {
242 return EnqueueDecision::Enqueue;
245 }
246
247 if context.evaluate_literal(self.reification_literal) != Some(false)
248 && self
249 .propagator
250 .detect_inconsistency(context.domains())
251 .is_some()
252 {
253 return EnqueueDecision::Enqueue;
256 }
257
258 EnqueueDecision::Skip
259 }
260}
261
262#[derive(Debug, Clone)]
263pub struct ReifiedChecker<Atomic: AtomicConstraint, Var> {
264 pub inner: BoxedChecker<Atomic>,
265 pub reification_literal: Var,
266}
267
268impl<Atomic: AtomicConstraint + Clone, Var: CheckerVariable<Atomic>> InferenceChecker<Atomic>
269 for ReifiedChecker<Atomic, Var>
270{
271 fn check(
272 &self,
273 state: pumpkin_checking::VariableState<Atomic>,
274 premises: &[Atomic],
275 consequent: Option<&Atomic>,
276 ) -> bool {
277 if self.reification_literal.induced_domain_contains(&state, 0) {
278 return false;
279 }
280
281 if let Some(consequent) = consequent
282 && self
283 .reification_literal
284 .does_atomic_constrain_self(consequent)
285 {
286 self.inner.check(state, premises, None)
287 } else {
288 self.inner.check(state, premises, consequent)
289 }
290 }
291}
292
293#[allow(deprecated, reason = "Will be refactored")]
294#[cfg(test)]
295mod tests {
296 use super::*;
297 use crate::conjunction;
298 use crate::containers::StorageKey;
299 use crate::engine::PropagatorConflict;
300 use crate::engine::test_solver::TestSolver;
301 use crate::predicate;
302 use crate::predicates::PropositionalConjunction;
303 use crate::proof::ConstraintTag;
304 use crate::proof::InferenceCode;
305 use crate::proof::Unknown;
306 use crate::propagation::EventsToRegister;
307 use crate::variables::DomainId;
308
309 #[test]
310 fn a_detected_inconsistency_is_given_as_reason_for_propagating_reification_literal_to_false() {
311 let mut solver = TestSolver::default();
312
313 let reification_literal = solver.new_literal();
314 let a = solver.new_variable(1, 1);
315 let b = solver.new_variable(2, 2);
316
317 let triggered_conflict = conjunction!([a == 1] & [b == 2]);
318 let t1 = triggered_conflict.clone();
319 let t2 = triggered_conflict.clone();
320
321 let inference_code =
322 solver.accept_inferences_by(ConstraintTag::create_from_index(0), Unknown);
323 let i1 = inference_code.clone();
324 let i2 = inference_code.clone();
325
326 let _ = solver
327 .new_propagator(ReifiedPropagatorArgs {
328 propagator: GenericPropagator::new(
329 vec![a, b],
330 move |_: PropagationContext| {
331 Err(PropagatorConflict {
332 conjunction: t1.clone(),
333 inference_code: i1.clone(),
334 }
335 .into())
336 },
337 move |_: Domains| {
338 Some(PropagatorConflict {
339 conjunction: t2.clone(),
340 inference_code: i2.clone(),
341 })
342 },
343 ),
344 reification_literal,
345 })
346 .expect("no conflict");
347
348 assert!(solver.is_literal_false(reification_literal));
349
350 let reason = solver.get_reason_bool(reification_literal, false);
351 assert_eq!(reason, triggered_conflict);
352 }
353
354 #[test]
355 fn a_true_literal_is_added_to_reason_for_propagation() {
356 let mut solver = TestSolver::default();
357
358 let reification_literal = solver.new_literal();
359 let var = solver.new_variable(1, 5);
360
361 let propagator = solver
362 .new_propagator(ReifiedPropagatorArgs {
363 propagator: GenericPropagator::new(
364 vec![var],
365 move |mut ctx: PropagationContext| {
366 ctx.post(
367 predicate![var >= 3],
368 (
369 conjunction!(),
370 &InferenceCode::unknown_label(ConstraintTag::create_from_index(0)),
371 ),
372 )?;
373 Ok(())
374 },
375 |_: Domains| None,
376 ),
377 reification_literal,
378 })
379 .expect("no conflict");
380
381 solver.assert_bounds(var, 1, 5);
382
383 let _ = solver.set_literal(reification_literal, true);
384 solver.propagate(propagator).expect("no conflict");
385
386 solver.assert_bounds(var, 3, 5);
387 let reason = solver.get_reason_int(predicate![var >= 3]);
388 assert_eq!(
389 reason,
390 PropositionalConjunction::from(reification_literal.get_true_predicate())
391 );
392 }
393
394 #[test]
395 fn a_true_literal_is_added_to_a_conflict_conjunction() {
396 let mut solver = TestSolver::default();
397
398 let reification_literal = solver.new_literal();
399 let _ = solver.set_literal(reification_literal, true);
400
401 let var = solver.new_variable(1, 1);
402 let inference_code =
403 solver.accept_inferences_by(ConstraintTag::create_from_index(0), Unknown);
404
405 let inconsistency = solver
406 .new_propagator(ReifiedPropagatorArgs {
407 propagator: GenericPropagator::new(
408 vec![var],
409 move |_: PropagationContext| {
410 Err(PropagatorConflict {
411 conjunction: conjunction!([var >= 1]),
412 inference_code: inference_code.clone(),
413 }
414 .into())
415 },
416 |_: Domains| None,
417 ),
418 reification_literal,
419 })
420 .expect_err("eagerly triggered the conflict");
421
422 match inconsistency {
423 Conflict::Propagator(conflict_nogood) => {
424 assert_eq!(
425 conflict_nogood.conjunction,
426 PropositionalConjunction::from(vec![
427 reification_literal.get_true_predicate(),
428 predicate![var >= 1]
429 ])
430 )
431 }
432
433 other => panic!("Inconsistency {other:?} is not expected."),
434 }
435 }
436
437 #[test]
438 fn notify_propagator_is_enqueued_if_inconsistency_can_be_detected() {
439 let mut solver = TestSolver::default();
440
441 let reification_literal = solver.new_literal();
442 let var = solver.new_variable(1, 5);
443
444 let inference_code =
445 solver.accept_inferences_by(ConstraintTag::create_from_index(0), Unknown);
446
447 let propagator = solver
448 .new_propagator(ReifiedPropagatorArgs {
449 propagator: GenericPropagator::new(
450 vec![var],
451 |_: PropagationContext| Ok(()),
452 move |context: Domains| {
453 if context.is_fixed(&var) {
454 Some(PropagatorConflict {
455 conjunction: conjunction!([var == 5]),
456 inference_code: inference_code.clone(),
457 })
458 } else {
459 None
460 }
461 },
462 )
463 .with_variables(&[var]),
464 reification_literal,
465 })
466 .expect("No conflict expected");
467
468 let enqueue = solver.increase_lower_bound_and_notify(propagator, 0, var, 5);
469 assert!(matches!(enqueue, EnqueueDecision::Enqueue))
470 }
471
472 #[derive(Clone)]
473 struct GenericPropagator<Propagation, ConsistencyCheck> {
474 propagation: Propagation,
475 consistency_check: ConsistencyCheck,
476 variables_to_register: Vec<DomainId>,
477 }
478
479 impl<Propagation, ConsistencyCheck> PropagatorConstructor
480 for GenericPropagator<Propagation, ConsistencyCheck>
481 where
482 Propagation: Fn(PropagationContext) -> PropagationStatusCP + 'static + Clone,
483 ConsistencyCheck: Fn(Domains) -> Option<PropagatorConflict> + 'static + Clone,
484 {
485 type PropagatorImpl = Self;
486
487 fn create(self, _: PropagatorConstructorContext) -> PropagatorSpec<Self::PropagatorImpl> {
488 let mut registration = EventsToRegister::empty();
489
490 for (index, variable) in self.variables_to_register.iter().enumerate() {
491 registration.add(variable, DomainEvents::ANY_INT, LocalId::from(index as u32));
492 }
493
494 PropagatorSpec {
495 registration,
496 checkers: RuntimeCheckers::empty(),
497 propagator: self,
498 }
499 }
500 }
501
502 impl<Propagation, ConsistencyCheck> Propagator for GenericPropagator<Propagation, ConsistencyCheck>
503 where
504 Propagation: Fn(PropagationContext) -> PropagationStatusCP + 'static + Clone,
505 ConsistencyCheck: Fn(Domains) -> Option<PropagatorConflict> + 'static + Clone,
506 {
507 fn name(&self) -> &str {
508 "Generic Propagator"
509 }
510
511 fn propagate_from_scratch(&self, context: PropagationContext) -> PropagationStatusCP {
512 (self.propagation)(context)
513 }
514
515 fn detect_inconsistency(&self, domains: Domains) -> Option<PropagatorConflict> {
516 (self.consistency_check)(domains)
517 }
518 }
519
520 impl<Propagation, ConsistencyCheck> GenericPropagator<Propagation, ConsistencyCheck>
521 where
522 Propagation: Fn(PropagationContext) -> PropagationStatusCP,
523 ConsistencyCheck: Fn(Domains) -> Option<PropagatorConflict>,
524 {
525 pub(crate) fn new(
526 variables_to_register: Vec<DomainId>,
527 propagation: Propagation,
528 consistency_check: ConsistencyCheck,
529 ) -> Self {
530 GenericPropagator {
531 propagation,
532 consistency_check,
533 variables_to_register,
534 }
535 }
536
537 pub(crate) fn with_variables(mut self, variables: &[DomainId]) -> Self {
538 self.variables_to_register = variables.into();
540 self
541 }
542 }
543}