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