Skip to main content

pumpkin_core/propagators/
reified_propagator.rs

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