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