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