Skip to main content

pumpkin_core/engine/cp/
test_solver.rs

1//! This module exposes helpers that aid testing of CP propagators. The [`TestSolver`] allows
2//! setting up specific scenarios under which to test the various operations of a propagator.
3use std::fmt::Debug;
4
5use pumpkin_checking::InferenceChecker;
6
7use super::PropagatorQueue;
8use crate::containers::KeyGenerator;
9use crate::engine::EmptyDomain;
10use crate::engine::State;
11use crate::engine::predicates::predicate::Predicate;
12use crate::engine::variables::DomainId;
13use crate::engine::variables::IntegerVariable;
14use crate::engine::variables::Literal;
15use crate::options::LearningOptions;
16use crate::predicate;
17use crate::predicates::PropositionalConjunction;
18use crate::proof::ConstraintTag;
19use crate::proof::InferenceCode;
20use crate::propagation::EnqueueDecision;
21use crate::propagation::ExplanationContext;
22use crate::propagation::NotificationContext;
23use crate::propagation::PropagationContext;
24use crate::propagation::PropagatorConstructor;
25use crate::propagation::PropagatorId;
26use crate::propagators::nogoods::NogoodPropagator;
27use crate::propagators::nogoods::NogoodPropagatorConstructor;
28use crate::state::Conflict;
29use crate::state::EmptyDomainConflict;
30use crate::state::PropagatorHandle;
31
32/// A container for CP variables, which can be used to test propagators.
33#[derive(Debug)]
34pub struct TestSolver {
35    pub state: State,
36    constraint_tags: KeyGenerator<ConstraintTag>,
37    pub nogood_handle: PropagatorHandle<NogoodPropagator>,
38}
39
40impl Default for TestSolver {
41    fn default() -> Self {
42        let mut state = State::default();
43        let handle = state.add_propagator(NogoodPropagatorConstructor::new(
44            0,
45            LearningOptions::default(),
46        ));
47        let mut solver = Self {
48            state,
49            constraint_tags: Default::default(),
50            nogood_handle: handle,
51        };
52        // We allocate space for the zero-th dummy variable at the root level of the assignments.
53        solver.state.notification_engine.grow();
54        solver
55    }
56}
57
58#[deprecated = "Will be replaced by the state API"]
59impl TestSolver {
60    pub fn accept_inferences_by(&mut self, inference_code: InferenceCode) {
61        #[derive(Debug, Clone, Copy)]
62        struct Checker;
63
64        impl InferenceChecker<Predicate> for Checker {
65            fn check(
66                &self,
67                _: pumpkin_checking::VariableState<Predicate>,
68                _: &[Predicate],
69                _: Option<&Predicate>,
70            ) -> bool {
71                true
72            }
73        }
74
75        self.state
76            .add_inference_checker(inference_code, Box::new(Checker));
77    }
78
79    pub fn new_variable(&mut self, lb: i32, ub: i32) -> DomainId {
80        self.state.new_interval_variable(lb, ub, None)
81    }
82
83    pub fn new_sparse_variable(&mut self, values: Vec<i32>) -> DomainId {
84        self.state.new_sparse_variable(values, None)
85    }
86
87    pub fn new_literal(&mut self) -> Literal {
88        let domain_id = self.new_variable(0, 1);
89        Literal::new(domain_id)
90    }
91
92    pub fn new_propagator<Constructor>(
93        &mut self,
94        constructor: Constructor,
95    ) -> Result<PropagatorId, Conflict>
96    where
97        Constructor: PropagatorConstructor,
98        Constructor::PropagatorImpl: 'static,
99    {
100        let handle = self.state.add_propagator(constructor);
101        self.state
102            .propagate_to_fixed_point()
103            .map(|_| handle.propagator_id())
104    }
105
106    pub fn contains<Var: IntegerVariable>(&self, var: Var, value: i32) -> bool {
107        var.contains(&self.state.assignments, value)
108    }
109
110    pub fn lower_bound(&self, var: DomainId) -> i32 {
111        self.state.assignments.get_lower_bound(var)
112    }
113
114    pub fn remove_and_notify(
115        &mut self,
116        propagator: PropagatorId,
117        var: DomainId,
118        value: i32,
119    ) -> EnqueueDecision {
120        let result = self.state.post(predicate!(var != value));
121        assert!(
122            result.is_ok(),
123            "The provided value to `increase_lower_bound` caused an empty domain, generally the propagator should not be notified of this change!"
124        );
125        let mut propagator_queue = PropagatorQueue::new(4);
126        #[allow(deprecated, reason = "Will be refactored in the future")]
127        self.state
128            .notification_engine
129            .notify_propagators_about_domain_events_test(
130                &mut self.state.assignments,
131                &mut self.state.trailed_values,
132                &mut self.state.propagators,
133                &mut propagator_queue,
134            );
135        if propagator_queue.is_propagator_enqueued(propagator) {
136            EnqueueDecision::Enqueue
137        } else {
138            EnqueueDecision::Skip
139        }
140    }
141
142    pub fn increase_lower_bound_and_notify(
143        &mut self,
144        propagator: PropagatorId,
145        _local_id: u32,
146        var: DomainId,
147        value: i32,
148    ) -> EnqueueDecision {
149        let result = self.state.post(predicate!(var >= value));
150        assert!(
151            result.is_ok(),
152            "The provided value to `increase_lower_bound` caused an empty domain, generally the propagator should not be notified of this change!"
153        );
154        let mut propagator_queue = PropagatorQueue::new(4);
155        #[allow(deprecated, reason = "Will be refactored in the future")]
156        self.state
157            .notification_engine
158            .notify_propagators_about_domain_events_test(
159                &mut self.state.assignments,
160                &mut self.state.trailed_values,
161                &mut self.state.propagators,
162                &mut propagator_queue,
163            );
164        if propagator_queue.is_propagator_enqueued(propagator) {
165            EnqueueDecision::Enqueue
166        } else {
167            EnqueueDecision::Skip
168        }
169    }
170
171    pub fn decrease_upper_bound_and_notify(
172        &mut self,
173        propagator: PropagatorId,
174        _local_id: u32,
175        var: DomainId,
176        value: i32,
177    ) -> EnqueueDecision {
178        let result = self.state.post(predicate!(var <= value));
179        assert!(
180            result.is_ok(),
181            "The provided value to `increase_lower_bound` caused an empty domain, generally the propagator should not be notified of this change!"
182        );
183        let mut propagator_queue = PropagatorQueue::new(4);
184        #[allow(deprecated, reason = "Will be refactored in the future")]
185        self.state
186            .notification_engine
187            .notify_propagators_about_domain_events_test(
188                &mut self.state.assignments,
189                &mut self.state.trailed_values,
190                &mut self.state.propagators,
191                &mut propagator_queue,
192            );
193        if propagator_queue.is_propagator_enqueued(propagator) {
194            EnqueueDecision::Enqueue
195        } else {
196            EnqueueDecision::Skip
197        }
198    }
199
200    pub fn is_literal_false(&self, literal: Literal) -> bool {
201        self.state
202            .assignments
203            .evaluate_predicate(literal.get_true_predicate())
204            .is_some_and(|truth_value| !truth_value)
205    }
206
207    pub fn upper_bound(&self, var: DomainId) -> i32 {
208        self.state.assignments.get_upper_bound(var)
209    }
210
211    pub fn remove(&mut self, var: DomainId, value: i32) -> Result<(), EmptyDomainConflict> {
212        let _ = self.state.post(predicate!(var != value))?;
213
214        Ok(())
215    }
216
217    pub fn set_literal(&mut self, literal: Literal, truth_value: bool) -> Result<(), EmptyDomain> {
218        let _ = match truth_value {
219            true => self.state.assignments.post_predicate(
220                literal.get_true_predicate(),
221                None,
222                &mut self.state.notification_engine,
223            )?,
224            false => self.state.assignments.post_predicate(
225                (!literal).get_true_predicate(),
226                None,
227                &mut self.state.notification_engine,
228            )?,
229        };
230
231        Ok(())
232    }
233
234    pub fn propagate(&mut self, propagator: PropagatorId) -> Result<(), Conflict> {
235        let context = PropagationContext::new(
236            &mut self.state.trailed_values,
237            &mut self.state.assignments,
238            &mut self.state.reason_store,
239            &mut self.state.notification_engine,
240            propagator,
241        );
242        self.state.propagators[propagator].propagate(context)
243    }
244
245    pub fn propagate_until_fixed_point(
246        &mut self,
247        propagator: PropagatorId,
248    ) -> Result<(), Conflict> {
249        let mut num_trail_entries = self.state.assignments.num_trail_entries();
250        self.notify_propagator(propagator);
251        loop {
252            {
253                // Specify the life-times to be able to retrieve the trail entries
254                let context = PropagationContext::new(
255                    &mut self.state.trailed_values,
256                    &mut self.state.assignments,
257                    &mut self.state.reason_store,
258                    &mut self.state.notification_engine,
259                    propagator,
260                );
261                self.state.propagators[propagator].propagate(context)?;
262                self.notify_propagator(propagator);
263            }
264            if self.state.assignments.num_trail_entries() == num_trail_entries {
265                break;
266            }
267            num_trail_entries = self.state.assignments.num_trail_entries();
268        }
269        Ok(())
270    }
271
272    pub fn notify_propagator(&mut self, _propagator: PropagatorId) {
273        #[allow(deprecated, reason = "Will be refactored in the future")]
274        self.state
275            .notification_engine
276            .notify_propagators_about_domain_events_test(
277                &mut self.state.assignments,
278                &mut self.state.trailed_values,
279                &mut self.state.propagators,
280                &mut PropagatorQueue::new(4),
281            );
282    }
283
284    pub fn get_reason_int(&mut self, predicate: Predicate) -> PropositionalConjunction {
285        #[allow(deprecated, reason = "Will be refactored in the future")]
286        let reason_ref = self
287            .state
288            .assignments
289            .get_reason_for_predicate_brute_force(predicate);
290        let mut predicates = vec![];
291        let _ = self.state.reason_store.get_or_compute(
292            reason_ref,
293            ExplanationContext::without_working_nogood(
294                &self.state.assignments,
295                self.state
296                    .assignments
297                    .get_trail_position(&predicate)
298                    .unwrap(),
299                &mut self.state.notification_engine,
300            ),
301            &mut self.state.propagators,
302            &mut predicates,
303        );
304
305        PropositionalConjunction::from(predicates)
306    }
307
308    pub fn get_reason_bool(
309        &mut self,
310        literal: Literal,
311        truth_value: bool,
312    ) -> PropositionalConjunction {
313        let predicate = match truth_value {
314            true => literal.get_true_predicate(),
315            false => (!literal).get_true_predicate(),
316        };
317        self.get_reason_int(predicate)
318    }
319
320    pub fn assert_bounds(&self, var: DomainId, lb: i32, ub: i32) {
321        let actual_lb = self.lower_bound(var);
322        let actual_ub = self.upper_bound(var);
323
324        assert_eq!(
325            (lb, ub),
326            (actual_lb, actual_ub),
327            "The expected bounds [{lb}..{ub}] did not match the actual bounds [{actual_lb}..{actual_ub}]"
328        );
329    }
330
331    pub fn new_constraint_tag(&mut self) -> ConstraintTag {
332        self.constraint_tags.next_key()
333    }
334
335    pub fn new_checkpoint(&mut self) {
336        self.state.new_checkpoint();
337    }
338
339    pub fn synchronise(&mut self, level: usize) {
340        let _ = self
341            .state
342            .assignments
343            .synchronise(level, &mut self.state.notification_engine);
344        self.state.notification_engine.synchronise(
345            level,
346            &self.state.assignments,
347            &mut self.state.trailed_values,
348        );
349        self.state.trailed_values.synchronise(level);
350
351        for propagator in self.state.propagators.iter_propagators_mut() {
352            let mut context =
353                NotificationContext::new(&mut self.state.trailed_values, &self.state.assignments);
354
355            propagator.synchronise(context.reborrow());
356        }
357    }
358}