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