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