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