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::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/// A [`PropagatorConstructor`] for the reified propagator.
29#[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        // The local ID for the reification literal will be one larger than the largest ID
58        // registered by the wrapped propagator.
59        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/// Propagator for the constraint `r -> p`, where `r` is a Boolean literal and `p` is an arbitrary
103/// propagator.
104///
105/// When a propagator is reified, it will only propagate whenever `r` is set to true. However, if
106/// the propagator implements [`Propagator::detect_inconsistency`], the result of that method may
107/// be used to propagate `r` to false. If that method is not implemented, `r` will never be
108/// propagated to false.
109#[derive(Clone, Debug)]
110pub struct ReifiedPropagator<WrappedPropagator> {
111    propagator: WrappedPropagator,
112    reification_literal: Literal,
113    /// The formatted name of the propagator.
114    name: String,
115    /// The `LocalId` of the reification literal. Is guaranteed to be a larger ID than any of the
116    /// registered ids of the wrapped propagator.
117    reification_literal_id: LocalId,
118
119    /// Holds the lazy explanations.
120    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            // If the original propagator skips then we always skip
238            return EnqueueDecision::Skip;
239        }
240
241        if context.evaluate_literal(self.reification_literal) == Some(true) {
242            // If the propagator would have enqueued and the literal is true then the reified
243            // propagator is also enqueued
244            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            // Or the literal is not false already and there the propagator has found an
254            // inconsistency (i.e. we should and can propagate the reification variable)
255            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            // Necessary for ensuring that the local IDs are correct when notifying
539            self.variables_to_register = variables.into();
540            self
541        }
542    }
543}