pumpkin_core/propagation/propagator.rs
1use downcast_rs::Downcast;
2use downcast_rs::impl_downcast;
3use dyn_clone::DynClone;
4use dyn_clone::clone_trait_object;
5
6use super::Domains;
7use super::ExplanationContext;
8use super::PropagationContext;
9use super::contexts::NotificationContext;
10use crate::basic_types::PredicateId;
11#[cfg(doc)]
12use crate::create_statistics_struct;
13#[cfg(doc)]
14use crate::engine::ConstraintSatisfactionSolver;
15use crate::engine::PropagationStatusCP;
16use crate::engine::PropagatorConflict;
17use crate::engine::notifications::OpaqueDomainEvent;
18use crate::predicates::Predicate;
19use crate::proof::InferenceCode;
20#[cfg(doc)]
21use crate::propagation::DomainEvent;
22#[cfg(doc)]
23use crate::propagation::PropagatorConstructor;
24#[cfg(doc)]
25use crate::propagation::PropagatorConstructorContext;
26#[cfg(doc)]
27use crate::propagation::ReadDomains;
28use crate::propagation::local_id::LocalId;
29#[cfg(doc)]
30use crate::pumpkin_asserts::PUMPKIN_ASSERT_ADVANCED;
31#[cfg(doc)]
32use crate::pumpkin_asserts::PUMPKIN_ASSERT_EXTREME;
33#[cfg(doc)]
34use crate::state::Conflict;
35use crate::statistics::statistic_logger::StatisticLogger;
36
37/// The result of [`Propagator::lazy_explanation`]: a slice of predicates that form the reason for
38/// a propagation, together with the [`InferenceCode`] that identifies the explanation algorithm.
39#[derive(Clone, Debug)]
40pub struct LazyExplanation<'a> {
41 /// The predicates that explain the propagation.
42 pub predicates: &'a [Predicate],
43 /// The inference code identifying the explanation algorithm.
44 pub inference_code: InferenceCode,
45}
46
47// We need to use this to cast from `Box<dyn Propagator>` to `NogoodPropagator`; rust inherently
48// does not allow downcasting from the trait definition to its concrete type.
49impl_downcast!(Propagator);
50
51// To allow the State object to be cloneable, we need to allow `Box<dyn Propagator>` to be cloned.
52clone_trait_object!(Propagator);
53
54/// A propagator removes values from domains which will never be in any solution, or raises
55/// explicit conflicts.
56///
57/// The only required functions are [`Propagator::name`],
58/// and [`Propagator::propagate_from_scratch`]; all other
59/// functions have default implementations. For initial development, the required functions are
60/// enough, but a more mature implementation considers all functions in most cases.
61///
62/// See the [`crate::propagation`] documentation for more details.
63pub trait Propagator: Downcast + DynClone {
64 /// Return the name of the propagator.
65 ///
66 /// This is a convenience method that is used for printing.
67 fn name(&self) -> &str;
68
69 /// Performs propagation from scratch (i.e., without relying on updating
70 /// internal data structures, as opposed to [`Propagator::propagate`]).
71 ///
72 /// The main aims of this method are to remove values from the domains of variables (using
73 /// [`PropagationContext::post`]) which cannot be part of any solution given the current
74 /// domains and to detect conflicts.
75 ///
76 /// In case no conflict has been detected this function should
77 /// return [`Result::Ok`], otherwise it should return a [`Result::Err`] with a [`Conflict`]
78 /// which contains the reason for the failure; either because a propagation caused an
79 /// an empty domain ([`Conflict::EmptyDomain`] as a result of [`PropagationContext::post`]) or
80 /// because the logic of the propagator found the current state to be inconsistent
81 /// ([`Conflict::Propagator`] ).
82 ///
83 /// It is usually best to implement this propagation method in the simplest
84 /// but correct way. When this crate is compiled with the `debug-checks` feature, this method
85 /// will be called to double check the reasons for failures and propagations that have been
86 /// reported by this propagator.
87 ///
88 /// Propagators are not required to propagate until a fixed point. It will be called again by
89 /// the solver until no further propagations happen.
90 fn propagate_from_scratch(&self, context: PropagationContext) -> PropagationStatusCP;
91
92 /// Performs propagation with state (i.e., with being able to mutate internal data structures,
93 /// as opposed to [`Propagator::propagate_from_scratch`]).
94 ///
95 /// The main aims of this method are to remove values from the domains of variables (using
96 /// [`PropagationContext::post`]) which cannot be part of any solution given the current
97 /// domains and to detect conflicts.
98 ///
99 /// In case no conflict has been detected this function should
100 /// return [`Result::Ok`], otherwise it should return a [`Result::Err`] with a [`Conflict`]
101 /// which contains the reason for the failure; either because a propagation caused an
102 /// an empty domain ([`Conflict::EmptyDomain`] as a result of [`PropagationContext::post`]) or
103 /// because the logic of the propagator found the current state to be inconsistent
104 /// ([`Conflict::Propagator`] ).
105 ///
106 /// Propagators are not required to propagate until a fixed point. It will be called
107 /// again by the solver until no further propagations happen.
108 ///
109 /// By default, this function calls [`Propagator::propagate_from_scratch`].
110 fn propagate(&mut self, context: PropagationContext) -> PropagationStatusCP {
111 self.propagate_from_scratch(context)
112 }
113
114 /// Returns whether the propagator should be enqueued for propagation when a [`DomainEvent`]
115 /// happens to one of the variables the propagator is subscribed to (as registered during
116 /// creation with [`PropagatorConstructor`] using [`PropagatorConstructorContext::register`]).
117 ///
118 /// This can be used to incrementally maintain data structures or perform propagations, and
119 /// should only be used for computationally cheap logic. Expensive computation should be
120 /// performed in the [`Propagator::propagate`] method.
121 ///
122 /// By default the propagator is always enqueued for every event it is subscribed to. Not all
123 /// propagators will benefit from implementing this, so it is not required to do so.
124 fn notify(
125 &mut self,
126 _context: NotificationContext,
127 _local_id: LocalId,
128 _event: OpaqueDomainEvent,
129 ) -> EnqueueDecision {
130 EnqueueDecision::Enqueue
131 }
132
133 /// This function is called when the effect of a [`DomainEvent`] is undone during backtracking
134 /// of one of the variables the propagator is subscribed to (as registered during creation with
135 /// [`PropagatorConstructor`] using [`PropagatorConstructorContext::register_backtrack`]).
136 ///
137 /// This can be used to incrementally maintain data structures or perform propagations, and
138 /// should only be used for computationally cheap logic. Expensive computation should be
139 /// performed in the [`Propagator::propagate`] method.
140 ///
141 /// *Note*: This method is only called for [`DomainEvent`]s for which [`Propagator::notify`]
142 /// was called. This means that if the propagator itself made a change, but was not notified of
143 /// it (e.g., due to a conflict being detected), then this method will also not be called for
144 /// that [`DomainEvent`].
145 ///
146 /// By default the propagator does nothing when this method is called. Not all propagators will
147 /// benefit from implementing this, so it is not required to do so.
148 fn notify_backtrack(
149 &mut self,
150 _context: Domains,
151 _local_id: LocalId,
152 _event: OpaqueDomainEvent,
153 ) {
154 }
155
156 /// Returns whether the propagator should be enqueued for propagation when a [`Predicate`] (with
157 /// corresponding [`PredicateId`]) which the propagator is subscribed to (as registered either
158 /// during using [`PropagationContext::register_predicate`] or during creation with
159 /// [`PropagatorConstructor`] using [`PropagatorConstructorContext::register_predicate`]).
160 ///
161 /// By default, the propagator will be enqueued.
162 fn notify_predicate_id_satisfied(
163 &mut self,
164 _context: NotificationContext,
165 _predicate_id: PredicateId,
166 ) -> EnqueueDecision {
167 EnqueueDecision::Enqueue
168 }
169
170 /// Called after backtracking, allowing the propagator to
171 /// update its internal data structures given the new variable domains.
172 ///
173 /// By default this function does nothing.
174 fn synchronise(&mut self, _context: NotificationContext<'_>) {}
175
176 /// Returns the [`Priority`] of the propagator, used for determining the order in which
177 /// propagators are called.
178 ///
179 /// See [`Priority`] documentation for more explanation.
180 ///
181 /// By default the priority is set to [`Priority::VeryLow`]. It is expected that
182 /// propagator implementations would set this value to some appropriate value.
183 fn priority(&self) -> Priority {
184 Priority::VeryLow
185 }
186
187 /// A function which returns [`Some`] with a [`PropagatorConflict`] when this propagator can
188 /// detect an inconsistency (and [`None`] otherwise).
189 ///
190 /// By implementing this function, if the propagator is reified, it can propagate the
191 /// reification literal based on the detected inconsistency. Yet, an implementation is not
192 /// needed for correctness, as [`Propagator::propagate`] should still check for
193 /// inconsistency as well.
194 fn detect_inconsistency(&self, _domains: Domains) -> Option<PropagatorConflict> {
195 None
196 }
197
198 /// Hook which is called when a propagated [`Predicate`] should be explained using a lazy
199 /// reason.
200 ///
201 /// The code which was attached to the propagation is given, as
202 /// well as a context object which defines what can be inspected from the solver to build the
203 /// explanation.
204 ///
205 /// *Note:* The context which is provided contains the _current_ state (i.e. the state when the
206 /// explanation is generated); the bounds at the time of the propagation can be retrieved using
207 /// methods such as [`ReadDomains::lower_bound_at_trail_position`] in combination
208 /// with [`ExplanationContext::get_trail_position`].
209 fn lazy_explanation(
210 &mut self,
211 _code: u64,
212 _context: ExplanationContext,
213 ) -> LazyExplanation<'_> {
214 panic!(
215 "{}",
216 format!(
217 "Propagator {} does not support lazy explanations.",
218 self.name()
219 )
220 );
221 }
222
223 /// Logs statistics of the propagator using the provided [`StatisticLogger`].
224 ///
225 /// It is recommended to create a struct through the [`create_statistics_struct!`] macro!
226 fn log_statistics(&self, _statistic_logger: StatisticLogger) {}
227}
228
229/// Indicator of what to do when a propagator is notified.
230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
231pub enum EnqueueDecision {
232 /// The propagator should be enqueued.
233 Enqueue,
234 /// The propagator should not be enqueued.
235 Skip,
236}
237
238/// The priority of a propagator, used for determining the order in which propagators will be
239/// called.
240///
241/// Propagators with high priority are propagated before propagators with low(er) priority. If two
242/// propagators have the same priority, then the order in which they are propagated is unspecified.
243///
244/// Typically, propagators with low computational complexity should be assigned a high
245/// priority (i.e., should be propagated before computationally expensive propagators).
246#[derive(Default, Debug, Clone, Copy, Hash, PartialEq, Eq)]
247#[repr(u8)]
248pub enum Priority {
249 High = 0,
250 Medium = 1,
251 Low = 2,
252 #[default]
253 VeryLow = 3,
254}
255
256impl PartialOrd for Priority {
257 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
258 ((*self) as u8).partial_cmp(&((*other) as u8))
259 }
260}