Skip to main content

pumpkin_core/engine/cp/
assignments.rs

1use crate::basic_types::Trail;
2use crate::containers::HashMap;
3use crate::containers::KeyedVec;
4use crate::engine::cp::reason::ReasonRef;
5use crate::engine::notifications::NotificationEngine;
6use crate::engine::predicates::predicate::Predicate;
7use crate::engine::predicates::predicate::PredicateType;
8use crate::engine::variables::DomainGeneratorIterator;
9use crate::engine::variables::DomainId;
10use crate::predicate;
11use crate::pumpkin_assert_eq_moderate;
12use crate::pumpkin_assert_eq_simple;
13use crate::pumpkin_assert_moderate;
14use crate::pumpkin_assert_simple;
15use crate::variables::IntegerVariable;
16
17#[derive(Clone, Debug)]
18pub struct Assignments {
19    pub(crate) trail: Trail<ConstraintProgrammingTrailEntry>,
20    /// The current bounds of the domain. This is a quick lookup of the data stored more verbosely
21    /// in `domains`.
22    bounds: KeyedVec<DomainId, (i32, i32)>,
23    domains: KeyedVec<DomainId, IntegerDomain>,
24    /// The number of values that have been pruned from the domain.
25    pruned_values: u64,
26}
27
28impl Default for Assignments {
29    fn default() -> Self {
30        let mut assignments = Self {
31            trail: Default::default(),
32            bounds: Default::default(),
33            domains: Default::default(),
34            pruned_values: 0,
35        };
36
37        // As a convention, we allocate a dummy domain_id=0, which represents a 0-1 variable that is
38        // assigned to one. We use it to represent predicates that are trivially true.
39        let dummy_variable = assignments.grow(1, 1);
40        assert_eq!(dummy_variable.id(), 0);
41
42        assignments
43    }
44}
45
46#[derive(Clone, Copy, Debug)]
47pub struct EmptyDomain;
48
49impl Assignments {
50    #[allow(unused, reason = "Could be used in the future")]
51    /// Returns all of the holes in the domain which were created at the provided decision level
52    pub(crate) fn get_holes_at_checkpoint(
53        &self,
54        domain_id: DomainId,
55        checkpoint: usize,
56    ) -> impl Iterator<Item = i32> + '_ {
57        self.domains[domain_id].get_holes_at_checkpoint(checkpoint)
58    }
59
60    /// Returns all of the holes in the domain which were created at the current decision level
61    pub(crate) fn get_holes_at_current_checkpoint(
62        &self,
63        domain_id: DomainId,
64    ) -> impl Iterator<Item = i32> + '_ {
65        self.domains[domain_id].get_holes_from_current_checkpoint(self.get_checkpoint())
66    }
67
68    /// Returns all of the holes (currently) in the domain of `var` (including ones which were
69    /// created at previous decision levels).
70    pub(crate) fn get_holes(&self, domain_id: DomainId) -> impl Iterator<Item = i32> + '_ {
71        self.domains[domain_id].get_holes()
72    }
73
74    pub(crate) fn new_checkpoint(&mut self) {
75        self.trail.new_checkpoint()
76    }
77
78    pub(crate) fn find_last_decision(&self) -> Option<Predicate> {
79        if self.get_checkpoint() == 0 {
80            None
81        } else {
82            let values_at_current_checkpoint =
83                self.trail.values_at_checkpoint(self.get_checkpoint());
84            let entry = &values_at_current_checkpoint[0];
85            pumpkin_assert_eq_simple!(None, entry.reason);
86
87            Some(entry.predicate)
88        }
89    }
90
91    pub(crate) fn get_checkpoint(&self) -> usize {
92        self.trail.get_checkpoint()
93    }
94
95    pub(crate) fn num_domains(&self) -> u32 {
96        self.domains.len() as u32
97    }
98
99    pub(crate) fn get_domains(&self) -> DomainGeneratorIterator {
100        // todo: we use 1 here to prevent the always true literal from ending up in the blocking
101        // clause
102        DomainGeneratorIterator::new(1, self.num_domains())
103    }
104
105    pub(crate) fn num_trail_entries(&self) -> usize {
106        self.trail.len()
107    }
108
109    pub(crate) fn get_trail_entry(&self, index: usize) -> ConstraintProgrammingTrailEntry {
110        self.trail[index].clone()
111    }
112
113    // registers the domain of a new integer variable
114    // note that this is an internal method that does _not_ allocate additional information
115    // necessary for the solver apart from the domain when creating a new integer variable, use
116    // create_new_domain_id in the ConstraintSatisfactionSolver
117    pub(crate) fn grow(&mut self, lower_bound: i32, upper_bound: i32) -> DomainId {
118        // This is necessary for the metric that maintains relative domain size. It is only updated
119        // when values are removed at levels beyond the root, and then it becomes a tricky value to
120        // update when a fresh domain needs to be considered.
121        pumpkin_assert_simple!(
122            self.get_checkpoint() == 0,
123            "can only create variables at the root"
124        );
125
126        let id = DomainId::new(self.num_domains());
127
128        let lower_bound_position = self.trail.len();
129        self.trail.push(ConstraintProgrammingTrailEntry {
130            predicate: predicate!(id >= lower_bound),
131            old_lower_bound: lower_bound,
132            old_upper_bound: upper_bound,
133            reason: None,
134        });
135        let upper_bound_position = self.trail.len();
136        self.trail.push(ConstraintProgrammingTrailEntry {
137            predicate: predicate!(id <= upper_bound),
138            old_lower_bound: lower_bound,
139            old_upper_bound: upper_bound,
140            reason: None,
141        });
142
143        let _ = self.domains.push(IntegerDomain::new(
144            lower_bound,
145            lower_bound_position,
146            upper_bound,
147            upper_bound_position,
148            id,
149        ));
150
151        let _ = self.bounds.push((lower_bound, upper_bound));
152
153        id
154    }
155    pub fn create_new_integer_variable_sparse(&mut self, mut values: Vec<i32>) -> DomainId {
156        assert!(
157            !values.is_empty(),
158            "cannot create a variable with an empty domain"
159        );
160
161        values.sort();
162        values.dedup();
163
164        let lower_bound = values[0];
165        let upper_bound = values[values.len() - 1];
166
167        let domain_id = self.grow(lower_bound, upper_bound);
168
169        let mut next_idx = 0;
170        for value in lower_bound..=upper_bound {
171            if value == values[next_idx] {
172                next_idx += 1;
173            } else {
174                let _ = self
175                    .remove_value_from_domain(domain_id, value, None)
176                    .expect("the domain should not be empty");
177
178                self.domains[domain_id].initial_holes.push(value);
179            }
180        }
181        self.domains[domain_id].initial_bounds_below_trail = self.trail.len() - 1;
182        pumpkin_assert_simple!(
183            next_idx == values.len(),
184            "Expected all values to have been processed"
185        );
186
187        self.update_bounds_snapshot(domain_id);
188
189        domain_id
190    }
191
192    pub(crate) fn debug_create_empty_clone(&self) -> Self {
193        let mut new_assignment = Assignments::default();
194
195        // Skip the dummy variable that is already created in `Assignments::default`.
196        for domain_id in self.domains.keys().skip(1) {
197            let lower_bound = self.get_initial_lower_bound(domain_id);
198            let upper_bound = self.get_initial_upper_bound(domain_id);
199            let holes = self.get_initial_holes(domain_id);
200
201            let new_domain_id = new_assignment.grow(lower_bound, upper_bound);
202            assert_eq!(new_domain_id, domain_id);
203
204            for hole in holes {
205                let changed_domain = new_assignment
206                    .remove_value_from_domain(domain_id, hole, None)
207                    .expect("initial domain cannot be empty");
208
209                assert!(changed_domain);
210            }
211
212            new_assignment.domains[new_domain_id].initial_bounds_below_trail =
213                new_assignment.trail.len() - 1;
214        }
215
216        new_assignment
217    }
218
219    pub(crate) fn is_initial_bound(&self, predicate: Predicate) -> bool {
220        let domain_id = predicate.get_domain();
221
222        let Some(trail_position) = self.get_trail_position(&predicate) else {
223            return false;
224        };
225
226        trail_position <= self.domains[domain_id].initial_bounds_below_trail
227    }
228}
229
230// methods for getting info about the domains
231impl Assignments {
232    pub(crate) fn get_lower_bound(&self, domain_id: DomainId) -> i32 {
233        let (lower_bound, _) = self.bounds[domain_id];
234
235        pumpkin_assert_eq_moderate!(
236            lower_bound,
237            self.domains[domain_id].lower_bound(),
238            "bounds for {domain_id} out of sync"
239        );
240
241        lower_bound
242    }
243
244    pub(crate) fn get_lower_bound_at_trail_position(
245        &self,
246        domain_id: DomainId,
247        trail_position: usize,
248    ) -> i32 {
249        self.domains[domain_id].lower_bound_at_trail_position(trail_position)
250    }
251
252    pub(crate) fn get_upper_bound(&self, domain_id: DomainId) -> i32 {
253        let (_, upper_bound) = self.bounds[domain_id];
254
255        pumpkin_assert_eq_moderate!(
256            upper_bound,
257            self.domains[domain_id].upper_bound(),
258            "bounds for {domain_id} out of sync"
259        );
260
261        upper_bound
262    }
263
264    pub(crate) fn get_upper_bound_at_trail_position(
265        &self,
266        domain_id: DomainId,
267        trail_position: usize,
268    ) -> i32 {
269        self.domains[domain_id].upper_bound_at_trail_position(trail_position)
270    }
271
272    pub(crate) fn get_initial_lower_bound(&self, domain_id: DomainId) -> i32 {
273        self.domains[domain_id].initial_lower_bound()
274    }
275
276    pub(crate) fn get_initial_upper_bound(&self, domain_id: DomainId) -> i32 {
277        self.domains[domain_id].initial_upper_bound()
278    }
279
280    pub(crate) fn get_initial_holes(&self, domain_id: DomainId) -> Vec<i32> {
281        self.domains[domain_id].initial_holes.clone()
282    }
283
284    pub(crate) fn get_assigned_value<Var: IntegerVariable>(&self, var: &Var) -> Option<i32> {
285        self.is_domain_assigned(var).then(|| var.lower_bound(self))
286    }
287
288    pub(crate) fn is_decision_predicate(&self, predicate: &Predicate) -> bool {
289        let domain = predicate.get_domain();
290        if let Some(trail_position) = self.get_trail_position(predicate)
291            && trail_position > self.domains[domain].initial_bounds_below_trail
292        {
293            self.trail[trail_position].reason.is_none()
294                && self.trail[trail_position].predicate == *predicate
295        } else {
296            false
297        }
298    }
299
300    pub(crate) fn get_domain_iterator(&self, domain_id: DomainId) -> IntegerDomainIterator<'_> {
301        self.domains[domain_id].domain_iterator()
302    }
303
304    /// Returns the conjunction of predicates that define the domain.
305    /// Root level predicates are ignored.
306    pub(crate) fn get_domain_description(&self, domain_id: DomainId) -> Vec<Predicate> {
307        let mut predicates = Vec::new();
308        let domain = &self.domains[domain_id];
309
310        // If the domain assigned at a nonroot level, this is just one predicate.
311        if domain.lower_bound() == domain.upper_bound()
312            && domain.lower_bound_checkpoint() > 0
313            && domain.checkpoint() > 0
314        {
315            predicates.push(predicate![domain_id == domain.lower_bound()]);
316            return predicates;
317        }
318
319        // Add bounds but avoid root assignments.
320        if domain.lower_bound_checkpoint() > 0 {
321            predicates.push(predicate![domain_id >= domain.lower_bound()]);
322        }
323
324        if domain.checkpoint() > 0 {
325            predicates.push(predicate![domain_id <= domain.upper_bound()]);
326        }
327
328        // Add holes.
329        for hole in &self.domains[domain_id].holes {
330            // Only record holes that are within the lower and upper bound,
331            // that are not root assignments.
332            // Since bound values cannot be in the holes,
333            // we can use '<' or '>'.
334            if hole.1.checkpoint > 0
335                && domain.lower_bound() < *hole.0
336                && *hole.0 < domain.upper_bound()
337            {
338                predicates.push(predicate![domain_id != *hole.0]);
339            }
340        }
341        predicates
342    }
343
344    pub(crate) fn is_value_in_domain(&self, domain_id: DomainId, value: i32) -> bool {
345        let (lower_bound, upper_bound) = self.bounds[domain_id];
346
347        if value < lower_bound || value > upper_bound {
348            return false;
349        }
350
351        let domain = &self.domains[domain_id];
352        domain.contains(value)
353    }
354
355    pub(crate) fn is_value_in_domain_at_trail_position(
356        &self,
357        domain_id: DomainId,
358        value: i32,
359        trail_position: usize,
360    ) -> bool {
361        self.domains[domain_id].contains_at_trail_position(value, trail_position)
362    }
363
364    pub(crate) fn is_domain_assigned<Var: IntegerVariable>(&self, var: &Var) -> bool {
365        var.lower_bound(self) == var.upper_bound(self)
366    }
367
368    /// Returns the index of the trail entry at which point the given predicate became true.
369    /// In case the predicate is not true, then the function returns None.
370    /// Note that it is not necessary for the predicate to be explicitly present on the trail,
371    /// e.g., if [x >= 10] is explicitly present on the trail but not [x >= 6], then the
372    /// trail position for [x >= 10] will be returned for the case [x >= 6].
373    pub(crate) fn get_trail_position(&self, predicate: &Predicate) -> Option<usize> {
374        self.domains[predicate.get_domain()]
375            .get_update_info(predicate)
376            .map(|u| u.trail_position)
377    }
378
379    /// If the predicate is assigned true, returns the decision level of the predicate.
380    /// Otherwise returns None.
381    pub(crate) fn get_checkpoint_for_predicate(&self, predicate: &Predicate) -> Option<usize> {
382        self.domains[predicate.get_domain()]
383            .get_update_info(predicate)
384            .map(|u| u.checkpoint)
385    }
386
387    pub fn get_domain_descriptions(&self) -> Vec<Predicate> {
388        let mut descriptions: Vec<Predicate> = vec![];
389        for domain in self.domains.iter().enumerate() {
390            let domain_id = DomainId::new(domain.0 as u32);
391            descriptions.append(&mut self.get_domain_description(domain_id));
392        }
393        descriptions
394    }
395}
396
397// methods to change the domains
398impl Assignments {
399    fn tighten_lower_bound(
400        &mut self,
401        domain_id: DomainId,
402        new_lower_bound: i32,
403        reason: Option<ReasonRef>,
404    ) -> Result<bool, EmptyDomain> {
405        // No need to do any changes if the new lower bound is weaker.
406        if new_lower_bound <= self.get_lower_bound(domain_id) {
407            return self.domains[domain_id].verify_consistency();
408        }
409
410        let predicate = predicate!(domain_id >= new_lower_bound);
411
412        let old_lower_bound = self.get_lower_bound(domain_id);
413        let old_upper_bound = self.get_upper_bound(domain_id);
414
415        // important to record trail position _before_ pushing to the trail
416        let trail_position = self.trail.len();
417
418        self.trail.push(ConstraintProgrammingTrailEntry {
419            predicate,
420            old_lower_bound,
421            old_upper_bound,
422            reason,
423        });
424
425        let checkpoint = self.get_checkpoint();
426        let domain = &mut self.domains[domain_id];
427
428        let update_took_place = domain.set_lower_bound(new_lower_bound, checkpoint, trail_position);
429
430        self.bounds[domain_id].0 = domain.lower_bound();
431
432        self.pruned_values += domain.lower_bound().abs_diff(old_lower_bound) as u64;
433
434        let _ = domain.verify_consistency()?;
435
436        Ok(update_took_place)
437    }
438
439    fn tighten_upper_bound(
440        &mut self,
441        domain_id: DomainId,
442        new_upper_bound: i32,
443        reason: Option<ReasonRef>,
444    ) -> Result<bool, EmptyDomain> {
445        // No need to do any changes if the new upper bound is weaker.
446        if new_upper_bound >= self.get_upper_bound(domain_id) {
447            return self.domains[domain_id].verify_consistency();
448        }
449
450        let predicate = predicate!(domain_id <= new_upper_bound);
451
452        let old_lower_bound = self.get_lower_bound(domain_id);
453        let old_upper_bound = self.get_upper_bound(domain_id);
454
455        // important to record trail position _before_ pushing to the trail
456        let trail_position = self.trail.len();
457
458        self.trail.push(ConstraintProgrammingTrailEntry {
459            predicate,
460            old_lower_bound,
461            old_upper_bound,
462            reason,
463        });
464
465        let checkpoint = self.get_checkpoint();
466        let domain = &mut self.domains[domain_id];
467
468        let update_took_place = domain.set_upper_bound(new_upper_bound, checkpoint, trail_position);
469
470        self.bounds[domain_id].1 = domain.upper_bound();
471
472        self.pruned_values += old_upper_bound.abs_diff(domain.upper_bound()) as u64;
473
474        let _ = domain.verify_consistency()?;
475
476        Ok(update_took_place)
477    }
478
479    fn make_assignment(
480        &mut self,
481        domain_id: DomainId,
482        assigned_value: i32,
483        reason: Option<ReasonRef>,
484    ) -> Result<bool, EmptyDomain> {
485        let mut update_took_place = false;
486
487        let predicate = predicate!(domain_id == assigned_value);
488
489        let old_lower_bound = self.get_lower_bound(domain_id);
490        let old_upper_bound = self.get_upper_bound(domain_id);
491
492        if old_lower_bound == assigned_value && old_upper_bound == assigned_value {
493            return self.domains[domain_id].verify_consistency();
494        }
495
496        // important to record trail position _before_ pushing to the trail
497        let trail_position = self.trail.len();
498
499        self.trail.push(ConstraintProgrammingTrailEntry {
500            predicate,
501            old_lower_bound,
502            old_upper_bound,
503            reason,
504        });
505
506        let checkpoint = self.get_checkpoint();
507        let domain = &mut self.domains[domain_id];
508
509        if old_lower_bound < assigned_value {
510            update_took_place |= domain.set_lower_bound(assigned_value, checkpoint, trail_position);
511            self.bounds[domain_id].0 = domain.lower_bound();
512            self.pruned_values += domain.lower_bound().abs_diff(old_lower_bound) as u64;
513        }
514
515        if old_upper_bound > assigned_value {
516            update_took_place |= domain.set_upper_bound(assigned_value, checkpoint, trail_position);
517            self.bounds[domain_id].1 = domain.upper_bound();
518            self.pruned_values += domain.upper_bound().abs_diff(old_upper_bound) as u64;
519        }
520
521        let _ = self.domains[domain_id].verify_consistency()?;
522
523        Ok(update_took_place)
524    }
525
526    fn remove_value_from_domain(
527        &mut self,
528        domain_id: DomainId,
529        removed_value_from_domain: i32,
530        reason: Option<ReasonRef>,
531    ) -> Result<bool, EmptyDomain> {
532        // No need to do any changes if the value is not present anyway.
533        if !self.domains[domain_id].contains(removed_value_from_domain) {
534            return self.domains[domain_id].verify_consistency();
535        }
536
537        let predicate = predicate!(domain_id != removed_value_from_domain);
538
539        let old_lower_bound = self.get_lower_bound(domain_id);
540        let old_upper_bound = self.get_upper_bound(domain_id);
541
542        // important to record trail position _before_ pushing to the trail
543        let trail_position = self.trail.len();
544
545        self.trail.push(ConstraintProgrammingTrailEntry {
546            predicate,
547            old_lower_bound,
548            old_upper_bound,
549            reason,
550        });
551
552        let checkpoint = self.get_checkpoint();
553        let domain = &mut self.domains[domain_id];
554
555        let _ = domain.remove_value(removed_value_from_domain, checkpoint, trail_position);
556
557        let changed_lower_bound = domain.lower_bound().abs_diff(old_lower_bound) as u64;
558        let changed_upper_bound = old_upper_bound.abs_diff(domain.upper_bound()) as u64;
559
560        if changed_lower_bound + changed_upper_bound > 0 {
561            self.pruned_values += changed_upper_bound + changed_lower_bound;
562        } else {
563            self.pruned_values += 1;
564        }
565
566        self.update_bounds_snapshot(domain_id);
567        let _ = self.domains[domain_id].verify_consistency()?;
568
569        Ok(true)
570    }
571
572    /// Apply the given [`Predicate`] to the integer domains.
573    ///
574    /// In case where the [`Predicate`] is already true, this does nothing and will
575    /// return `false`. If the predicate was unassigned and became true, then `true`
576    /// is returned. If instead applying the [`Predicate`] leads to an
577    /// [`EmptyDomain`], the error variant is returned.
578    pub(crate) fn post_predicate(
579        &mut self,
580        predicate: Predicate,
581        reason: Option<ReasonRef>,
582        notification_engine: &mut NotificationEngine,
583    ) -> Result<bool, EmptyDomain> {
584        let (lower_bound_before, upper_bound_before) = self.bounds[predicate.get_domain()];
585
586        let mut removal_took_place = false;
587
588        let domain_id = predicate.get_domain();
589        let value = predicate.get_right_hand_side();
590
591        let update_took_place = match predicate.get_predicate_type() {
592            PredicateType::LowerBound => self.tighten_lower_bound(domain_id, value, reason)?,
593            PredicateType::UpperBound => self.tighten_upper_bound(domain_id, value, reason)?,
594            PredicateType::NotEqual => {
595                removal_took_place = self.remove_value_from_domain(domain_id, value, reason)?;
596                removal_took_place
597            }
598            PredicateType::Equal => self.make_assignment(domain_id, value, reason)?,
599        };
600
601        if update_took_place {
602            notification_engine.event_occurred(
603                lower_bound_before,
604                upper_bound_before,
605                self.domains[predicate.get_domain()].lower_bound(),
606                self.domains[predicate.get_domain()].upper_bound(),
607                removal_took_place,
608                predicate.get_domain(),
609            );
610        }
611
612        Ok(update_took_place)
613    }
614
615    /// Determines whether the provided [`Predicate`] holds in the current state of the
616    /// [`Assignments`]. In case the predicate is not assigned yet (neither true nor false),
617    /// returns None.
618    pub(crate) fn evaluate_predicate(&self, predicate: Predicate) -> Option<bool> {
619        let domain_id = predicate.get_domain();
620        let value = predicate.get_right_hand_side();
621
622        match predicate.get_predicate_type() {
623            PredicateType::LowerBound => {
624                if self.get_lower_bound(domain_id) >= value {
625                    Some(true)
626                } else if self.get_upper_bound(domain_id) < value {
627                    Some(false)
628                } else {
629                    None
630                }
631            }
632            PredicateType::UpperBound => {
633                if self.get_upper_bound(domain_id) <= value {
634                    Some(true)
635                } else if self.get_lower_bound(domain_id) > value {
636                    Some(false)
637                } else {
638                    None
639                }
640            }
641            PredicateType::NotEqual => {
642                if !self.is_value_in_domain(domain_id, value) {
643                    Some(true)
644                } else if let Some(assigned_value) = self.get_assigned_value(&domain_id) {
645                    // Previous branch concluded the value is not in the domain, so if the variable
646                    // is assigned, then it is assigned to the not equals value.
647                    pumpkin_assert_simple!(assigned_value == value);
648                    Some(false)
649                } else {
650                    None
651                }
652            }
653            PredicateType::Equal => {
654                if !self.is_value_in_domain(domain_id, value) {
655                    Some(false)
656                } else if let Some(assigned_value) = self.get_assigned_value(&domain_id) {
657                    pumpkin_assert_moderate!(assigned_value == value);
658                    Some(true)
659                } else {
660                    None
661                }
662            }
663        }
664    }
665
666    pub(crate) fn is_predicate_satisfied(&self, predicate: Predicate) -> bool {
667        self.evaluate_predicate(predicate)
668            .is_some_and(|truth_value| truth_value)
669    }
670
671    #[allow(unused, reason = "makes sense to have in this API")]
672    pub(crate) fn is_predicate_falsified(&self, predicate: Predicate) -> bool {
673        self.evaluate_predicate(predicate)
674            .is_some_and(|truth_value| !truth_value)
675    }
676
677    /// Synchronises the internal structures of [`Assignments`] based on the fact that
678    /// backtracking to `new_checkpoint` is taking place. This method returns the list of
679    /// [`DomainId`]s and their values which were fixed (i.e. domain of size one) before
680    /// backtracking and are unfixed (i.e. domain of two or more values) after synchronisation.
681    pub(crate) fn synchronise(
682        &mut self,
683        new_checkpoint: usize,
684        notification_engine: &mut NotificationEngine,
685    ) -> Vec<(DomainId, i32)> {
686        let mut unfixed_variables = Vec::new();
687        let num_trail_entries_before_synchronisation = self.num_trail_entries();
688
689        pumpkin_assert_simple!(
690            new_checkpoint <= self.trail.get_checkpoint(),
691            "Expected the new decision level {new_checkpoint} to be less than or equal to the current decision level {}",
692            self.trail.get_checkpoint(),
693        );
694
695        self.trail
696            .synchronise(new_checkpoint)
697            .enumerate()
698            .for_each(|(index, entry)| {
699                // Calculate how many values are re-introduced into the domain.
700                let domain_id = entry.predicate.get_domain();
701                let lower_bound_before = self.domains[domain_id].lower_bound();
702                let upper_bound_before = self.domains[domain_id].upper_bound();
703
704                let trail_index = num_trail_entries_before_synchronisation - index - 1;
705
706                let add_on_upper_bound = entry.old_upper_bound.abs_diff(upper_bound_before) as u64;
707                let add_on_lower_bound = entry.old_lower_bound.abs_diff(lower_bound_before) as u64;
708                self.pruned_values -= add_on_upper_bound + add_on_lower_bound;
709
710                if entry.predicate.is_not_equal_predicate()
711                    && add_on_lower_bound + add_on_upper_bound == 0
712                {
713                    self.pruned_values -= 1;
714                }
715
716                let fixed_before =
717                    self.domains[domain_id].lower_bound() == self.domains[domain_id].upper_bound();
718                self.domains[domain_id].undo_trail_entry(&entry);
719
720                let new_lower_bound = self.domains[domain_id].lower_bound();
721                let new_upper_bound = self.domains[domain_id].upper_bound();
722                self.bounds[domain_id] = (new_lower_bound, new_upper_bound);
723
724                notification_engine.undo_trail_entry(
725                    fixed_before,
726                    lower_bound_before,
727                    upper_bound_before,
728                    new_lower_bound,
729                    new_upper_bound,
730                    trail_index,
731                    entry.predicate,
732                );
733
734                if new_lower_bound != new_upper_bound {
735                    // Variable used to be fixed but is not after backtracking
736                    unfixed_variables.push((domain_id, lower_bound_before));
737                }
738            });
739
740        // Drain does not remove the events from the internal data structure. Elements are removed
741        // lazily, as the iterator gets executed. For this reason we go through the entire iterator.
742        notification_engine.clear_events();
743
744        unfixed_variables
745    }
746
747    /// todo: This is a temporary hack, not to be used in general.
748    pub(crate) fn remove_last_trail_element(&mut self) -> (Predicate, ReasonRef) {
749        let entry = self.trail.pop().unwrap();
750        let domain_id = entry.predicate.get_domain();
751        self.domains[domain_id].undo_trail_entry(&entry);
752        self.update_bounds_snapshot(domain_id);
753
754        let reason_ref = entry.reason.unwrap();
755
756        (entry.predicate, reason_ref)
757    }
758
759    /// Get the number of values pruned from all the domains.
760    pub(crate) fn get_pruned_value_count(&self) -> u64 {
761        self.pruned_values
762    }
763
764    fn update_bounds_snapshot(&mut self, domain_id: DomainId) {
765        self.bounds[domain_id] = (
766            self.domains[domain_id].lower_bound(),
767            self.domains[domain_id].upper_bound(),
768        );
769    }
770}
771
772impl Assignments {
773    #[deprecated]
774    pub(crate) fn get_reason_for_predicate_brute_force(&self, predicate: Predicate) -> ReasonRef {
775        self.trail
776            .iter()
777            .find_map(|entry| {
778                if entry.predicate == predicate {
779                    entry.reason
780                } else {
781                    None
782                }
783            })
784            .unwrap_or_else(|| panic!("could not find a reason for predicate {predicate}"))
785    }
786}
787
788#[derive(Clone, Debug)]
789pub(crate) struct ConstraintProgrammingTrailEntry {
790    pub predicate: Predicate,
791    /// Explicitly store the bound before the predicate was applied so that it is easier later on
792    ///  to update the bounds when backtracking.
793    pub(crate) old_lower_bound: i32,
794    pub(crate) old_upper_bound: i32,
795    /// Stores the a reference to the reason in the `ReasonStore`, only makes sense if a
796    /// propagation  took place, e.g., does _not_ make sense in the case of a decision or if
797    /// the update was due  to synchronisation from the propositional trail.
798    pub(crate) reason: Option<ReasonRef>,
799}
800
801#[derive(Clone, Copy, Debug)]
802struct PairDecisionLevelTrailPosition {
803    checkpoint: usize,
804    trail_position: usize,
805}
806
807#[derive(Clone, Debug)]
808struct BoundUpdateInfo {
809    bound: i32,
810    checkpoint: usize,
811    trail_position: usize,
812}
813
814#[derive(Clone, Debug)]
815struct HoleUpdateInfo {
816    removed_value: i32,
817
818    checkpoint: usize,
819
820    triggered_lower_bound_update: bool,
821    triggered_upper_bound_update: bool,
822}
823
824/// This is the CP representation of a domain. It stores the bounds alongside holes in the domain.
825/// When the domain is in an empty state, `lower_bound > upper_bound`.
826/// The domain tracks all domain changes, so it is possible to query the domain at a given
827/// cp trail position, i.e., the domain at some previous point in time.
828/// This is needed to support lazy explanations.
829#[derive(Clone, Debug)]
830struct IntegerDomain {
831    id: DomainId,
832    /// The 'updates' fields chronologically records the changes to the domain.
833    lower_bound_updates: Vec<BoundUpdateInfo>,
834    upper_bound_updates: Vec<BoundUpdateInfo>,
835    hole_updates: Vec<HoleUpdateInfo>,
836    /// Auxiliary data structure to make it easy to check if a value is present or not.
837    /// This is done to avoid going through 'hole_updates'.
838    /// It maps a removed value with its decision level and trail position.
839    /// In the future we could consider using direct hashing if the domain is small.
840    holes: HashMap<i32, PairDecisionLevelTrailPosition>,
841    // Records the trail entry at which all of the root bounds are true
842    initial_bounds_below_trail: usize,
843    /// The holes that exist in the input problem.
844    initial_holes: Vec<i32>,
845}
846
847impl IntegerDomain {
848    fn new(
849        lower_bound: i32,
850        lower_bound_position: usize,
851        upper_bound: i32,
852        upper_bound_position: usize,
853        id: DomainId,
854    ) -> IntegerDomain {
855        pumpkin_assert_simple!(lower_bound <= upper_bound, "Cannot create an empty domain.");
856
857        let lower_bound_updates = vec![BoundUpdateInfo {
858            bound: lower_bound,
859            checkpoint: 0,
860            trail_position: lower_bound_position,
861        }];
862
863        let upper_bound_updates = vec![BoundUpdateInfo {
864            bound: upper_bound,
865            checkpoint: 0,
866            trail_position: upper_bound_position,
867        }];
868
869        IntegerDomain {
870            id,
871            initial_holes: vec![],
872            lower_bound_updates,
873            upper_bound_updates,
874            hole_updates: vec![],
875            holes: Default::default(),
876            initial_bounds_below_trail: std::cmp::max(lower_bound_position, upper_bound_position),
877        }
878    }
879
880    fn lower_bound(&self) -> i32 {
881        // the last entry contains the current lower bound
882        self.lower_bound_updates
883            .last()
884            .expect("Cannot be empty.")
885            .bound
886    }
887
888    fn lower_bound_checkpoint(&self) -> usize {
889        self.lower_bound_updates
890            .last()
891            .expect("Cannot be empty.")
892            .checkpoint
893    }
894
895    fn initial_lower_bound(&self) -> i32 {
896        // the first entry is never removed,
897        // and contains the bound that was assigned upon creation
898        self.lower_bound_updates[0].bound
899    }
900
901    fn lower_bound_at_trail_position(&self, trail_position: usize) -> i32 {
902        // TODO: could possibly cache old queries, and maybe even first checking large/small trail
903        // position values (in case those are commonly used)
904
905        // We find the update with the largest trail position such that it is smaller than or equal
906        // to the input trail position
907        //
908        // Recall that by the nature of the updates, the updates are stored in increasing order of
909        // trail position.
910        //
911        // We find the first index such that `u.trail_position > trail_position` and then we
912        // subtract 1 from that
913        let index = self
914            .lower_bound_updates
915            .partition_point(|u| u.trail_position <= trail_position);
916
917        self.lower_bound_updates[index.saturating_sub(1)].bound
918    }
919
920    fn upper_bound(&self) -> i32 {
921        // the last entry contains the current upper bound
922        self.upper_bound_updates
923            .last()
924            .expect("Cannot be empty.")
925            .bound
926    }
927
928    fn checkpoint(&self) -> usize {
929        self.upper_bound_updates
930            .last()
931            .expect("Cannot be empty.")
932            .checkpoint
933    }
934
935    fn initial_upper_bound(&self) -> i32 {
936        // the first entry is never removed,
937        // and contains the bound that was assigned upon creation
938        self.upper_bound_updates[0].bound
939    }
940
941    fn upper_bound_at_trail_position(&self, trail_position: usize) -> i32 {
942        // TODO: could possibly cache old queries, and maybe even first checking large/small trail
943        // position values (in case those are commonly used)
944
945        // We find the update with the largest trail position such that it is smaller than or equal
946        // to the input trail position
947        //
948        // Recall that by the nature of the updates, the updates are stored in increasing order of
949        // trail position.
950        //
951        // We find the first index such that `u.trail_position > trail_position` and then we
952        // subtract 1 from that
953        let index = self
954            .upper_bound_updates
955            .partition_point(|u| u.trail_position <= trail_position)
956            .saturating_sub(1);
957
958        self.upper_bound_updates[index].bound
959    }
960
961    fn domain_iterator(&self) -> IntegerDomainIterator<'_> {
962        // Ideally we use into_iter but I did not manage to get it to work,
963        // because the iterator takes a lifelines
964        // (the iterator takes a reference to the domain).
965        // So this will do for now.
966        IntegerDomainIterator::new(self)
967    }
968
969    fn contains(&self, value: i32) -> bool {
970        self.lower_bound() <= value
971            && value <= self.upper_bound()
972            && !self.holes.contains_key(&value)
973    }
974
975    fn contains_at_trail_position(&self, value: i32, trail_position: usize) -> bool {
976        // If the value is out of bounds,
977        // then we can safety say that the value is not in the domain.
978        if self.lower_bound_at_trail_position(trail_position) > value
979            || self.upper_bound_at_trail_position(trail_position) < value
980        {
981            return false;
982        }
983        // Otherwise we need to check if there is a hole with that specific value.
984
985        // In case the hole is made at the given trail position or earlier,
986        // the value is not in the domain.
987        if let Some(hole_info) = self.holes.get(&value)
988            && hole_info.trail_position <= trail_position
989        {
990            return false;
991        }
992
993        // Since none of the previous checks triggered, the value is in the domain.
994        true
995    }
996
997    fn remove_value(
998        &mut self,
999        removed_value: i32,
1000        checkpoint: usize,
1001        trail_position: usize,
1002    ) -> bool {
1003        if removed_value < self.lower_bound()
1004            || removed_value > self.upper_bound()
1005            || self.holes.contains_key(&removed_value)
1006        {
1007            return false;
1008        }
1009
1010        self.hole_updates.push(HoleUpdateInfo {
1011            removed_value,
1012            checkpoint,
1013            triggered_lower_bound_update: false,
1014            triggered_upper_bound_update: false,
1015        });
1016        // Note that it is important to remove the hole now,
1017        // because the later if statements may use the holes.
1018        let old_none_entry = self.holes.insert(
1019            removed_value,
1020            PairDecisionLevelTrailPosition {
1021                checkpoint,
1022                trail_position,
1023            },
1024        );
1025        pumpkin_assert_moderate!(old_none_entry.is_none());
1026
1027        // Check if removing a value triggers a lower bound update.
1028        if self.lower_bound() == removed_value {
1029            let _ = self.set_lower_bound(removed_value + 1, checkpoint, trail_position);
1030            self.hole_updates
1031                .last_mut()
1032                .expect("we just pushed a value, so must be present")
1033                .triggered_lower_bound_update = true;
1034        }
1035        // Check if removing the value triggers an upper bound update.
1036        if self.upper_bound() == removed_value {
1037            let _ = self.set_upper_bound(removed_value - 1, checkpoint, trail_position);
1038            self.hole_updates
1039                .last_mut()
1040                .expect("we just pushed a value, so must be present")
1041                .triggered_upper_bound_update = true;
1042        }
1043
1044        true
1045    }
1046
1047    fn debug_is_valid_upper_bound_domain_update(
1048        &self,
1049        checkpoint: usize,
1050        trail_position: usize,
1051    ) -> bool {
1052        self.upper_bound_updates.last().unwrap().checkpoint <= checkpoint
1053            && self.upper_bound_updates.last().unwrap().trail_position < trail_position
1054    }
1055
1056    fn set_upper_bound(
1057        &mut self,
1058        new_upper_bound: i32,
1059        checkpoint: usize,
1060        trail_position: usize,
1061    ) -> bool {
1062        pumpkin_assert_moderate!(
1063            self.debug_is_valid_upper_bound_domain_update(checkpoint, trail_position)
1064        );
1065
1066        if new_upper_bound >= self.upper_bound() {
1067            return false;
1068        }
1069
1070        self.upper_bound_updates.push(BoundUpdateInfo {
1071            bound: new_upper_bound,
1072            checkpoint,
1073            trail_position,
1074        });
1075        self.update_upper_bound_with_respect_to_holes();
1076
1077        true
1078    }
1079
1080    fn update_upper_bound_with_respect_to_holes(&mut self) {
1081        while self.holes.contains_key(&self.upper_bound())
1082            && self.lower_bound() <= self.upper_bound()
1083        {
1084            self.upper_bound_updates.last_mut().unwrap().bound -= 1;
1085        }
1086    }
1087
1088    fn debug_is_valid_lower_bound_domain_update(
1089        &self,
1090        checkpoint: usize,
1091        trail_position: usize,
1092    ) -> bool {
1093        trail_position == 0
1094            || self.lower_bound_updates.last().unwrap().checkpoint <= checkpoint
1095                && self.lower_bound_updates.last().unwrap().trail_position < trail_position
1096    }
1097
1098    fn set_lower_bound(
1099        &mut self,
1100        new_lower_bound: i32,
1101        checkpoint: usize,
1102        trail_position: usize,
1103    ) -> bool {
1104        pumpkin_assert_moderate!(
1105            self.debug_is_valid_lower_bound_domain_update(checkpoint, trail_position)
1106        );
1107
1108        if new_lower_bound <= self.lower_bound() {
1109            return false;
1110        }
1111
1112        self.lower_bound_updates.push(BoundUpdateInfo {
1113            bound: new_lower_bound,
1114            checkpoint,
1115            trail_position,
1116        });
1117        self.update_lower_bound_with_respect_to_holes();
1118
1119        true
1120    }
1121
1122    fn update_lower_bound_with_respect_to_holes(&mut self) {
1123        while self.holes.contains_key(&self.lower_bound())
1124            && self.lower_bound() <= self.upper_bound()
1125        {
1126            self.lower_bound_updates.last_mut().unwrap().bound += 1;
1127        }
1128    }
1129
1130    fn debug_bounds_check(&self) -> bool {
1131        // If the domain is empty, the lower bound will be greater than the upper bound.
1132        if self.lower_bound() > self.upper_bound() {
1133            true
1134        } else {
1135            self.lower_bound() >= self.initial_lower_bound()
1136                && self.upper_bound() <= self.initial_upper_bound()
1137                && !self.holes.contains_key(&self.lower_bound())
1138                && !self.holes.contains_key(&self.upper_bound())
1139        }
1140    }
1141
1142    fn verify_consistency(&self) -> Result<bool, EmptyDomain> {
1143        if self.lower_bound() > self.upper_bound() {
1144            Err(EmptyDomain)
1145        } else {
1146            Ok(false)
1147        }
1148    }
1149
1150    fn undo_trail_entry(&mut self, entry: &ConstraintProgrammingTrailEntry) {
1151        let domain_id = entry.predicate.get_domain();
1152        match entry.predicate.get_predicate_type() {
1153            PredicateType::LowerBound => {
1154                pumpkin_assert_moderate!(domain_id == self.id);
1155
1156                let _ = self.lower_bound_updates.pop();
1157                pumpkin_assert_moderate!(!self.lower_bound_updates.is_empty());
1158            }
1159            PredicateType::UpperBound => {
1160                pumpkin_assert_moderate!(domain_id == self.id);
1161
1162                let _ = self.upper_bound_updates.pop();
1163                pumpkin_assert_moderate!(!self.upper_bound_updates.is_empty());
1164            }
1165            PredicateType::NotEqual => {
1166                pumpkin_assert_moderate!(domain_id == self.id);
1167
1168                let not_equal_constant = entry.predicate.get_right_hand_side();
1169
1170                let hole_update = self
1171                    .hole_updates
1172                    .pop()
1173                    .expect("Must have record of domain removal.");
1174                pumpkin_assert_moderate!(hole_update.removed_value == not_equal_constant);
1175
1176                let _ = self
1177                    .holes
1178                    .remove(&not_equal_constant)
1179                    .expect("Must be present.");
1180
1181                if hole_update.triggered_lower_bound_update {
1182                    let _ = self.lower_bound_updates.pop();
1183                    pumpkin_assert_moderate!(!self.lower_bound_updates.is_empty());
1184                }
1185
1186                if hole_update.triggered_upper_bound_update {
1187                    let _ = self.upper_bound_updates.pop();
1188                    pumpkin_assert_moderate!(!self.upper_bound_updates.is_empty());
1189                }
1190            }
1191            PredicateType::Equal => {
1192                let lower_bound_update = self.lower_bound_updates.last().unwrap();
1193                let upper_bound_update = self.upper_bound_updates.last().unwrap();
1194
1195                if lower_bound_update.trail_position > upper_bound_update.trail_position {
1196                    let _ = self.lower_bound_updates.pop();
1197                } else if upper_bound_update.trail_position > lower_bound_update.trail_position {
1198                    let _ = self.upper_bound_updates.pop();
1199                } else {
1200                    let _ = self.lower_bound_updates.pop();
1201                    let _ = self.upper_bound_updates.pop();
1202                }
1203            }
1204        };
1205
1206        // these asserts will be removed, for now it is a sanity check
1207        // later we may remove the old bound from the trail entry since it is not needed
1208        pumpkin_assert_eq_simple!(self.lower_bound(), entry.old_lower_bound);
1209        pumpkin_assert_eq_simple!(self.upper_bound(), entry.old_upper_bound);
1210
1211        pumpkin_assert_moderate!(self.debug_bounds_check());
1212    }
1213
1214    fn get_update_info(&self, predicate: &Predicate) -> Option<PairDecisionLevelTrailPosition> {
1215        // Perhaps the recursion could be done in a cleaner way,
1216        // e.g., separate functions dependibng on the type of predicate.
1217        // For the initial version, the current version is okay.
1218        let domain_id = predicate.get_domain();
1219        let value = predicate.get_right_hand_side();
1220
1221        match predicate.get_predicate_type() {
1222            PredicateType::LowerBound => {
1223                // Recall that by the nature of the updates,
1224                // the updates are stored in increasing order of the lower bound.
1225
1226                // find the update with smallest lower bound
1227                // that is greater than or equal to the input lower bound
1228                let position = self
1229                    .lower_bound_updates
1230                    .partition_point(|u| u.bound < value);
1231
1232                (position < self.lower_bound_updates.len()).then(|| {
1233                    let u = &self.lower_bound_updates[position];
1234                    PairDecisionLevelTrailPosition {
1235                        checkpoint: u.checkpoint,
1236                        trail_position: u.trail_position,
1237                    }
1238                })
1239            }
1240            PredicateType::UpperBound => {
1241                // Recall that by the nature of the updates,
1242                // the updates are stored in decreasing order of the upper bound.
1243
1244                // find the update with greatest upper bound
1245                // that is smaller than or equal to the input upper bound
1246                let position = self
1247                    .upper_bound_updates
1248                    .partition_point(|u| u.bound > value);
1249
1250                (position < self.upper_bound_updates.len()).then(|| {
1251                    let u = &self.upper_bound_updates[position];
1252                    PairDecisionLevelTrailPosition {
1253                        checkpoint: u.checkpoint,
1254                        trail_position: u.trail_position,
1255                    }
1256                })
1257            }
1258            PredicateType::NotEqual => {
1259                // Check the explictly stored holes.
1260                // If the value has been removed explicitly,
1261                // then the stored time is the first time the value was removed.
1262                if let Some(hole_info) = self.holes.get(&value) {
1263                    Some(*hole_info)
1264                } else {
1265                    // Otherwise, check the case when the lower/upper bound surpassed the value.
1266                    // If this never happened, then report that the predicate is not true.
1267
1268                    // Note that it cannot be that both the lower bound and upper bound surpassed
1269                    // the not equals constant, i.e., at most one of the two may happen.
1270                    // So we can stop as soon as we find one of the two.
1271
1272                    // Check the lower bound first.
1273                    if let Some(trail_position) =
1274                        self.get_update_info(&predicate!(domain_id >= value + 1))
1275                    {
1276                        // The lower bound removed the value from the domain,
1277                        // report the trail position of the lower bound.
1278                        Some(trail_position)
1279                    } else {
1280                        // The lower bound did not surpass the value,
1281                        // now check the upper bound.
1282                        self.get_update_info(&predicate!(domain_id <= value - 1))
1283                    }
1284                }
1285            }
1286            PredicateType::Equal => {
1287                // For equality to hold, both the lower and upper bound predicates must hold.
1288                // Check lower bound first.
1289                if let Some(lb_trail_position) =
1290                    self.get_update_info(&predicate!(domain_id >= value))
1291                {
1292                    // The lower bound found,
1293                    // now the check depends on the upper bound.
1294
1295                    // If both the lower and upper bounds are present,
1296                    // report the trail position of the bound that was set last.
1297                    // Otherwise, return that the predicate is not on the trail.
1298                    self.get_update_info(&predicate!(domain_id <= value))
1299                        .map(|ub_trail_position| {
1300                            if lb_trail_position.trail_position > ub_trail_position.trail_position {
1301                                lb_trail_position
1302                            } else {
1303                                ub_trail_position
1304                            }
1305                        })
1306                }
1307                // If the lower bound is never reached,
1308                // then surely the equality predicate cannot be true.
1309                else {
1310                    None
1311                }
1312            }
1313        }
1314    }
1315
1316    /// Returns the holes which were created on the provided decision level.
1317    pub(crate) fn get_holes_at_checkpoint(
1318        &self,
1319        checkpoint: usize,
1320    ) -> impl Iterator<Item = i32> + '_ {
1321        self.hole_updates
1322            .iter()
1323            .filter(move |entry| entry.checkpoint == checkpoint)
1324            .map(|entry| entry.removed_value)
1325    }
1326
1327    /// Returns the holes which were created on the current decision level.
1328    pub(crate) fn get_holes_from_current_checkpoint(
1329        &self,
1330        current_checkpoint: usize,
1331    ) -> impl Iterator<Item = i32> + '_ {
1332        self.hole_updates
1333            .iter()
1334            .rev()
1335            .take_while(move |entry| entry.checkpoint == current_checkpoint)
1336            .map(|entry| entry.removed_value)
1337    }
1338
1339    /// Returns all of the holes (currently) in the domain of `var` (including ones which were
1340    /// created at previous decision levels).
1341    pub(crate) fn get_holes(&self) -> impl Iterator<Item = i32> + '_ {
1342        self.holes.keys().copied()
1343    }
1344}
1345
1346#[derive(Debug)]
1347pub(crate) struct IntegerDomainIterator<'a> {
1348    domain: &'a IntegerDomain,
1349    current_value: i32,
1350}
1351
1352impl IntegerDomainIterator<'_> {
1353    fn new(domain: &IntegerDomain) -> IntegerDomainIterator<'_> {
1354        IntegerDomainIterator {
1355            domain,
1356            current_value: domain.lower_bound(),
1357        }
1358    }
1359}
1360
1361impl Iterator for IntegerDomainIterator<'_> {
1362    type Item = i32;
1363    fn next(&mut self) -> Option<i32> {
1364        // We would not expect to iterate through inconsistent domains,
1365        // although we support trying to do so. Not sure if this is good a idea?
1366        if self.domain.verify_consistency().is_err() {
1367            return None;
1368        }
1369
1370        // Note that the current value is never a hole. This is guaranteed by 1) having
1371        // a consistent domain, 2) the iterator starts with the lower bound,
1372        // and 3) the while loop after this if statement updates the current value
1373        // to a non-hole value (if there are any left within the bounds).
1374        let result = if self.current_value <= self.domain.upper_bound() {
1375            Some(self.current_value)
1376        } else {
1377            None
1378        };
1379
1380        self.current_value += 1;
1381        // If the current value is within the bounds, but is not in the domain,
1382        // linearly look for the next non-hole value.
1383        while self.current_value <= self.domain.upper_bound()
1384            && !self.domain.contains(self.current_value)
1385        {
1386            self.current_value += 1;
1387        }
1388        result
1389    }
1390}
1391
1392#[cfg(test)]
1393mod tests {
1394    use super::*;
1395    use crate::engine::notifications::DomainEvent;
1396
1397    #[test]
1398    fn jump_in_bound_change_lower_and_upper_bound_event_backtrack() {
1399        let mut notification_engine = NotificationEngine::test_default();
1400        let mut assignment = Assignments::default();
1401        let d1 = assignment.grow(1, 5);
1402        notification_engine.grow();
1403
1404        assignment.new_checkpoint();
1405
1406        let _ = assignment
1407            .post_predicate(predicate!(d1 != 1), None, &mut notification_engine)
1408            .expect("non-empty domain");
1409        let _ = assignment
1410            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1411            .expect("non-empty domain");
1412
1413        let _ = assignment.synchronise(0, &mut notification_engine);
1414
1415        let events = notification_engine
1416            .drain_backtrack_domain_events()
1417            .collect::<Vec<_>>();
1418        assert_eq!(events.len(), 3);
1419
1420        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1421        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1422        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1423    }
1424
1425    #[test]
1426    fn jump_in_bound_change_assign_event_backtrack() {
1427        let mut notification_engine = NotificationEngine::test_default();
1428        let mut assignment = Assignments::default();
1429        let d1 = assignment.grow(1, 5);
1430        notification_engine.grow();
1431
1432        assignment.new_checkpoint();
1433
1434        let _ = assignment
1435            .post_predicate(predicate!(d1 != 2), None, &mut notification_engine)
1436            .expect("non-empty domain");
1437        let _ = assignment
1438            .post_predicate(predicate!(d1 != 3), None, &mut notification_engine)
1439            .expect("non-empty domain");
1440        let _ = assignment
1441            .post_predicate(predicate!(d1 != 4), None, &mut notification_engine)
1442            .expect("non-empty domain");
1443        let _ = assignment
1444            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1445            .expect("non-empty domain");
1446        let _ = assignment
1447            .post_predicate(predicate!(d1 != 1), None, &mut notification_engine)
1448            .expect_err("empty domain");
1449
1450        let _ = assignment.synchronise(0, &mut notification_engine);
1451
1452        let events = notification_engine
1453            .drain_backtrack_domain_events()
1454            .collect::<Vec<_>>();
1455        assert_eq!(events.len(), 4);
1456
1457        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1458        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1459        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1460        assert_contains_events(&events, d1, [DomainEvent::Assign]);
1461    }
1462
1463    #[test]
1464    fn jump_in_bound_change_upper_bound_event_backtrack() {
1465        let mut notification_engine = NotificationEngine::test_default();
1466        let mut assignment = Assignments::default();
1467        let d1 = assignment.grow(1, 5);
1468        notification_engine.grow();
1469
1470        assignment.new_checkpoint();
1471
1472        let _ = assignment
1473            .post_predicate(predicate!(d1 != 3), None, &mut notification_engine)
1474            .expect("non-empty domain");
1475        let _ = assignment
1476            .post_predicate(predicate!(d1 != 4), None, &mut notification_engine)
1477            .expect("non-empty domain");
1478        let _ = assignment
1479            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1480            .expect("non-empty domain");
1481
1482        let _ = assignment.synchronise(0, &mut notification_engine);
1483
1484        let events = notification_engine
1485            .drain_backtrack_domain_events()
1486            .collect::<Vec<_>>();
1487        assert_eq!(events.len(), 2);
1488
1489        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1490        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1491    }
1492
1493    #[test]
1494    fn jump_in_bound_change_lower_bound_event_backtrack() {
1495        let mut notification_engine = NotificationEngine::test_default();
1496        let mut assignment = Assignments::default();
1497        let d1 = assignment.grow(1, 5);
1498        notification_engine.grow();
1499
1500        assignment.new_checkpoint();
1501
1502        let _ = assignment
1503            .remove_value_from_domain(d1, 3, None)
1504            .expect("non-empty domain");
1505        let _ = assignment
1506            .remove_value_from_domain(d1, 2, None)
1507            .expect("non-empty domain");
1508        let _ = assignment
1509            .remove_value_from_domain(d1, 1, None)
1510            .expect("non-empty domain");
1511
1512        let _ = assignment.synchronise(0, &mut notification_engine);
1513
1514        let events = notification_engine
1515            .drain_backtrack_domain_events()
1516            .collect::<Vec<_>>();
1517        assert_eq!(events.len(), 2);
1518
1519        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1520        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1521    }
1522
1523    #[test]
1524    fn lower_bound_change_lower_bound_event() {
1525        let mut notification_engine = NotificationEngine::default();
1526        let mut assignment = Assignments::default();
1527        let d1 = assignment.grow(1, 5);
1528        notification_engine.grow();
1529
1530        let _ = assignment
1531            .post_predicate(predicate!(d1 >= 2), None, &mut notification_engine)
1532            .expect("non-empty domain");
1533
1534        let events = notification_engine
1535            .drain_domain_events()
1536            .collect::<Vec<_>>();
1537        assert_eq!(events.len(), 1);
1538
1539        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1540    }
1541
1542    #[test]
1543    fn upper_bound_change_triggers_upper_bound_event() {
1544        let mut notification_engine = NotificationEngine::default();
1545        let mut assignment = Assignments::default();
1546        let d1 = assignment.grow(1, 5);
1547        notification_engine.grow();
1548
1549        let _ = assignment
1550            .post_predicate(predicate!(d1 <= 2), None, &mut notification_engine)
1551            .expect("non-empty domain");
1552
1553        let events = notification_engine
1554            .drain_domain_events()
1555            .collect::<Vec<_>>();
1556        assert_eq!(events.len(), 1);
1557        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1558    }
1559
1560    #[test]
1561    fn bounds_change_can_also_trigger_assign_event() {
1562        let mut notification_engine = NotificationEngine::default();
1563        let mut assignment = Assignments::default();
1564
1565        let d1 = assignment.grow(1, 5);
1566        let d2 = assignment.grow(1, 5);
1567        notification_engine.grow();
1568        notification_engine.grow();
1569
1570        let _ = assignment
1571            .post_predicate(predicate!(d1 >= 5), None, &mut notification_engine)
1572            .expect("non-empty domain");
1573        let _ = assignment
1574            .post_predicate(predicate!(d2 <= 1), None, &mut notification_engine)
1575            .expect("non-empty domain");
1576
1577        let events = notification_engine
1578            .drain_domain_events()
1579            .collect::<Vec<_>>();
1580        assert_eq!(events.len(), 4, "expected more than 4 events: {events:?}");
1581
1582        assert_contains_events(&events, d1, [DomainEvent::LowerBound, DomainEvent::Assign]);
1583        assert_contains_events(&events, d2, [DomainEvent::UpperBound, DomainEvent::Assign]);
1584    }
1585
1586    #[test]
1587    fn making_assignment_triggers_appropriate_events() {
1588        let mut notification_engine = NotificationEngine::default();
1589        let mut assignment = Assignments::default();
1590
1591        let d1 = assignment.grow(1, 5);
1592        let d2 = assignment.grow(1, 5);
1593        let d3 = assignment.grow(1, 5);
1594        notification_engine.grow();
1595        notification_engine.grow();
1596        notification_engine.grow();
1597
1598        let _ = assignment
1599            .post_predicate(predicate!(d1 == 1), None, &mut notification_engine)
1600            .expect("non-empty domain");
1601        let _ = assignment
1602            .post_predicate(predicate!(d2 == 5), None, &mut notification_engine)
1603            .expect("non-empty domain");
1604        let _ = assignment
1605            .post_predicate(predicate!(d3 == 3), None, &mut notification_engine)
1606            .expect("non-empty domain");
1607
1608        let events = notification_engine
1609            .drain_domain_events()
1610            .collect::<Vec<_>>();
1611        assert_eq!(events.len(), 7);
1612
1613        assert_contains_events(&events, d1, [DomainEvent::Assign, DomainEvent::UpperBound]);
1614        assert_contains_events(&events, d2, [DomainEvent::Assign, DomainEvent::LowerBound]);
1615        assert_contains_events(
1616            &events,
1617            d3,
1618            [
1619                DomainEvent::Assign,
1620                DomainEvent::LowerBound,
1621                DomainEvent::UpperBound,
1622            ],
1623        );
1624    }
1625
1626    #[test]
1627    fn removal_triggers_removal_event() {
1628        let mut notification_engine = NotificationEngine::default();
1629        let mut assignment = Assignments::default();
1630        let d1 = assignment.grow(1, 5);
1631        notification_engine.grow();
1632
1633        let _ = assignment
1634            .post_predicate(predicate!(d1 != 2), None, &mut notification_engine)
1635            .expect("non-empty domain");
1636
1637        let events = notification_engine
1638            .drain_domain_events()
1639            .collect::<Vec<_>>();
1640        assert_eq!(events.len(), 1);
1641        assert!(events.contains(&(DomainEvent::Removal, d1)));
1642    }
1643
1644    #[test]
1645    fn value_can_be_removed_from_domains() {
1646        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1647        let _ = domain.remove_value(1, 1, 2);
1648
1649        assert!(domain.contains(2));
1650        assert!(!domain.contains(1));
1651    }
1652
1653    #[test]
1654    fn removing_the_lower_bound_updates_that_lower_bound() {
1655        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1656        let _ = domain.remove_value(1, 1, 1);
1657        let _ = domain.remove_value(2, 1, 2);
1658
1659        assert_eq!(3, domain.lower_bound());
1660    }
1661
1662    #[test]
1663    fn removing_the_upper_bound_updates_the_upper_bound() {
1664        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1665        let _ = domain.remove_value(4, 0, 1);
1666        let _ = domain.remove_value(5, 0, 2);
1667
1668        assert_eq!(3, domain.upper_bound());
1669    }
1670
1671    #[test]
1672    fn an_empty_domain_accepts_removal_operations() {
1673        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1674        let _ = domain.remove_value(4, 0, 1);
1675        let _ = domain.remove_value(1, 0, 2);
1676        let _ = domain.remove_value(1, 0, 3);
1677    }
1678
1679    #[test]
1680    fn setting_lower_bound_rounds_up_to_nearest_value_in_domain() {
1681        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1682        let _ = domain.remove_value(2, 1, 2);
1683        let _ = domain.remove_value(3, 1, 3);
1684        let _ = domain.set_lower_bound(2, 1, 4);
1685
1686        assert_eq!(4, domain.lower_bound());
1687    }
1688
1689    #[test]
1690    fn setting_upper_bound_rounds_down_to_nearest_value_in_domain() {
1691        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1692        let _ = domain.remove_value(4, 0, 1);
1693        let _ = domain.set_upper_bound(4, 0, 2);
1694
1695        assert_eq!(3, domain.upper_bound());
1696    }
1697
1698    #[test]
1699    fn undo_removal_at_bounds_indexes_into_values_domain_correctly() {
1700        let mut notification_engine = NotificationEngine::default();
1701        let mut assignment = Assignments::default();
1702        let d1 = assignment.grow(1, 5);
1703        notification_engine.grow();
1704
1705        assignment.new_checkpoint();
1706
1707        let _ = assignment
1708            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1709            .expect("non-empty domain");
1710
1711        let _ = assignment.synchronise(0, &mut notification_engine);
1712
1713        assert_eq!(5, assignment.get_upper_bound(d1));
1714    }
1715
1716    fn assert_contains_events(
1717        slice: &[(DomainEvent, DomainId)],
1718        domain: DomainId,
1719        required_events: impl IntoIterator<Item = DomainEvent>,
1720    ) {
1721        for event in required_events {
1722            assert!(slice.contains(&(event, domain)));
1723        }
1724    }
1725
1726    fn get_domain1() -> (DomainId, IntegerDomain) {
1727        let domain_id = DomainId::new(0);
1728        let mut domain = IntegerDomain::new(0, 0, 100, 1, domain_id);
1729        let _ = domain.set_lower_bound(1, 0, 1);
1730        let _ = domain.set_lower_bound(5, 1, 2);
1731        let _ = domain.set_lower_bound(10, 2, 10);
1732        let _ = domain.set_lower_bound(20, 5, 50);
1733        let _ = domain.set_lower_bound(50, 10, 70);
1734
1735        (domain_id, domain)
1736    }
1737
1738    #[test]
1739    fn lower_bound_trail_position_inbetween_value() {
1740        let (domain_id, domain) = get_domain1();
1741
1742        assert_eq!(
1743            domain
1744                .get_update_info(&predicate!(domain_id >= 12))
1745                .unwrap()
1746                .trail_position,
1747            50
1748        );
1749    }
1750
1751    #[test]
1752    fn lower_bound_trail_position_last_bound() {
1753        let (domain_id, domain) = get_domain1();
1754
1755        assert_eq!(
1756            domain
1757                .get_update_info(&predicate!(domain_id >= 50))
1758                .unwrap()
1759                .trail_position,
1760            70
1761        );
1762    }
1763
1764    #[test]
1765    fn lower_bound_trail_position_beyond_value() {
1766        let (domain_id, domain) = get_domain1();
1767
1768        assert!(
1769            domain
1770                .get_update_info(&predicate!(domain_id >= 101))
1771                .is_none()
1772        );
1773    }
1774
1775    #[test]
1776    fn lower_bound_trail_position_trivial() {
1777        let (domain_id, domain) = get_domain1();
1778
1779        assert_eq!(
1780            domain
1781                .get_update_info(&predicate!(domain_id >= -10))
1782                .unwrap()
1783                .trail_position,
1784            0
1785        );
1786    }
1787
1788    #[test]
1789    fn lower_bound_trail_position_with_removals() {
1790        let (domain_id, mut domain) = get_domain1();
1791        let _ = domain.remove_value(50, 11, 75);
1792        let _ = domain.remove_value(51, 11, 77);
1793        let _ = domain.remove_value(52, 11, 80);
1794
1795        assert_eq!(
1796            domain
1797                .get_update_info(&predicate!(domain_id >= 52))
1798                .unwrap()
1799                .trail_position,
1800            77
1801        );
1802    }
1803
1804    #[test]
1805    fn removal_trail_position() {
1806        let (domain_id, mut domain) = get_domain1();
1807        let _ = domain.remove_value(50, 11, 75);
1808        let _ = domain.remove_value(51, 11, 77);
1809        let _ = domain.remove_value(52, 11, 80);
1810
1811        assert_eq!(
1812            domain
1813                .get_update_info(&predicate!(domain_id != 50))
1814                .unwrap()
1815                .trail_position,
1816            75
1817        );
1818    }
1819
1820    #[test]
1821    fn removal_trail_position_after_lower_bound() {
1822        let (domain_id, mut domain) = get_domain1();
1823        let _ = domain.remove_value(50, 11, 75);
1824        let _ = domain.remove_value(51, 11, 77);
1825        let _ = domain.remove_value(52, 11, 80);
1826        let _ = domain.set_lower_bound(60, 11, 150);
1827
1828        assert_eq!(
1829            domain
1830                .get_update_info(&predicate!(domain_id != 55))
1831                .unwrap()
1832                .trail_position,
1833            150
1834        );
1835    }
1836
1837    #[test]
1838    fn lower_bound_change_backtrack() {
1839        let mut notification_engine = NotificationEngine::default();
1840        let mut assignment = Assignments::default();
1841        let domain_id1 = assignment.grow(0, 100);
1842        let domain_id2 = assignment.grow(0, 50);
1843        notification_engine.grow();
1844        notification_engine.grow();
1845
1846        // decision level 1
1847        assignment.new_checkpoint();
1848        let _ = assignment
1849            .post_predicate(predicate!(domain_id1 >= 2), None, &mut notification_engine)
1850            .expect("");
1851        let _ = assignment
1852            .post_predicate(predicate!(domain_id2 >= 25), None, &mut notification_engine)
1853            .expect("");
1854
1855        // decision level 2
1856        assignment.new_checkpoint();
1857        let _ = assignment
1858            .post_predicate(predicate!(domain_id1 >= 5), None, &mut notification_engine)
1859            .expect("");
1860
1861        // decision level 3
1862        assignment.new_checkpoint();
1863        let _ = assignment
1864            .post_predicate(predicate!(domain_id1 >= 7), None, &mut notification_engine)
1865            .expect("");
1866
1867        assert_eq!(assignment.get_lower_bound(domain_id1), 7);
1868
1869        let _ = assignment.synchronise(1, &mut notification_engine);
1870
1871        assert_eq!(assignment.get_lower_bound(domain_id1), 2);
1872    }
1873
1874    #[test]
1875    fn lower_bound_inbetween_updates() {
1876        let (_, domain) = get_domain1();
1877        assert_eq!(domain.lower_bound_at_trail_position(25), 10);
1878    }
1879
1880    #[test]
1881    fn lower_bound_beyond_trail_position() {
1882        let (_, domain) = get_domain1();
1883        assert_eq!(domain.lower_bound_at_trail_position(1000), 50);
1884    }
1885
1886    #[test]
1887    fn lower_bound_at_update() {
1888        let (_, domain) = get_domain1();
1889        assert_eq!(domain.lower_bound_at_trail_position(50), 20);
1890    }
1891
1892    #[test]
1893    fn lower_bound_at_trail_position_after_removals() {
1894        let (_, mut domain) = get_domain1();
1895        let _ = domain.remove_value(50, 11, 75);
1896        let _ = domain.remove_value(51, 11, 77);
1897        let _ = domain.remove_value(52, 11, 80);
1898
1899        assert_eq!(domain.lower_bound_at_trail_position(77), 52);
1900    }
1901
1902    #[test]
1903    fn lower_bound_at_trail_position_after_removals_and_bound_update() {
1904        let (_, mut domain) = get_domain1();
1905        let _ = domain.remove_value(50, 11, 75);
1906        let _ = domain.remove_value(51, 11, 77);
1907        let _ = domain.remove_value(52, 11, 80);
1908        let _ = domain.set_lower_bound(60, 11, 150);
1909
1910        assert_eq!(domain.lower_bound_at_trail_position(100), 53);
1911    }
1912
1913    #[test]
1914    fn inconsistent_bound_updates() {
1915        let domain_id = DomainId::new(0);
1916        let mut domain = IntegerDomain::new(0, 0, 2, 1, domain_id);
1917        let _ = domain.set_lower_bound(2, 1, 1);
1918        let _ = domain.set_upper_bound(1, 1, 2);
1919        assert!(domain.verify_consistency().is_err());
1920    }
1921
1922    #[test]
1923    fn inconsistent_domain_removals() {
1924        let domain_id = DomainId::new(0);
1925        let mut domain = IntegerDomain::new(0, 0, 2, 1, domain_id);
1926        let _ = domain.remove_value(1, 1, 1);
1927        let _ = domain.remove_value(2, 1, 2);
1928        let _ = domain.remove_value(0, 1, 3);
1929        assert!(domain.verify_consistency().is_err());
1930    }
1931
1932    #[test]
1933    fn domain_iterator_simple() {
1934        let domain_id = DomainId::new(0);
1935        let domain = IntegerDomain::new(0, 0, 5, 1, domain_id);
1936        let mut iter = domain.domain_iterator();
1937        assert_eq!(iter.next(), Some(0));
1938        assert_eq!(iter.next(), Some(1));
1939        assert_eq!(iter.next(), Some(2));
1940        assert_eq!(iter.next(), Some(3));
1941        assert_eq!(iter.next(), Some(4));
1942        assert_eq!(iter.next(), Some(5));
1943        assert_eq!(iter.next(), None);
1944    }
1945
1946    #[test]
1947    fn domain_iterator_skip_holes() {
1948        let domain_id = DomainId::new(0);
1949        let mut domain = IntegerDomain::new(0, 0, 5, 1, domain_id);
1950        let _ = domain.remove_value(1, 0, 5);
1951        let _ = domain.remove_value(4, 0, 10);
1952
1953        let mut iter = domain.domain_iterator();
1954        assert_eq!(iter.next(), Some(0));
1955        assert_eq!(iter.next(), Some(2));
1956        assert_eq!(iter.next(), Some(3));
1957        assert_eq!(iter.next(), Some(5));
1958        assert_eq!(iter.next(), None);
1959    }
1960
1961    #[test]
1962    fn domain_iterator_removed_bounds() {
1963        let domain_id = DomainId::new(0);
1964        let mut domain = IntegerDomain::new(0, 0, 5, 1, domain_id);
1965        let _ = domain.remove_value(0, 0, 1);
1966        let _ = domain.remove_value(5, 0, 10);
1967
1968        let mut iter = domain.domain_iterator();
1969        assert_eq!(iter.next(), Some(1));
1970        assert_eq!(iter.next(), Some(2));
1971        assert_eq!(iter.next(), Some(3));
1972        assert_eq!(iter.next(), Some(4));
1973        assert_eq!(iter.next(), None);
1974    }
1975
1976    #[test]
1977    fn domain_iterator_removed_values_present_beyond_bounds() {
1978        let domain_id = DomainId::new(0);
1979        let mut domain = IntegerDomain::new(0, 0, 10, 1, domain_id);
1980        let _ = domain.remove_value(7, 0, 1);
1981        let _ = domain.remove_value(9, 0, 5);
1982        let _ = domain.remove_value(2, 0, 10);
1983        let _ = domain.set_upper_bound(6, 1, 10);
1984
1985        let mut iter = domain.domain_iterator();
1986        assert_eq!(iter.next(), Some(0));
1987        assert_eq!(iter.next(), Some(1));
1988        assert_eq!(iter.next(), Some(3));
1989        assert_eq!(iter.next(), Some(4));
1990        assert_eq!(iter.next(), Some(5));
1991        assert_eq!(iter.next(), Some(6));
1992        assert_eq!(iter.next(), None);
1993    }
1994
1995    #[test]
1996    fn various_tests_evaluate_predicate() {
1997        let mut notification_engine = NotificationEngine::default();
1998        let mut assignments = Assignments::default();
1999        // Create the domain {0, 1, 3, 4, 5, 6}
2000        let domain_id = assignments.grow(0, 10);
2001        notification_engine.grow();
2002
2003        let _ =
2004            assignments.post_predicate(predicate!(domain_id != 7), None, &mut notification_engine);
2005        let _ =
2006            assignments.post_predicate(predicate!(domain_id != 9), None, &mut notification_engine);
2007        let _ =
2008            assignments.post_predicate(predicate!(domain_id != 2), None, &mut notification_engine);
2009        let _ =
2010            assignments.post_predicate(predicate!(domain_id <= 6), None, &mut notification_engine);
2011
2012        let lb_predicate = |lower_bound: i32| -> Predicate { predicate!(domain_id >= lower_bound) };
2013        let ub_predicate = |upper_bound: i32| -> Predicate { predicate!(domain_id <= upper_bound) };
2014        let eq_predicate =
2015            |equality_constant: i32| -> Predicate { predicate!(domain_id == equality_constant) };
2016        let neq_predicate =
2017            |not_equal_constant: i32| -> Predicate { predicate!(domain_id != not_equal_constant) };
2018
2019        assert!(
2020            assignments
2021                .evaluate_predicate(lb_predicate(0))
2022                .is_some_and(|x| x)
2023        );
2024        assert!(assignments.evaluate_predicate(lb_predicate(1)).is_none());
2025        assert!(assignments.evaluate_predicate(lb_predicate(2)).is_none());
2026        assert!(assignments.evaluate_predicate(lb_predicate(3)).is_none());
2027        assert!(assignments.evaluate_predicate(lb_predicate(4)).is_none());
2028        assert!(assignments.evaluate_predicate(lb_predicate(5)).is_none());
2029        assert!(assignments.evaluate_predicate(lb_predicate(6)).is_none());
2030        assert!(
2031            assignments
2032                .evaluate_predicate(lb_predicate(7))
2033                .is_some_and(|x| !x)
2034        );
2035        assert!(
2036            assignments
2037                .evaluate_predicate(lb_predicate(8))
2038                .is_some_and(|x| !x)
2039        );
2040        assert!(
2041            assignments
2042                .evaluate_predicate(lb_predicate(9))
2043                .is_some_and(|x| !x)
2044        );
2045        assert!(
2046            assignments
2047                .evaluate_predicate(lb_predicate(10))
2048                .is_some_and(|x| !x)
2049        );
2050
2051        assert!(assignments.evaluate_predicate(ub_predicate(0)).is_none());
2052        assert!(assignments.evaluate_predicate(ub_predicate(1)).is_none());
2053        assert!(assignments.evaluate_predicate(ub_predicate(2)).is_none());
2054        assert!(assignments.evaluate_predicate(ub_predicate(3)).is_none());
2055        assert!(assignments.evaluate_predicate(ub_predicate(4)).is_none());
2056        assert!(assignments.evaluate_predicate(ub_predicate(5)).is_none());
2057        assert!(
2058            assignments
2059                .evaluate_predicate(ub_predicate(6))
2060                .is_some_and(|x| x)
2061        );
2062        assert!(
2063            assignments
2064                .evaluate_predicate(ub_predicate(7))
2065                .is_some_and(|x| x)
2066        );
2067        assert!(
2068            assignments
2069                .evaluate_predicate(ub_predicate(8))
2070                .is_some_and(|x| x)
2071        );
2072        assert!(
2073            assignments
2074                .evaluate_predicate(ub_predicate(9))
2075                .is_some_and(|x| x)
2076        );
2077        assert!(
2078            assignments
2079                .evaluate_predicate(ub_predicate(10))
2080                .is_some_and(|x| x)
2081        );
2082
2083        assert!(assignments.evaluate_predicate(neq_predicate(0)).is_none());
2084        assert!(assignments.evaluate_predicate(neq_predicate(1)).is_none());
2085        assert!(
2086            assignments
2087                .evaluate_predicate(neq_predicate(2))
2088                .is_some_and(|x| x)
2089        );
2090        assert!(assignments.evaluate_predicate(neq_predicate(3)).is_none());
2091        assert!(assignments.evaluate_predicate(neq_predicate(4)).is_none());
2092        assert!(assignments.evaluate_predicate(neq_predicate(5)).is_none());
2093        assert!(assignments.evaluate_predicate(neq_predicate(6)).is_none());
2094        assert!(
2095            assignments
2096                .evaluate_predicate(neq_predicate(7))
2097                .is_some_and(|x| x)
2098        );
2099        assert!(
2100            assignments
2101                .evaluate_predicate(neq_predicate(8))
2102                .is_some_and(|x| x)
2103        );
2104        assert!(
2105            assignments
2106                .evaluate_predicate(neq_predicate(9))
2107                .is_some_and(|x| x)
2108        );
2109        assert!(
2110            assignments
2111                .evaluate_predicate(neq_predicate(10))
2112                .is_some_and(|x| x)
2113        );
2114
2115        assert!(assignments.evaluate_predicate(eq_predicate(0)).is_none());
2116        assert!(assignments.evaluate_predicate(eq_predicate(1)).is_none());
2117        assert!(
2118            assignments
2119                .evaluate_predicate(eq_predicate(2))
2120                .is_some_and(|x| !x)
2121        );
2122        assert!(assignments.evaluate_predicate(eq_predicate(3)).is_none());
2123        assert!(assignments.evaluate_predicate(eq_predicate(4)).is_none());
2124        assert!(assignments.evaluate_predicate(eq_predicate(5)).is_none());
2125        assert!(assignments.evaluate_predicate(eq_predicate(6)).is_none());
2126        assert!(
2127            assignments
2128                .evaluate_predicate(eq_predicate(7))
2129                .is_some_and(|x| !x)
2130        );
2131        assert!(
2132            assignments
2133                .evaluate_predicate(eq_predicate(8))
2134                .is_some_and(|x| !x)
2135        );
2136        assert!(
2137            assignments
2138                .evaluate_predicate(eq_predicate(9))
2139                .is_some_and(|x| !x)
2140        );
2141        assert!(
2142            assignments
2143                .evaluate_predicate(eq_predicate(10))
2144                .is_some_and(|x| !x)
2145        );
2146
2147        let _ =
2148            assignments.post_predicate(predicate!(domain_id >= 6), None, &mut notification_engine);
2149
2150        assert!(
2151            assignments
2152                .evaluate_predicate(neq_predicate(6))
2153                .is_some_and(|x| !x)
2154        );
2155        assert!(
2156            assignments
2157                .evaluate_predicate(eq_predicate(6))
2158                .is_some_and(|x| x)
2159        );
2160        assert!(
2161            assignments
2162                .evaluate_predicate(lb_predicate(6))
2163                .is_some_and(|x| x)
2164        );
2165        assert!(
2166            assignments
2167                .evaluate_predicate(ub_predicate(6))
2168                .is_some_and(|x| x)
2169        );
2170    }
2171}