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