pumpkin_core/engine/cp/
reason.rs1use 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#[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 pub(crate) fn new_slot(&mut self) -> Slot<'_> {
34 Slot { store: self }
35 }
36
37 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 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#[derive(Default, Debug, Clone, Copy, Hash, Eq, PartialEq)]
91pub(crate) struct ReasonRef(pub(crate) u32);
92
93#[derive(Debug, Clone)]
95pub enum Reason {
96 Eager(PropositionalConjunction, InferenceCode),
99 DynamicLazy(u64),
107}
108
109#[derive(Debug, Clone)]
111pub(crate) enum StoredReason {
112 Eager(PropositionalConjunction, InferenceCode),
115 DynamicLazy(u64),
123}
124
125impl StoredReason {
126 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 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#[derive(Debug)]
172pub(crate) struct Slot<'a> {
173 store: &'a mut ReasonStore,
174}
175
176impl Slot<'_> {
177 pub(crate) fn reason_ref(&self) -> ReasonRef {
179 ReasonRef(self.store.trail.len() as u32)
180 }
181
182 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}