Skip to main content

pumpkin_core/engine/cp/
reason.rs

1use std::fmt::Debug;
2
3use crate::basic_types::PropositionalConjunction;
4use crate::basic_types::Trail;
5#[cfg(doc)]
6use crate::containers::KeyedVec;
7use crate::predicates::Predicate;
8use crate::proof::InferenceCode;
9use crate::propagation::ExplanationContext;
10use crate::propagation::PropagatorId;
11use crate::propagation::store::PropagatorStore;
12use crate::pumpkin_assert_simple;
13
14/// The reason store holds a reason for each change made by a CP propagator on a trail.
15#[derive(Default, Debug, Clone)]
16pub(crate) struct ReasonStore {
17    trail: Trail<(PropagatorId, StoredReason)>,
18}
19
20impl ReasonStore {
21    pub(crate) fn push(&mut self, propagator: PropagatorId, reason: StoredReason) -> ReasonRef {
22        let index = self.trail.len();
23        self.trail.push((propagator, reason));
24        pumpkin_assert_simple!(
25            index < (1 << 30),
26            "ReasonRef in reason store should fit in ContraintReference, \
27             which has 30 bits available at most"
28        );
29        ReasonRef(index as u32)
30    }
31
32    /// Similar to [`KeyedVec::new_slot`].
33    pub(crate) fn new_slot(&mut self) -> Slot<'_> {
34        Slot { store: self }
35    }
36
37    /// Evaluate the reason with the given reference, write the predicates to `destination_buffer`,
38    /// and return the [`InferenceCode`] associated with the reason.
39    ///
40    /// # Panics
41    /// Panics if `reference` does not exist in the store.
42    pub(crate) fn get_or_compute(
43        &self,
44        reference: ReasonRef,
45        context: ExplanationContext<'_>,
46        propagators: &mut PropagatorStore,
47        destination_buffer: &mut impl Extend<Predicate>,
48    ) -> InferenceCode {
49        let reason = self
50            .trail
51            .get(reference.0 as usize)
52            .expect("reason reference should not be stale");
53
54        reason
55            .1
56            .compute(context, reason.0, propagators, destination_buffer)
57    }
58
59    #[allow(unused, reason = "Will be reintroduced with database management")]
60    pub(crate) fn get_lazy_code(&self, reference: ReasonRef) -> Option<&u64> {
61        match self.trail.get(reference.0 as usize) {
62            Some(reason) => match &reason.1 {
63                StoredReason::Eager(_, _) => None,
64                StoredReason::DynamicLazy(code) => Some(code),
65            },
66            None => None,
67        }
68    }
69
70    pub(crate) fn new_checkpoint(&mut self) {
71        self.trail.new_checkpoint()
72    }
73
74    pub(crate) fn synchronise(&mut self, level: usize) {
75        let _ = self.trail.synchronise(level);
76    }
77
78    #[cfg(test)]
79    pub(crate) fn len(&self) -> usize {
80        self.trail.len()
81    }
82
83    /// Get the propagator which generated the given reason.
84    pub(crate) fn get_propagator(&self, reason_ref: ReasonRef) -> PropagatorId {
85        self.trail.get(reason_ref.0 as usize).unwrap().0
86    }
87}
88
89/// A reference to a reason
90#[derive(Default, Debug, Clone, Copy, Hash, Eq, PartialEq)]
91pub(crate) struct ReasonRef(pub(crate) u32);
92
93/// A reason for CP propagator to make a change
94#[derive(Debug, Clone)]
95pub enum Reason {
96    /// An eager reason contains the propositional conjunction with the reason, without the
97    ///   propagated predicate, and the [`InferenceCode`] identifying the explanation algorithm.
98    Eager(PropositionalConjunction, InferenceCode),
99    /// A lazy reason, which is computed on-demand rather than up-front. This is also referred to
100    /// as a 'backward' reason.
101    ///
102    /// A lazy reason contains a payload that propagators can use to identify what type of
103    /// propagation the reason is for. The payload should be enough for the propagator to construct
104    /// an explanation based on its internal state. The [`InferenceCode`] is returned by
105    /// [`crate::propagation::Propagator::lazy_explanation`] on demand.
106    DynamicLazy(u64),
107}
108
109/// A reason for CP propagator to make a change
110#[derive(Debug, Clone)]
111pub(crate) enum StoredReason {
112    /// An eager reason contains the propositional conjunction with the reason, without the
113    ///   propagated predicate, and the [`InferenceCode`] identifying the explanation algorithm.
114    Eager(PropositionalConjunction, InferenceCode),
115    /// A lazy reason, which is computed on-demand rather than up-front. This is also referred to
116    /// as a 'backward' reason.
117    ///
118    /// A lazy reason contains a payload that propagators can use to identify what type of
119    /// propagation the reason is for. The payload should be enough for the propagator to construct
120    /// an explanation based on its internal state. The [`InferenceCode`] is returned by
121    /// [`crate::propagation::Propagator::lazy_explanation`] on demand.
122    DynamicLazy(u64),
123}
124
125impl StoredReason {
126    /// Evaluate the reason, write the predicates to `destination_buffer`, and return the
127    /// [`InferenceCode`] associated with the reason.
128    pub(crate) fn compute(
129        &self,
130        context: ExplanationContext<'_>,
131        propagator_id: PropagatorId,
132        propagators: &mut PropagatorStore,
133        destination_buffer: &mut impl Extend<Predicate>,
134    ) -> InferenceCode {
135        match self {
136            // We do not replace the reason with an eager explanation for dynamic lazy explanations.
137            //
138            // Benchmarking will have to show whether this should change or not.
139            StoredReason::DynamicLazy(code) => {
140                let expl = propagators[propagator_id].lazy_explanation(*code, context);
141                destination_buffer.extend(expl.predicates.iter().copied());
142                expl.inference_code
143            }
144            StoredReason::Eager(result, inference_code) => {
145                destination_buffer.extend(result.iter().copied());
146                inference_code.clone()
147            }
148        }
149    }
150}
151
152impl From<(PropositionalConjunction, &InferenceCode)> for Reason {
153    fn from((conj, code): (PropositionalConjunction, &InferenceCode)) -> Self {
154        Reason::Eager(conj, code.clone())
155    }
156}
157
158impl From<u64> for Reason {
159    fn from(value: u64) -> Self {
160        Reason::DynamicLazy(value)
161    }
162}
163
164impl From<usize> for Reason {
165    fn from(value: usize) -> Self {
166        Reason::DynamicLazy(value as u64)
167    }
168}
169
170/// A reserved slot for a new reason in the [`ReasonStore`].
171#[derive(Debug)]
172pub(crate) struct Slot<'a> {
173    store: &'a mut ReasonStore,
174}
175
176impl Slot<'_> {
177    /// The reference for this slot.
178    pub(crate) fn reason_ref(&self) -> ReasonRef {
179        ReasonRef(self.store.trail.len() as u32)
180    }
181
182    /// Populate the slot with a [`Reason`].
183    pub(crate) fn populate(self, propagator: PropagatorId, reason: StoredReason) -> ReasonRef {
184        self.store.push(propagator, reason)
185    }
186}
187
188#[cfg(test)]
189mod tests {
190    use std::num::NonZero;
191
192    use super::*;
193    use crate::conjunction;
194    use crate::engine::Assignments;
195    use crate::engine::notifications::NotificationEngine;
196    use crate::engine::variables::DomainId;
197    use crate::proof::ConstraintTag;
198
199    fn dummy_inference_code() -> InferenceCode {
200        InferenceCode::unknown_label(ConstraintTag::from_non_zero(NonZero::new(1).unwrap()))
201    }
202
203    #[test]
204    fn computing_an_eager_reason_returns_a_reference_to_the_conjunction() {
205        let integers = Assignments::default();
206        let mut notification_engine = NotificationEngine::default();
207
208        let x = DomainId::new(0);
209        let y = DomainId::new(1);
210
211        let conjunction = conjunction!([x == 1] & [y == 2]);
212        let reason = StoredReason::Eager(conjunction.clone(), dummy_inference_code());
213
214        let mut out_reason = vec![];
215        let _ = reason.compute(
216            ExplanationContext::test_new(&integers, &mut notification_engine),
217            PropagatorId(0),
218            &mut PropagatorStore::default(),
219            &mut out_reason,
220        );
221
222        assert_eq!(conjunction.as_slice(), &out_reason);
223    }
224
225    #[test]
226    fn pushing_a_reason_gives_a_reason_ref_that_can_be_computed() {
227        let mut reason_store = ReasonStore::default();
228        let integers = Assignments::default();
229        let mut notification_engine = NotificationEngine::default();
230
231        let x = DomainId::new(0);
232        let y = DomainId::new(1);
233
234        let conjunction = conjunction!([x == 1] & [y == 2]);
235        let reason_ref = reason_store.push(
236            PropagatorId(0),
237            StoredReason::Eager(conjunction.clone(), dummy_inference_code()),
238        );
239
240        assert_eq!(ReasonRef(0), reason_ref);
241
242        let mut out_reason = vec![];
243        let _ = reason_store.get_or_compute(
244            reason_ref,
245            ExplanationContext::test_new(&integers, &mut notification_engine),
246            &mut PropagatorStore::default(),
247            &mut out_reason,
248        );
249
250        assert_eq!(conjunction.as_slice(), &out_reason);
251    }
252}