Skip to main content

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}