pumpkin_core/engine/state.rs
1use std::sync::Arc;
2
3use pumpkin_checking::BoxedChecker;
4use pumpkin_checking::InferenceChecker;
5#[cfg(feature = "check-propagations")]
6use pumpkin_checking::VariableState;
7
8use crate::containers::HashMap;
9use crate::containers::KeyGenerator;
10use crate::create_statistics_struct;
11use crate::engine::Assignments;
12use crate::engine::ConstraintProgrammingTrailEntry;
13use crate::engine::DebugHelper;
14use crate::engine::PropagatorQueue;
15use crate::engine::TrailedValues;
16use crate::engine::VariableNames;
17#[cfg(test)]
18use crate::engine::cp::reason::StoredReason;
19use crate::engine::notifications::NotificationEngine;
20use crate::engine::reason::ReasonStore;
21use crate::predicate;
22use crate::predicates::Predicate;
23use crate::predicates::PredicateType;
24#[cfg(test)]
25use crate::predicates::PropositionalConjunction;
26use crate::proof::ConstraintTag;
27use crate::proof::InferenceCode;
28use crate::propagation::CurrentNogood;
29use crate::propagation::Domains;
30use crate::propagation::ExplanationContext;
31#[cfg(feature = "check-propagations")]
32use crate::propagation::InferenceCheckers;
33use crate::propagation::NotificationContext;
34use crate::propagation::PropagationContext;
35use crate::propagation::Propagator;
36use crate::propagation::PropagatorConstructor;
37use crate::propagation::PropagatorConstructorContext;
38use crate::propagation::PropagatorId;
39use crate::propagation::store::PropagatorStore;
40use crate::pumpkin_assert_advanced;
41use crate::pumpkin_assert_eq_simple;
42use crate::pumpkin_assert_extreme;
43use crate::pumpkin_assert_simple;
44use crate::results::SolutionReference;
45use crate::state::Conflict;
46use crate::state::EmptyDomainConflict;
47use crate::state::PropagatorHandle;
48use crate::statistics::StatisticLogger;
49use crate::statistics::log_statistic;
50use crate::variables::DomainId;
51use crate::variables::IntegerVariable;
52use crate::variables::Literal;
53
54/// The [`State`] is the container of variables and propagators.
55///
56/// [`State`] implements [`Clone`], and cloning the [`State`] will create a fresh copy of the
57/// [`State`]. If the [`State`] is large, this may be extremely expensive.
58#[derive(Debug, Clone)]
59pub struct State {
60 /// The list of propagators; propagators live here and are queried when events (domain changes)
61 /// happen.
62 pub(crate) propagators: PropagatorStore,
63 /// Tracks information related to the assignments of integer variables.
64 pub(crate) assignments: Assignments,
65 /// Keep track of trailed values (i.e. values which automatically backtrack).
66 pub(crate) trailed_values: TrailedValues,
67 /// The names of the variables in the solver.
68 pub(crate) variable_names: VariableNames,
69 /// Dictates the order in which propagators will be called to propagate.
70 pub(crate) propagator_queue: PropagatorQueue,
71 /// Handles storing information about propagation reasons, which are used later to construct
72 /// explanations during conflict analysis.
73 pub(crate) reason_store: ReasonStore,
74 /// Component responsible for providing notifications for changes to the domains of variables
75 /// and/or the polarity [Predicate]s
76 pub(crate) notification_engine: NotificationEngine,
77
78 /// The [`ConstraintTag`]s generated for this proof.
79 pub(crate) constraint_tags: KeyGenerator<ConstraintTag>,
80
81 statistics: StateStatistics,
82
83 /// Inference checkers to run in the propagation loop.
84 checkers: HashMap<InferenceCode, Vec<BoxedChecker<Predicate>>>,
85}
86
87create_statistics_struct!(StateStatistics {
88 num_propagators_called: usize,
89 num_propagations: usize,
90 num_conflicts: usize,
91 /// The number of levels which were backjumped.
92 ///
93 /// For an individual backtrack due to a learned nogood, this is calculated according to the
94 /// formula `CurrentDecisionLevel - 1 - BacktrackLevel` (i.e. how many levels (in total) has
95 /// the solver backtracked and not backjumped)
96 sum_of_backjumps: u64,
97 /// The number of times a backjump (i.e. backtracking more than a single decision level due to
98 /// a learned nogood) occurs.
99 num_backjumps: u64,
100});
101
102impl Default for State {
103 fn default() -> Self {
104 let mut result = Self {
105 assignments: Default::default(),
106 trailed_values: TrailedValues::default(),
107 variable_names: VariableNames::default(),
108 propagator_queue: PropagatorQueue::default(),
109 propagators: PropagatorStore::default(),
110 reason_store: ReasonStore::default(),
111 notification_engine: NotificationEngine::default(),
112 statistics: StateStatistics::default(),
113 constraint_tags: KeyGenerator::default(),
114 checkers: HashMap::default(),
115 };
116 // As a convention, the assignments contain a dummy domain_id=0, which represents a 0-1
117 // variable that is assigned to one. We use it to represent predicates that are
118 // trivially true. We need to adjust other data structures to take this into account.
119 let dummy_id = Predicate::trivially_true().get_domain();
120
121 result.variable_names.add_integer(dummy_id, "Dummy".into());
122 assert!(dummy_id.id() == 0);
123 assert!(result.assignments.get_lower_bound(dummy_id) == 1);
124 assert!(result.assignments.get_upper_bound(dummy_id) == 1);
125
126 result
127 }
128}
129
130impl State {
131 pub(crate) fn log_statistics(&self, verbose: bool) {
132 log_statistic("variables", self.assignments.num_domains());
133 log_statistic("propagators", self.propagators.num_propagators());
134 log_statistic("failures", self.statistics.num_conflicts);
135 log_statistic("propagations", self.statistics.num_propagators_called);
136 log_statistic("nogoods", self.statistics.num_conflicts);
137 if verbose {
138 log_statistic(
139 "numAtomicConstraintsPropagated",
140 self.statistics.num_propagations,
141 );
142 for (index, propagator) in self.propagators.iter_propagators().enumerate() {
143 propagator.log_statistics(StatisticLogger::new([
144 propagator.name(),
145 "number",
146 index.to_string().as_str(),
147 ]));
148 }
149 }
150 }
151}
152
153/// Operations to create .
154impl State {
155 /// Create a new [`ConstraintTag`].
156 pub fn new_constraint_tag(&mut self) -> ConstraintTag {
157 self.constraint_tags.next_key()
158 }
159
160 /// Creates a new Boolean (0-1) variable.
161 ///
162 /// The name is used in solver traces to identify individual domains. They are required to be
163 /// unique. If the state already contains a domain with the given name, then this function
164 /// will panic.
165 ///
166 /// Creation of new [`Literal`]s is not influenced by the current checkpoint of the state.
167 /// If a [`Literal`] is created at a non-zero checkpoint, then it will _not_ 'disappear'
168 /// when backtracking past the checkpoint where the domain was created.
169 pub fn new_literal(&mut self, name: Option<Arc<str>>) -> Literal {
170 let domain_id = self.new_interval_variable(0, 1, name);
171 Literal::new(domain_id)
172 }
173
174 /// Creates a new interval variable with the given lower and upper bound.
175 ///
176 /// The name is used in solver traces to identify individual domains. They are required to be
177 /// unique. If the state already contains a domain with the given name, then this function
178 /// will panic.
179 ///
180 /// Variables can be unnamed. In that case, `None` can be provided as the name. However,
181 /// when the solver queries the name (e.g. when logging a proof), and no name exists for a
182 /// domain, the solver will crash.
183 ///
184 /// Creation of new domains is not influenced by the current checkpoint of the state. If
185 /// a domain is created at a non-zero checkpoint, then it will _not_ 'disappear' when
186 /// backtracking past the checkpoint where the domain was created.)
187 pub fn new_interval_variable(
188 &mut self,
189 lower_bound: i32,
190 upper_bound: i32,
191 name: Option<Arc<str>>,
192 ) -> DomainId {
193 let domain_id = self.assignments.grow(lower_bound, upper_bound);
194
195 if let Some(name) = name {
196 self.variable_names.add_integer(domain_id, name);
197 }
198
199 self.notification_engine.grow();
200
201 domain_id
202 }
203
204 /// Creates a new sparse domain with the given values.
205 ///
206 /// Note that this is implemented as an interval domain with explicit holes in the domain. For
207 /// very sparse domains, this can result in a high memory overhead.
208 ///
209 /// For more information on creation of domains, see [`State::new_interval_variable`].
210 pub fn new_sparse_variable(&mut self, values: Vec<i32>, name: Option<String>) -> DomainId {
211 let domain_id = self.assignments.create_new_integer_variable_sparse(values);
212
213 if let Some(name) = name {
214 self.variable_names.add_integer(domain_id, name.into());
215 }
216
217 self.notification_engine.grow();
218
219 domain_id
220 }
221}
222
223/// Operations to retrieve information about values
224impl State {
225 /// Returns the lower-bound of the given `variable`.
226 pub fn lower_bound<Var: IntegerVariable>(&self, variable: Var) -> i32 {
227 variable.lower_bound(&self.assignments)
228 }
229
230 /// Returns the upper-bound of the given `variable`.
231 pub fn upper_bound<Var: IntegerVariable>(&self, variable: Var) -> i32 {
232 variable.upper_bound(&self.assignments)
233 }
234
235 /// Returns whether the given `variable` contains the provided `value`.
236 pub fn contains<Var: IntegerVariable>(&self, variable: Var, value: i32) -> bool {
237 variable.contains(&self.assignments, value)
238 }
239
240 /// If the given `variable` is fixed, then [`Some`] containing the assigned value is
241 /// returned. Otherwise, [`None`] is returned.
242 pub fn fixed_value<Var: IntegerVariable>(&self, variable: Var) -> Option<i32> {
243 (self.lower_bound(variable.clone()) == self.upper_bound(variable.clone()))
244 .then(|| self.lower_bound(variable))
245 }
246
247 /// Returns `true` if the given predicate is assigned simply by the initial domain of the
248 /// variable.
249 pub fn is_implied_by_initial_domain(&self, predicate: Predicate) -> bool {
250 self.assignments.is_initial_bound(predicate)
251 }
252
253 /// Returns the truth value of the provided [`Predicate`].
254 ///
255 /// If the [`Predicate`] is assigned in the current [`State`] then [`Some`] containing whether
256 /// the [`Predicate`] is satisfied or falsified is returned. Otherwise, [`None`] is returned.
257 pub fn truth_value(&self, predicate: Predicate) -> Option<bool> {
258 self.assignments.evaluate_predicate(predicate)
259 }
260
261 /// If the provided [`Predicate`] is satisfied then it returns [`Some`] containing the
262 /// checkpoint at which the [`Predicate`] became satisfied. Otherwise, [`None`] is returned.
263 pub fn get_checkpoint_for_predicate(&self, predicate: Predicate) -> Option<usize> {
264 self.assignments.get_checkpoint_for_predicate(&predicate)
265 }
266
267 /// Returns the truth value of the provided [`Literal`].
268 ///
269 /// If the [`Literal`] is assigned in the current [`State`] then [`Some`] containing whether
270 /// the [`Literal`] is satisfied or falsified is returned. Otherwise, [`None`] is returned.
271 pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
272 self.truth_value(literal.get_true_predicate())
273 }
274
275 /// Returns the number of created checkpoints.
276 pub fn get_checkpoint(&self) -> usize {
277 self.assignments.get_checkpoint()
278 }
279}
280
281/// Operations for retrieving information about trail
282impl State {
283 /// Returns the length of the trail.
284 pub(crate) fn trail_len(&self) -> usize {
285 self.assignments.num_trail_entries()
286 }
287
288 /// Returns the [`Predicate`] at the provided `trail_index`.
289 pub(crate) fn trail_entry(&self, trail_index: usize) -> ConstraintProgrammingTrailEntry {
290 self.assignments.get_trail_entry(trail_index)
291 }
292
293 /// Returns whether the provided [`Predicate`] is explicitly on the trail.
294 ///
295 /// For example, if we post the [`Predicate`] [x >= v], then the predicate [x >= v - 1] is
296 /// not explicity on the trail.
297 pub fn is_on_trail(&self, predicate: Predicate) -> bool {
298 let trail_position = self.trail_position(predicate);
299
300 trail_position.is_some_and(|trail_position| {
301 self.assignments.trail[trail_position].predicate == predicate
302 })
303 }
304
305 /// Returns whether the trail position of the provided [`Predicate`].
306 pub fn trail_position(&self, predicate: Predicate) -> Option<usize> {
307 self.assignments.get_trail_position(&predicate)
308 }
309}
310
311/// Operations for adding constraints.
312impl State {
313 /// Enqueues the propagator with [`PropagatorHandle`] `handle` for propagation.
314 #[deprecated]
315 pub(crate) fn enqueue_propagator<P: Propagator>(&mut self, handle: PropagatorHandle<P>) {
316 let priority = self.propagators[handle.propagator_id()].priority();
317 self.propagator_queue
318 .enqueue_propagator(handle.propagator_id(), priority);
319 }
320
321 /// Add a new propagator to the [`State`]. The constructor for that propagator should
322 /// subscribe to the appropriate domain events so that the propagator is called when
323 /// necessary.
324 ///
325 /// While the propagator is added to the queue for propagation, this function does _not_
326 /// trigger a round of propagation. An explicit call to [`State::propagate_to_fixed_point`] is
327 /// necessary to run the new propagator for the first time.
328 pub fn add_propagator<Constructor>(
329 &mut self,
330 constructor: Constructor,
331 ) -> PropagatorHandle<Constructor::PropagatorImpl>
332 where
333 Constructor: PropagatorConstructor,
334 Constructor::PropagatorImpl: 'static,
335 {
336 #[cfg(feature = "check-propagations")]
337 constructor.add_inference_checkers(InferenceCheckers::new(self));
338
339 let original_handle: PropagatorHandle<Constructor::PropagatorImpl> =
340 self.propagators.new_propagator().key();
341
342 let constructor_context =
343 PropagatorConstructorContext::new(original_handle.propagator_id(), self);
344 let propagator = constructor.create(constructor_context);
345
346 pumpkin_assert_simple!(
347 propagator.priority() as u8 <= 3,
348 "The propagator priority exceeds 3.
349 Currently we only support values up to 3,
350 but this can easily be changed if there is a good reason."
351 );
352
353 let slot = self.propagators.new_propagator();
354 let handle = slot.populate(propagator);
355
356 pumpkin_assert_eq_simple!(handle.propagator_id(), original_handle.propagator_id());
357
358 #[allow(deprecated, reason = "Will be refactored")]
359 self.enqueue_propagator(handle);
360
361 handle
362 }
363
364 /// Add an inference checker to the state.
365 ///
366 /// The inference checker will be used to check propagations performed during
367 /// [`Self::propagate_to_fixed_point`], if the `check-propagations` feature is enabled.
368 ///
369 /// Multiple inference checkers may be added for the same inference code. In that case, if
370 /// any checker accepts the inference, the inference is accepted.
371 pub fn add_inference_checker(
372 &mut self,
373 inference_code: InferenceCode,
374 checker: Box<dyn InferenceChecker<Predicate>>,
375 ) {
376 let checkers = self.checkers.entry(inference_code).or_default();
377 checkers.push(BoxedChecker::from(checker));
378 }
379}
380
381/// Operations for retrieving propagators.
382impl State {
383 /// Get a reference to the propagator identified by the given handle.
384 ///
385 /// For an exclusive reference, use [`State::get_propagator_mut`].
386 pub fn get_propagator<P: Propagator>(&self, handle: PropagatorHandle<P>) -> Option<&P> {
387 self.propagators.get_propagator(handle)
388 }
389
390 /// Get an exclusive reference to the propagator identified by the given handle.
391 pub fn get_propagator_mut<P: Propagator>(
392 &mut self,
393 handle: PropagatorHandle<P>,
394 ) -> Option<&mut P> {
395 self.propagators.get_propagator_mut(handle)
396 }
397
398 /// Convert the given propagator ID into a typed [`PropagatorHandle`].
399 ///
400 /// If the propagator ID does not correspond to a propagator of the expected type, then
401 /// `None` is returned.
402 pub fn as_propagator_handle<P: Propagator>(
403 &mut self,
404 propagator_id: PropagatorId,
405 ) -> Option<PropagatorHandle<P>> {
406 self.propagators.as_propagator_handle(propagator_id)
407 }
408
409 /// Get an exclusive reference to the propagator identified by the given handle and a context
410 /// which can be used for propagation.
411 pub(crate) fn get_propagator_mut_with_context<P: Propagator>(
412 &mut self,
413 handle: PropagatorHandle<P>,
414 ) -> (Option<&mut P>, PropagationContext<'_>) {
415 (
416 self.propagators.get_propagator_mut(handle),
417 PropagationContext::new(
418 &mut self.trailed_values,
419 &mut self.assignments,
420 &mut self.reason_store,
421 &mut self.notification_engine,
422 handle.propagator_id(),
423 ),
424 )
425 }
426}
427
428/// Operations for modifying the state.
429impl State {
430 /// Apply a [`Predicate`] to the [`State`].
431 ///
432 /// Returns `true` if a change to a domain occured, and `false` if the given [`Predicate`] was
433 /// already true.
434 ///
435 /// If a domain becomes empty due to this operation, an [`EmptyDomainConflict`] error is
436 /// returned.
437 ///
438 /// This method does _not_ perform any propagation. For that, an explicit call to
439 /// [`State::propagate_to_fixed_point`] is required. This allows the
440 /// posting of multiple predicates before the entire propagation engine is invoked.
441 ///
442 /// A call to [`State::restore_to`] that goes past the checkpoint at which a [`Predicate`]
443 /// was posted will undo the effect of that [`Predicate`]. See the documentation of
444 /// [`State::new_checkpoint`] and
445 /// [`State::restore_to`] for more information.
446 pub fn post(&mut self, predicate: Predicate) -> Result<bool, EmptyDomainConflict> {
447 self.assignments
448 .post_predicate(predicate, None, &mut self.notification_engine)
449 .map_err(|_| EmptyDomainConflict {
450 trigger_predicate: predicate,
451 trigger_reason: None,
452 })
453 }
454
455 #[cfg(test)]
456 fn post_with_reason(
457 &mut self,
458 predicate: Predicate,
459 reason: PropositionalConjunction,
460 inference_code: InferenceCode,
461 propagator_id: PropagatorId,
462 ) -> Result<(), EmptyDomainConflict> {
463 let slot = self.reason_store.new_slot();
464
465 let modification_result = self.assignments.post_predicate(
466 predicate,
467 Some(slot.reason_ref()),
468 &mut self.notification_engine,
469 );
470
471 match modification_result {
472 Ok(false) => Ok(()),
473 Ok(true) => {
474 let _ = slot.populate(propagator_id, StoredReason::Eager(reason, inference_code));
475 Ok(())
476 }
477 Err(_) => {
478 let _ = slot.populate(propagator_id, StoredReason::Eager(reason, inference_code));
479 let (trigger_predicate, trigger_reason) =
480 self.assignments.remove_last_trail_element();
481
482 Err(EmptyDomainConflict {
483 trigger_predicate,
484 trigger_reason: Some(trigger_reason),
485 })
486 }
487 }
488 }
489
490 /// Create a checkpoint of the current [`State`], that can be returned to with
491 /// [`State::restore_to`].
492 ///
493 /// The current checkpoint can be retrieved using the method [`State::get_checkpoint`].
494 ///
495 /// If the state is not at fixed-point, then this method will panic.
496 ///
497 /// # Example
498 /// ```
499 /// use pumpkin_core::predicate;
500 /// use pumpkin_core::state::State;
501 ///
502 /// let mut state = State::default();
503 /// let variable = state.new_interval_variable(1, 10, Some("x1".into()));
504 ///
505 /// assert_eq!(state.get_checkpoint(), 0);
506 ///
507 /// state.new_checkpoint();
508 ///
509 /// assert_eq!(state.get_checkpoint(), 1);
510 ///
511 /// state
512 /// .post(predicate![variable <= 5])
513 /// .expect("The lower bound is 1 so no conflict");
514 /// assert_eq!(state.upper_bound(variable), 5);
515 ///
516 /// state.restore_to(0);
517 ///
518 /// assert_eq!(state.get_checkpoint(), 0);
519 /// assert_eq!(state.upper_bound(variable), 10);
520 /// ```
521 pub fn new_checkpoint(&mut self) {
522 pumpkin_assert_simple!(
523 self.propagator_queue.is_empty(),
524 "Can only create a new checkpoint when all propagation has occurred"
525 );
526 self.assignments.new_checkpoint();
527 self.notification_engine.new_checkpoint();
528 self.trailed_values.new_checkpoint();
529 self.reason_store.new_checkpoint();
530 }
531
532 /// Restore to the given checkpoint and return the [`DomainId`]s which were fixed before
533 /// restoring, with their assigned values.
534 ///
535 /// If the provided checkpoint is equal to the current checkpoint, this is a no-op. If
536 /// the provided checkpoint is larger than the current checkpoint, this method will
537 /// panic.
538 ///
539 /// See [`State::new_checkpoint`] for an example.
540 pub fn restore_to(&mut self, checkpoint: usize) -> Vec<(DomainId, i32)> {
541 pumpkin_assert_simple!(checkpoint <= self.get_checkpoint());
542
543 self.statistics.sum_of_backjumps +=
544 (self.get_checkpoint().saturating_sub(1) - checkpoint) as u64;
545 if self.get_checkpoint() - checkpoint > 1 {
546 self.statistics.num_backjumps += 1;
547 }
548
549 if checkpoint == self.get_checkpoint() {
550 return vec![];
551 }
552
553 let unfixed_after_backtracking = self
554 .assignments
555 .synchronise(checkpoint, &mut self.notification_engine);
556 self.trailed_values.synchronise(checkpoint);
557 self.reason_store.synchronise(checkpoint);
558
559 self.propagator_queue.clear();
560 // For now all propagators are called to synchronise, in the future this will be improved in
561 // two ways:
562 // + allow incremental synchronisation
563 // + only call the subset of propagators that were notified since last backtrack
564 for propagator in self.propagators.iter_propagators_mut() {
565 let mut context = NotificationContext::new(&mut self.trailed_values, &self.assignments);
566
567 propagator.synchronise(context.reborrow());
568 }
569
570 let _ = self.notification_engine.process_backtrack_events(
571 &mut self.assignments,
572 &mut self.trailed_values,
573 &mut self.propagators,
574 );
575 self.notification_engine.clear_event_drain();
576
577 self.notification_engine
578 .update_last_notified_index(&mut self.assignments);
579 // Should be done after the assignments and trailed values have been synchronised
580 self.notification_engine.synchronise(
581 checkpoint,
582 &self.assignments,
583 &mut self.trailed_values,
584 );
585
586 unfixed_after_backtracking
587 }
588
589 /// Performs a single call to [`Propagator::propagate`] for the propagator with the provided
590 /// [`PropagatorId`].
591 ///
592 /// Other propagators could be enqueued as a result of the changes made by the propagated
593 /// propagator but a call to [`State::propagate_to_fixed_point`] is
594 /// required for further propagation to occur.
595 ///
596 /// It could be that the current [`State`] implies a conflict by propagation. In that case, an
597 /// [`Err`] with [`Conflict`] is returned.
598 ///
599 /// Once the [`State`] is conflicting, then the only operation that is defined is
600 /// [`State::restore_to`]. All other operations and queries on the state are undetermined.
601 fn propagate(&mut self, propagator_id: PropagatorId) -> Result<(), Conflict> {
602 self.statistics.num_propagators_called += 1;
603
604 let num_trail_entries_before = self.assignments.num_trail_entries();
605
606 let propagation_status = {
607 let propagator = &mut self.propagators[propagator_id];
608 let context = PropagationContext::new(
609 &mut self.trailed_values,
610 &mut self.assignments,
611 &mut self.reason_store,
612 &mut self.notification_engine,
613 propagator_id,
614 );
615 propagator.propagate(context)
616 };
617
618 #[cfg(feature = "check-propagations")]
619 self.check_propagations(num_trail_entries_before);
620
621 match propagation_status {
622 Ok(_) => {
623 // Notify other propagators of the propagations and continue.
624 self.notification_engine
625 .notify_propagators_about_domain_events(
626 &mut self.assignments,
627 &mut self.trailed_values,
628 &mut self.propagators,
629 &mut self.propagator_queue,
630 );
631 pumpkin_assert_extreme!(
632 DebugHelper::debug_check_propagations(
633 num_trail_entries_before,
634 propagator_id,
635 &self.trailed_values,
636 &self.assignments,
637 &mut self.reason_store,
638 &mut self.propagators,
639 &self.notification_engine
640 ),
641 "Checking the propagations performed by the propagator led to inconsistencies!"
642 );
643 }
644 Err(conflict) => {
645 #[cfg(feature = "check-propagations")]
646 self.check_conflict(&conflict);
647
648 self.statistics.num_conflicts += 1;
649 if let Conflict::Propagator(inner) = &conflict {
650 pumpkin_assert_advanced!(DebugHelper::debug_reported_failure(
651 &self.trailed_values,
652 &self.assignments,
653 &inner.conjunction,
654 &self.propagators[propagator_id],
655 propagator_id,
656 &self.notification_engine
657 ));
658 }
659
660 return Err(conflict);
661 }
662 }
663 Ok(())
664 }
665
666 /// Check the inference that triggered the given conflict.
667 ///
668 /// Does nothing when the conflict is an empty domain.
669 ///
670 /// Panics when the inference checker rejects the conflict.
671 #[cfg(feature = "check-propagations")]
672 fn check_conflict(&mut self, conflict: &Conflict) {
673 if let Conflict::Propagator(propagator_conflict) = conflict {
674 self.run_checker(
675 propagator_conflict.conjunction.clone(),
676 None,
677 &propagator_conflict.inference_code,
678 );
679 }
680 }
681
682 /// For every item on the trail starting at index `first_propagation_index`, run the
683 /// inference checker for it.
684 ///
685 /// This method should be called after every propagator invocation, so all elements on the
686 /// trail starting at `first_propagation_index` should be propagations. Otherwise this function
687 /// will panic.
688 ///
689 /// If the checker rejects the inference, this method panics.
690 #[cfg(feature = "check-propagations")]
691 pub(crate) fn check_propagations(&mut self, first_propagation_index: usize) {
692 let mut reason_buffer = vec![];
693
694 for trail_index in first_propagation_index..self.assignments.num_trail_entries() {
695 let entry = self.assignments.get_trail_entry(trail_index);
696
697 let reason_ref = entry
698 .reason
699 .expect("propagations should only be checked after propagations");
700
701 reason_buffer.clear();
702 let inference_code = self.reason_store.get_or_compute(
703 reason_ref,
704 ExplanationContext::without_working_nogood(
705 &self.assignments,
706 trail_index,
707 &mut self.notification_engine,
708 ),
709 &mut self.propagators,
710 &mut reason_buffer,
711 );
712
713 self.run_checker(
714 std::mem::take(&mut reason_buffer),
715 Some(entry.predicate),
716 &inference_code,
717 );
718 }
719 }
720
721 /// Performs fixed-point propagation using the propagators defined in the [`State`].
722 ///
723 /// The posted [`Predicate`]s (using [`State::post`]) and added propagators (using
724 /// [`State::add_propagator`]) cause propagators to be enqueued when the events that
725 /// they have subscribed to are triggered. As propagation causes more changes to be made,
726 /// more propagators are enqueued. This continues until applying all (enqueued)
727 /// propagators leads to no more domain changes.
728 ///
729 /// It could be that the current [`State`] implies a conflict by propagation. In that case, an
730 /// error with [`Conflict`] is returned.
731 ///
732 /// Once the [`State`] is conflicting, then the only operation that is defined is
733 /// [`State::restore_to`]. All other operations and queries on the state are unspecified.
734 pub fn propagate_to_fixed_point(&mut self) -> Result<(), Conflict> {
735 // The initial domain events are due to the decision predicate.
736 self.notification_engine
737 .notify_propagators_about_domain_events(
738 &mut self.assignments,
739 &mut self.trailed_values,
740 &mut self.propagators,
741 &mut self.propagator_queue,
742 );
743
744 // Keep propagating until there are unprocessed propagators, or a conflict is detected.
745 while let Some(propagator_id) = self.propagator_queue.pop() {
746 self.propagate(propagator_id)?;
747 }
748
749 // Only check fixed point propagation if there was no reported conflict,
750 // since otherwise the state may be inconsistent.
751 pumpkin_assert_extreme!(DebugHelper::debug_fixed_point_propagation(
752 &self.trailed_values,
753 &self.assignments,
754 &self.propagators,
755 &self.notification_engine
756 ));
757
758 Ok(())
759 }
760}
761
762#[cfg(feature = "check-propagations")]
763impl State {
764 /// Run the checker for the given inference code on the given inference.
765 fn run_checker(
766 &self,
767 premises: impl IntoIterator<Item = Predicate>,
768 consequent: Option<Predicate>,
769 inference_code: &InferenceCode,
770 ) {
771 let premises: Vec<_> = premises.into_iter().collect();
772
773 let checkers = self
774 .checkers
775 .get(inference_code)
776 .map(|vec| vec.as_slice())
777 .unwrap_or(&[]);
778
779 assert!(
780 !checkers.is_empty(),
781 "missing checker for inference code {inference_code:?}"
782 );
783
784 let any_checker_accepts_inference = checkers.iter().any(|checker| {
785 // Construct the variable state for the conflict check.
786 let variable_state = VariableState::prepare_for_conflict_check(
787 premises.clone(),
788 consequent,
789 )
790 .unwrap_or_else(|domain| {
791 panic!(
792 "inconsistent atomics over domain {domain:?} in inference by {inference_code:?}"
793 )
794 });
795
796 checker.check(variable_state, &premises, consequent.as_ref())
797 });
798
799 assert!(
800 any_checker_accepts_inference,
801 "checker for inference code {:?} fails on inference {:?} -> {:?}",
802 inference_code,
803 premises.into_iter().collect::<Vec<_>>(),
804 consequent,
805 );
806 }
807}
808
809impl State {
810 /// This is a temporary accessor to help refactoring.
811 pub(crate) fn get_solution_reference(&self) -> SolutionReference<'_> {
812 SolutionReference::new(&self.assignments)
813 }
814
815 /// Returns a mapping of [`DomainId`] to variable name.
816 pub(crate) fn variable_names(&self) -> &VariableNames {
817 &self.variable_names
818 }
819
820 pub(crate) fn get_propagation_reason_trail_entry(
821 &mut self,
822 trail_position: usize,
823 reason_buffer: &mut (impl Extend<Predicate> + AsRef<[Predicate]>),
824 ) -> InferenceCode {
825 let entry = self.trail_entry(trail_position);
826 let reason_ref = entry
827 .reason
828 .expect("Added by a propagator and must therefore have a reason");
829 self.reason_store.get_or_compute(
830 reason_ref,
831 ExplanationContext::without_working_nogood(
832 &self.assignments,
833 trail_position,
834 &mut self.notification_engine,
835 ),
836 &mut self.propagators,
837 reason_buffer,
838 )
839 }
840 /// Get the reason for a predicate being true and store it in `reason_buffer`.
841 ///
842 /// If the provided [`Predicate`] is propagated by a propagator, then the [`InferenceCode`]
843 /// accompanies the propagation is returned.
844 ///
845 /// The provided `current_nogood` can be used by the propagator to provide a different reason;
846 /// use [`CurrentNogood::empty`] otherwise.
847 ///
848 /// All the predicates appended to the `reason_buffer` will evaluate to `true`. The buffer
849 /// is _not_ cleared before predicates are appended.
850 ///
851 /// If the provided predicate is not true, then this method will panic.
852 pub fn get_propagation_reason(
853 &mut self,
854 predicate: Predicate,
855 reason_buffer: &mut (impl Extend<Predicate> + AsRef<[Predicate]>),
856 current_nogood: CurrentNogood<'_>,
857 ) -> Option<InferenceCode> {
858 // TODO: this function could be put into the reason store
859
860 // Note that this function can only be called with propagations, and never decision
861 // predicates. Furthermore only predicate from the current checkpoint will be
862 // considered. This is due to how the 1uip conflict analysis works: it scans the
863 // predicates in reverse order of assignment, and stops as soon as there is only one
864 // predicate from the current checkpoint in the learned nogood.
865
866 // This means that the procedure would never ask for the reason of the decision predicate
867 // from the current checkpoint, because that would mean that all other predicates from
868 // the current checkpoint have been removed from the nogood, and the decision
869 // predicate is the only one left, but in that case, the 1uip would terminate since
870 // there would be only one predicate from the current checkpoint. For this
871 // reason, it is safe to assume that in the following, that any input predicate is
872 // indeed a propagated predicate.
873 if self.assignments.is_initial_bound(predicate) {
874 return None;
875 }
876
877 let trail_position = self
878 .assignments
879 .get_trail_position(&predicate)
880 .unwrap_or_else(|| panic!("The predicate {predicate:?} must be true during conflict analysis. Bounds were {},{}", self.lower_bound(predicate.get_domain()), self.upper_bound(predicate.get_domain())));
881
882 let trail_entry = self.assignments.get_trail_entry(trail_position);
883
884 // We distinguish between three cases:
885 // 1) The predicate is explicitly present on the trail.
886 if trail_entry.predicate == predicate {
887 let reason_ref = trail_entry.reason?;
888
889 let explanation_context = ExplanationContext::new(
890 &self.assignments,
891 current_nogood,
892 trail_position,
893 &mut self.notification_engine,
894 );
895
896 let inference_code = self.reason_store.get_or_compute(
897 reason_ref,
898 explanation_context,
899 &mut self.propagators,
900 reason_buffer,
901 );
902
903 Some(inference_code)
904 }
905 // 2) The predicate is true due to a propagation, and not explicitly on the trail.
906 // It is necessary to further analyse what was the reason for setting the predicate true.
907 else {
908 // The reason for propagation depends on:
909 // 1) The predicate on the trail at the moment the input predicate became true, and
910 // 2) The input predicate.
911 match (
912 trail_entry.predicate.get_predicate_type(),
913 predicate.get_predicate_type(),
914 ) {
915 (PredicateType::LowerBound, PredicateType::LowerBound) => {
916 let trail_lower_bound = trail_entry.predicate.get_right_hand_side();
917 let domain_id = predicate.get_domain();
918 let input_lower_bound = predicate.get_right_hand_side();
919 // Both the input predicate and the trail predicate are lower bound
920 // literals. Two cases to consider:
921 // 1) The trail predicate has a greater right-hand side, meaning
922 // the reason for the input predicate is true is because a stronger
923 // right-hand side predicate was posted. We can reuse the same
924 // reason as for the trail bound.
925 // todo: could consider lifting here, since the trail bound
926 // might be too strong.
927 if trail_lower_bound > input_lower_bound {
928 reason_buffer.extend(std::iter::once(trail_entry.predicate));
929 }
930 // Otherwise, the input bound is strictly greater than the trailed
931 // bound. This means the reason is due to holes in the domain.
932 else {
933 // Note that the bounds cannot be equal.
934 // If the bound were equal, the predicate would be explicitly on the
935 // trail, so we would have detected this case earlier.
936 pumpkin_assert_simple!(trail_lower_bound < input_lower_bound);
937
938 // The reason for the propagation of the input predicate [x >= a] is
939 // because [x >= a-1] & [x != a]. Conflict analysis will then
940 // recursively decompose these further.
941
942 // Note that we do not need to worry about decreasing the lower
943 // bounds so much so that it reaches its root lower bound, for which
944 // there is no reason since it is given as input to the problem.
945 // We cannot reach the original lower bound since in the 1uip, we
946 // only look for reasons for predicates from the current decision
947 // level, and we never look for reasons at the root level.
948
949 let one_less_bound_predicate =
950 predicate!(domain_id >= input_lower_bound - 1);
951
952 let not_equals_predicate = predicate!(domain_id != input_lower_bound - 1);
953 reason_buffer.extend(std::iter::once(one_less_bound_predicate));
954 reason_buffer.extend(std::iter::once(not_equals_predicate));
955 }
956 }
957 (PredicateType::LowerBound, PredicateType::NotEqual) => {
958 let trail_lower_bound = trail_entry.predicate.get_right_hand_side();
959 let not_equal_constant = predicate.get_right_hand_side();
960 // The trail entry is a lower bound literal,
961 // and the input predicate is a not equals.
962 // Only one case to consider:
963 // The trail lower bound is greater than the not_equals_constant,
964 // so it safe to take the reason from the trail.
965 // todo: lifting could be used here
966 pumpkin_assert_simple!(trail_lower_bound > not_equal_constant);
967 reason_buffer.extend(std::iter::once(trail_entry.predicate));
968 }
969 (PredicateType::LowerBound, PredicateType::Equal) => {
970 let domain_id = predicate.get_domain();
971 let equality_constant = predicate.get_right_hand_side();
972 // The input predicate is an equality predicate, and the trail predicate
973 // is a lower bound predicate. This means that the time of posting the
974 // trail predicate is when the input predicate became true.
975
976 // Note that the input equality constant does _not_ necessarily equal
977 // the trail lower bound. This would be the
978 // case when the the trail lower bound is lower than the input equality
979 // constant, but due to holes in the domain, the lower bound got raised
980 // to just the value of the equality constant.
981 // For example, {1, 2, 3, 10}, then posting [x >= 5] will raise the
982 // lower bound to x >= 10.
983
984 let predicate_lb = predicate!(domain_id >= equality_constant);
985 let predicate_ub = predicate!(domain_id <= equality_constant);
986 reason_buffer.extend(std::iter::once(predicate_lb));
987 reason_buffer.extend(std::iter::once(predicate_ub));
988 }
989 (PredicateType::UpperBound, PredicateType::UpperBound) => {
990 let trail_upper_bound = trail_entry.predicate.get_right_hand_side();
991 let domain_id = predicate.get_domain();
992 let input_upper_bound = predicate.get_right_hand_side();
993 // Both the input and trail predicates are upper bound predicates.
994 // There are two scenarios to consider:
995 // 1) The input upper bound is greater than the trail upper bound, meaning that
996 // the reason for the input predicate is the propagation of a stronger upper
997 // bound. We can safely use the reason for of the trail predicate as the
998 // reason for the input predicate.
999 // todo: lifting could be applied here.
1000 if trail_upper_bound < input_upper_bound {
1001 reason_buffer.extend(std::iter::once(trail_entry.predicate));
1002 } else {
1003 // I think it cannot be that the bounds are equal, since otherwise we
1004 // would have found the predicate explicitly on the trail.
1005 pumpkin_assert_simple!(trail_upper_bound > input_upper_bound);
1006
1007 // The input upper bound is greater than the trail predicate, meaning
1008 // that holes in the domain also played a rule in lowering the upper
1009 // bound.
1010
1011 // The reason of the input predicate [x <= a] is computed recursively as
1012 // the reason for [x <= a + 1] & [x != a + 1].
1013
1014 let new_ub_predicate = predicate!(domain_id <= input_upper_bound + 1);
1015 let not_equal_predicate = predicate!(domain_id != input_upper_bound + 1);
1016 reason_buffer.extend(std::iter::once(new_ub_predicate));
1017 reason_buffer.extend(std::iter::once(not_equal_predicate));
1018 }
1019 }
1020 (PredicateType::UpperBound, PredicateType::NotEqual) => {
1021 let trail_upper_bound = trail_entry.predicate.get_right_hand_side();
1022 let not_equal_constant = predicate.get_right_hand_side();
1023 // The input predicate is a not equal predicate, and the trail predicate is
1024 // an upper bound predicate. This is only possible when the upper bound was
1025 // pushed below the not equals value. Otherwise the hole would have been
1026 // explicitly placed on the trail and we would have found it earlier.
1027 pumpkin_assert_simple!(not_equal_constant > trail_upper_bound);
1028
1029 // The bound was set past the not equals, so we can safely returns the trail
1030 // reason. todo: can do lifting here.
1031 reason_buffer.extend(std::iter::once(trail_entry.predicate));
1032 }
1033 (PredicateType::UpperBound, PredicateType::Equal) => {
1034 let domain_id = predicate.get_domain();
1035 let equality_constant = predicate.get_right_hand_side();
1036 // The input predicate is an equality predicate, and the trail predicate
1037 // is an upper bound predicate. This means that the time of posting the
1038 // trail predicate is when the input predicate became true.
1039
1040 // Note that the input equality constant does _not_ necessarily equal
1041 // the trail upper bound. This would be the
1042 // case when the the trail upper bound is greater than the input equality
1043 // constant, but due to holes in the domain, the upper bound got lowered
1044 // to just the value of the equality constant.
1045 // For example, x = {1, 2, 3, 8, 15}, setting [x <= 12] would lower the
1046 // upper bound to x <= 8.
1047
1048 // Note that it could be that one of the two predicates are decision
1049 // predicates, so we need to use the substitute functions.
1050
1051 let predicate_lb = predicate!(domain_id >= equality_constant);
1052 let predicate_ub = predicate!(domain_id <= equality_constant);
1053 reason_buffer.extend(std::iter::once(predicate_lb));
1054 reason_buffer.extend(std::iter::once(predicate_ub));
1055 }
1056 (PredicateType::NotEqual, PredicateType::LowerBound) => {
1057 let not_equal_constant = trail_entry.predicate.get_right_hand_side();
1058 let domain_id = predicate.get_domain();
1059 let input_lower_bound = predicate.get_right_hand_side();
1060 // The trail predicate is not equals, but the input predicate is a lower
1061 // bound predicate. This means that creating the hole in the domain resulted
1062 // in raising the lower bound.
1063
1064 // I think this holds. The not_equals_constant cannot be greater, since that
1065 // would not impact the lower bound. It can also not be the same, since
1066 // creating a hole cannot result in the lower bound being raised to the
1067 // hole, there must be some other reason for that to happen, which we would
1068 // find earlier.
1069 pumpkin_assert_simple!(input_lower_bound > not_equal_constant);
1070
1071 // The reason for the input predicate [x >= a] is computed recursively as
1072 // the reason for [x >= a - 1] & [x != a - 1].
1073 let new_lb_predicate = predicate!(domain_id >= input_lower_bound - 1);
1074 let new_not_equals_predicate = predicate!(domain_id != input_lower_bound - 1);
1075
1076 reason_buffer.extend(std::iter::once(new_lb_predicate));
1077 reason_buffer.extend(std::iter::once(new_not_equals_predicate));
1078 }
1079 (PredicateType::NotEqual, PredicateType::UpperBound) => {
1080 let not_equal_constant = trail_entry.predicate.get_right_hand_side();
1081 let domain_id = predicate.get_domain();
1082 let input_upper_bound = predicate.get_right_hand_side();
1083 // The trail predicate is not equals, but the input predicate is an upper
1084 // bound predicate. This means that creating the hole in the domain resulted
1085 // in lower the upper bound.
1086
1087 // I think this holds. The not_equals_constant cannot be smaller, since that
1088 // would not impact the upper bound. It can also not be the same, since
1089 // creating a hole cannot result in the upper bound being lower to the
1090 // hole, there must be some other reason for that to happen, which we would
1091 // find earlier.
1092 pumpkin_assert_simple!(input_upper_bound < not_equal_constant);
1093
1094 // The reason for the input predicate [x <= a] is computed recursively as
1095 // the reason for [x <= a + 1] & [x != a + 1].
1096 let new_ub_predicate = predicate!(domain_id <= input_upper_bound + 1);
1097 let new_not_equals_predicate = predicate!(domain_id != input_upper_bound + 1);
1098
1099 reason_buffer.extend(std::iter::once(new_ub_predicate));
1100 reason_buffer.extend(std::iter::once(new_not_equals_predicate));
1101 }
1102 (PredicateType::NotEqual, PredicateType::Equal) => {
1103 let domain_id = predicate.get_domain();
1104 let equality_constant = predicate.get_right_hand_side();
1105 // The trail predicate is not equals, but the input predicate is
1106 // equals. The only time this could is when the not equals forces the
1107 // lower/upper bounds to meet. So we simply look for the reasons for those
1108 // bounds recursively.
1109
1110 // Note that it could be that one of the two predicates are decision
1111 // predicates, so we need to use the substitute functions.
1112
1113 let predicate_lb = predicate!(domain_id >= equality_constant);
1114 let predicate_ub = predicate!(domain_id <= equality_constant);
1115
1116 reason_buffer.extend(std::iter::once(predicate_lb));
1117 reason_buffer.extend(std::iter::once(predicate_ub));
1118 }
1119 (
1120 PredicateType::Equal,
1121 PredicateType::LowerBound | PredicateType::UpperBound | PredicateType::NotEqual,
1122 ) => {
1123 // The trail predicate is equality, but the input predicate is either a
1124 // lower-bound, upper-bound, or not equals.
1125 //
1126 // TODO: could consider lifting here
1127 reason_buffer.extend(std::iter::once(trail_entry.predicate))
1128 }
1129 _ => unreachable!(
1130 "Unreachable combination of {} and {}",
1131 trail_entry.predicate, predicate
1132 ),
1133 };
1134 None
1135 }
1136 }
1137}
1138
1139impl State {
1140 pub fn get_domains(&mut self) -> Domains<'_> {
1141 Domains::new(&self.assignments, &mut self.trailed_values)
1142 }
1143
1144 pub fn get_propagation_context(&mut self) -> PropagationContext<'_> {
1145 PropagationContext::new(
1146 &mut self.trailed_values,
1147 &mut self.assignments,
1148 &mut self.reason_store,
1149 &mut self.notification_engine,
1150 PropagatorId(0),
1151 )
1152 }
1153}
1154
1155#[cfg(test)]
1156mod tests {
1157 use crate::conjunction;
1158 use crate::containers::StorageKey;
1159 use crate::declare_inference_label;
1160 use crate::predicate;
1161 use crate::proof::InferenceCode;
1162 use crate::state::CurrentNogood;
1163 use crate::state::PropagatorId;
1164 use crate::state::State;
1165
1166 declare_inference_label!(TestLabel);
1167
1168 #[test]
1169 fn reason_correct_after_creation_variable() {
1170 let mut state = State::default();
1171
1172 let y = state.new_interval_variable(0, 10, None);
1173 let x = state.new_interval_variable(0, 10, None);
1174
1175 let tag = state.new_constraint_tag();
1176 let result = state.post_with_reason(
1177 predicate!(x >= 5),
1178 conjunction!([y >= 5]),
1179 InferenceCode::new(tag, TestLabel),
1180 PropagatorId::create_from_index(0),
1181 );
1182
1183 assert_eq!(result, Ok(()));
1184
1185 let mut buffer = vec![];
1186 let _ =
1187 state.get_propagation_reason(predicate!(x >= 5), &mut buffer, CurrentNogood::empty());
1188
1189 assert_eq!(buffer, vec![predicate!(y >= 5)])
1190 }
1191}