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    /// Determines whether the provided [`Predicate`] holds at the provided trail position. In case
667    /// the predicate is not assigned yet (neither true nor false), returns None.
668    pub(crate) fn evaluate_predicate_at_trail_position(
669        &self,
670        predicate: Predicate,
671        trail_position: usize,
672    ) -> Option<bool> {
673        let domain_id = predicate.get_domain();
674        let value = predicate.get_right_hand_side();
675
676        match predicate.get_predicate_type() {
677            PredicateType::LowerBound => {
678                if self.get_lower_bound_at_trail_position(domain_id, trail_position) >= value {
679                    Some(true)
680                } else if self.get_upper_bound_at_trail_position(domain_id, trail_position) < value
681                {
682                    Some(false)
683                } else {
684                    None
685                }
686            }
687            PredicateType::UpperBound => {
688                if self.get_upper_bound_at_trail_position(domain_id, trail_position) <= value {
689                    Some(true)
690                } else if self.get_lower_bound_at_trail_position(domain_id, trail_position) > value
691                {
692                    Some(false)
693                } else {
694                    None
695                }
696            }
697            PredicateType::NotEqual => {
698                if !self.is_value_in_domain_at_trail_position(domain_id, value, trail_position) {
699                    Some(true)
700                } else if let Some(assigned_value) =
701                    self.get_assigned_value_at_trail_position(&domain_id, trail_position)
702                {
703                    // Previous branch concluded the value is not in the domain, so if the variable
704                    // is assigned, then it is assigned to the not equals value.
705                    pumpkin_assert_simple!(assigned_value == value);
706                    Some(false)
707                } else {
708                    None
709                }
710            }
711            PredicateType::Equal => {
712                if !self.is_value_in_domain_at_trail_position(domain_id, value, trail_position) {
713                    Some(false)
714                } else if let Some(assigned_value) =
715                    self.get_assigned_value_at_trail_position(&domain_id, trail_position)
716                {
717                    pumpkin_assert_moderate!(assigned_value == value);
718                    Some(true)
719                } else {
720                    None
721                }
722            }
723        }
724    }
725
726    pub(crate) fn get_assigned_value_at_trail_position<Var: IntegerVariable>(
727        &self,
728        var: &Var,
729        trail_position: usize,
730    ) -> Option<i32> {
731        self.is_domain_assigned_at_trail_position(var, trail_position)
732            .then(|| var.lower_bound(self))
733    }
734
735    pub(crate) fn is_domain_assigned_at_trail_position<Var: IntegerVariable>(
736        &self,
737        var: &Var,
738        trail_position: usize,
739    ) -> bool {
740        var.lower_bound_at_trail_position(self, trail_position)
741            == var.upper_bound_at_trail_position(self, trail_position)
742    }
743
744    pub(crate) fn is_predicate_satisfied(&self, predicate: Predicate) -> bool {
745        self.evaluate_predicate(predicate)
746            .is_some_and(|truth_value| truth_value)
747    }
748
749    #[allow(unused, reason = "makes sense to have in this API")]
750    pub(crate) fn is_predicate_falsified(&self, predicate: Predicate) -> bool {
751        self.evaluate_predicate(predicate)
752            .is_some_and(|truth_value| !truth_value)
753    }
754
755    /// Synchronises the internal structures of [`Assignments`] based on the fact that
756    /// backtracking to `new_checkpoint` is taking place. This method returns the list of
757    /// [`DomainId`]s and their values which were fixed (i.e. domain of size one) before
758    /// backtracking and are unfixed (i.e. domain of two or more values) after synchronisation.
759    pub(crate) fn synchronise(
760        &mut self,
761        new_checkpoint: usize,
762        notification_engine: &mut NotificationEngine,
763    ) -> Vec<(DomainId, i32)> {
764        let mut unfixed_variables = Vec::new();
765        let num_trail_entries_before_synchronisation = self.num_trail_entries();
766
767        pumpkin_assert_simple!(
768            new_checkpoint <= self.trail.get_checkpoint(),
769            "Expected the new decision level {new_checkpoint} to be less than or equal to the current decision level {}",
770            self.trail.get_checkpoint(),
771        );
772
773        self.trail
774            .synchronise(new_checkpoint)
775            .enumerate()
776            .for_each(|(index, entry)| {
777                // Calculate how many values are re-introduced into the domain.
778                let domain_id = entry.predicate.get_domain();
779                let lower_bound_before = self.domains[domain_id].lower_bound();
780                let upper_bound_before = self.domains[domain_id].upper_bound();
781
782                let trail_index = num_trail_entries_before_synchronisation - index - 1;
783
784                let add_on_upper_bound = entry.old_upper_bound.abs_diff(upper_bound_before) as u64;
785                let add_on_lower_bound = entry.old_lower_bound.abs_diff(lower_bound_before) as u64;
786                self.pruned_values -= add_on_upper_bound + add_on_lower_bound;
787
788                if entry.predicate.is_not_equal_predicate()
789                    && add_on_lower_bound + add_on_upper_bound == 0
790                {
791                    self.pruned_values -= 1;
792                }
793
794                let fixed_before =
795                    self.domains[domain_id].lower_bound() == self.domains[domain_id].upper_bound();
796                self.domains[domain_id].undo_trail_entry(&entry);
797
798                let new_lower_bound = self.domains[domain_id].lower_bound();
799                let new_upper_bound = self.domains[domain_id].upper_bound();
800                self.bounds[domain_id] = (new_lower_bound, new_upper_bound);
801
802                notification_engine.undo_trail_entry(
803                    fixed_before,
804                    lower_bound_before,
805                    upper_bound_before,
806                    new_lower_bound,
807                    new_upper_bound,
808                    trail_index,
809                    entry.predicate,
810                );
811
812                if new_lower_bound != new_upper_bound {
813                    // Variable used to be fixed but is not after backtracking
814                    unfixed_variables.push((domain_id, lower_bound_before));
815                }
816            });
817
818        // Drain does not remove the events from the internal data structure. Elements are removed
819        // lazily, as the iterator gets executed. For this reason we go through the entire iterator.
820        notification_engine.clear_events();
821
822        unfixed_variables
823    }
824
825    /// todo: This is a temporary hack, not to be used in general.
826    pub(crate) fn remove_last_trail_element(&mut self) -> (Predicate, ReasonRef) {
827        let entry = self.trail.pop().unwrap();
828        let domain_id = entry.predicate.get_domain();
829        self.domains[domain_id].undo_trail_entry(&entry);
830        self.update_bounds_snapshot(domain_id);
831
832        let reason_ref = entry.reason.unwrap();
833
834        (entry.predicate, reason_ref)
835    }
836
837    /// Get the number of values pruned from all the domains.
838    pub(crate) fn get_pruned_value_count(&self) -> u64 {
839        self.pruned_values
840    }
841
842    fn update_bounds_snapshot(&mut self, domain_id: DomainId) {
843        self.bounds[domain_id] = (
844            self.domains[domain_id].lower_bound(),
845            self.domains[domain_id].upper_bound(),
846        );
847    }
848}
849
850impl Assignments {
851    #[deprecated]
852    pub(crate) fn get_reason_for_predicate_brute_force(&self, predicate: Predicate) -> ReasonRef {
853        self.trail
854            .iter()
855            .find_map(|entry| {
856                if entry.predicate == predicate {
857                    entry.reason
858                } else {
859                    None
860                }
861            })
862            .unwrap_or_else(|| panic!("could not find a reason for predicate {predicate}"))
863    }
864}
865
866#[derive(Clone, Debug)]
867pub(crate) struct ConstraintProgrammingTrailEntry {
868    pub predicate: Predicate,
869    /// Explicitly store the bound before the predicate was applied so that it is easier later on
870    ///  to update the bounds when backtracking.
871    pub(crate) old_lower_bound: i32,
872    pub(crate) old_upper_bound: i32,
873    /// Stores the a reference to the reason in the `ReasonStore`, only makes sense if a
874    /// propagation  took place, e.g., does _not_ make sense in the case of a decision or if
875    /// the update was due  to synchronisation from the propositional trail.
876    pub(crate) reason: Option<ReasonRef>,
877}
878
879#[derive(Clone, Copy, Debug)]
880struct PairDecisionLevelTrailPosition {
881    checkpoint: usize,
882    trail_position: usize,
883}
884
885#[derive(Clone, Debug)]
886struct BoundUpdateInfo {
887    bound: i32,
888    checkpoint: usize,
889    trail_position: usize,
890}
891
892#[derive(Clone, Debug)]
893struct HoleUpdateInfo {
894    removed_value: i32,
895
896    checkpoint: usize,
897
898    triggered_lower_bound_update: bool,
899    triggered_upper_bound_update: bool,
900}
901
902/// This is the CP representation of a domain. It stores the bounds alongside holes in the domain.
903/// When the domain is in an empty state, `lower_bound > upper_bound`.
904/// The domain tracks all domain changes, so it is possible to query the domain at a given
905/// cp trail position, i.e., the domain at some previous point in time.
906/// This is needed to support lazy explanations.
907#[derive(Clone, Debug)]
908struct IntegerDomain {
909    id: DomainId,
910    /// The 'updates' fields chronologically records the changes to the domain.
911    lower_bound_updates: Vec<BoundUpdateInfo>,
912    upper_bound_updates: Vec<BoundUpdateInfo>,
913    hole_updates: Vec<HoleUpdateInfo>,
914    /// Auxiliary data structure to make it easy to check if a value is present or not.
915    /// This is done to avoid going through 'hole_updates'.
916    /// It maps a removed value with its decision level and trail position.
917    /// In the future we could consider using direct hashing if the domain is small.
918    holes: HashMap<i32, PairDecisionLevelTrailPosition>,
919    // Records the trail entry at which all of the root bounds are true
920    initial_bounds_below_trail: usize,
921    /// The holes that exist in the input problem.
922    initial_holes: Vec<i32>,
923}
924
925impl IntegerDomain {
926    fn new(
927        lower_bound: i32,
928        lower_bound_position: usize,
929        upper_bound: i32,
930        upper_bound_position: usize,
931        id: DomainId,
932    ) -> IntegerDomain {
933        pumpkin_assert_simple!(lower_bound <= upper_bound, "Cannot create an empty domain.");
934
935        let lower_bound_updates = vec![BoundUpdateInfo {
936            bound: lower_bound,
937            checkpoint: 0,
938            trail_position: lower_bound_position,
939        }];
940
941        let upper_bound_updates = vec![BoundUpdateInfo {
942            bound: upper_bound,
943            checkpoint: 0,
944            trail_position: upper_bound_position,
945        }];
946
947        IntegerDomain {
948            id,
949            initial_holes: vec![],
950            lower_bound_updates,
951            upper_bound_updates,
952            hole_updates: vec![],
953            holes: Default::default(),
954            initial_bounds_below_trail: std::cmp::max(lower_bound_position, upper_bound_position),
955        }
956    }
957
958    fn lower_bound(&self) -> i32 {
959        // the last entry contains the current lower bound
960        self.lower_bound_updates
961            .last()
962            .expect("Cannot be empty.")
963            .bound
964    }
965
966    fn lower_bound_checkpoint(&self) -> usize {
967        self.lower_bound_updates
968            .last()
969            .expect("Cannot be empty.")
970            .checkpoint
971    }
972
973    fn initial_lower_bound(&self) -> i32 {
974        // the first entry is never removed,
975        // and contains the bound that was assigned upon creation
976        self.lower_bound_updates[0].bound
977    }
978
979    fn lower_bound_at_trail_position(&self, trail_position: usize) -> i32 {
980        // TODO: could possibly cache old queries, and maybe even first checking large/small trail
981        // position values (in case those are commonly used)
982
983        // We find the update with the largest trail position such that it is smaller than or equal
984        // to the input trail position
985        //
986        // Recall that by the nature of the updates, the updates are stored in increasing order of
987        // trail position.
988        //
989        // We find the first index such that `u.trail_position > trail_position` and then we
990        // subtract 1 from that
991        let index = self
992            .lower_bound_updates
993            .partition_point(|u| u.trail_position <= trail_position);
994
995        self.lower_bound_updates[index.saturating_sub(1)].bound
996    }
997
998    fn upper_bound(&self) -> i32 {
999        // the last entry contains the current upper bound
1000        self.upper_bound_updates
1001            .last()
1002            .expect("Cannot be empty.")
1003            .bound
1004    }
1005
1006    fn checkpoint(&self) -> usize {
1007        self.upper_bound_updates
1008            .last()
1009            .expect("Cannot be empty.")
1010            .checkpoint
1011    }
1012
1013    fn initial_upper_bound(&self) -> i32 {
1014        // the first entry is never removed,
1015        // and contains the bound that was assigned upon creation
1016        self.upper_bound_updates[0].bound
1017    }
1018
1019    fn upper_bound_at_trail_position(&self, trail_position: usize) -> i32 {
1020        // TODO: could possibly cache old queries, and maybe even first checking large/small trail
1021        // position values (in case those are commonly used)
1022
1023        // We find the update with the largest trail position such that it is smaller than or equal
1024        // to the input trail position
1025        //
1026        // Recall that by the nature of the updates, the updates are stored in increasing order of
1027        // trail position.
1028        //
1029        // We find the first index such that `u.trail_position > trail_position` and then we
1030        // subtract 1 from that
1031        let index = self
1032            .upper_bound_updates
1033            .partition_point(|u| u.trail_position <= trail_position)
1034            .saturating_sub(1);
1035
1036        self.upper_bound_updates[index].bound
1037    }
1038
1039    fn domain_iterator(&self) -> IntegerDomainIterator<'_> {
1040        // Ideally we use into_iter but I did not manage to get it to work,
1041        // because the iterator takes a lifelines
1042        // (the iterator takes a reference to the domain).
1043        // So this will do for now.
1044        IntegerDomainIterator::new(self)
1045    }
1046
1047    fn contains(&self, value: i32) -> bool {
1048        self.lower_bound() <= value
1049            && value <= self.upper_bound()
1050            && !self.holes.contains_key(&value)
1051    }
1052
1053    fn contains_at_trail_position(&self, value: i32, trail_position: usize) -> bool {
1054        // If the value is out of bounds,
1055        // then we can safety say that the value is not in the domain.
1056        if self.lower_bound_at_trail_position(trail_position) > value
1057            || self.upper_bound_at_trail_position(trail_position) < value
1058        {
1059            return false;
1060        }
1061        // Otherwise we need to check if there is a hole with that specific value.
1062
1063        // In case the hole is made at the given trail position or earlier,
1064        // the value is not in the domain.
1065        if let Some(hole_info) = self.holes.get(&value)
1066            && hole_info.trail_position <= trail_position
1067        {
1068            return false;
1069        }
1070
1071        // Since none of the previous checks triggered, the value is in the domain.
1072        true
1073    }
1074
1075    fn remove_value(
1076        &mut self,
1077        removed_value: i32,
1078        checkpoint: usize,
1079        trail_position: usize,
1080    ) -> bool {
1081        if removed_value < self.lower_bound()
1082            || removed_value > self.upper_bound()
1083            || self.holes.contains_key(&removed_value)
1084        {
1085            return false;
1086        }
1087
1088        self.hole_updates.push(HoleUpdateInfo {
1089            removed_value,
1090            checkpoint,
1091            triggered_lower_bound_update: false,
1092            triggered_upper_bound_update: false,
1093        });
1094        // Note that it is important to remove the hole now,
1095        // because the later if statements may use the holes.
1096        let old_none_entry = self.holes.insert(
1097            removed_value,
1098            PairDecisionLevelTrailPosition {
1099                checkpoint,
1100                trail_position,
1101            },
1102        );
1103        pumpkin_assert_moderate!(old_none_entry.is_none());
1104
1105        // Check if removing a value triggers a lower bound update.
1106        if self.lower_bound() == removed_value {
1107            let _ = self.set_lower_bound(removed_value + 1, checkpoint, trail_position);
1108            self.hole_updates
1109                .last_mut()
1110                .expect("we just pushed a value, so must be present")
1111                .triggered_lower_bound_update = true;
1112        }
1113        // Check if removing the value triggers an upper bound update.
1114        if self.upper_bound() == removed_value {
1115            let _ = self.set_upper_bound(removed_value - 1, checkpoint, trail_position);
1116            self.hole_updates
1117                .last_mut()
1118                .expect("we just pushed a value, so must be present")
1119                .triggered_upper_bound_update = true;
1120        }
1121
1122        true
1123    }
1124
1125    fn debug_is_valid_upper_bound_domain_update(
1126        &self,
1127        checkpoint: usize,
1128        trail_position: usize,
1129    ) -> bool {
1130        self.upper_bound_updates.last().unwrap().checkpoint <= checkpoint
1131            && self.upper_bound_updates.last().unwrap().trail_position < trail_position
1132    }
1133
1134    fn set_upper_bound(
1135        &mut self,
1136        new_upper_bound: i32,
1137        checkpoint: usize,
1138        trail_position: usize,
1139    ) -> bool {
1140        pumpkin_assert_moderate!(
1141            self.debug_is_valid_upper_bound_domain_update(checkpoint, trail_position)
1142        );
1143
1144        if new_upper_bound >= self.upper_bound() {
1145            return false;
1146        }
1147
1148        self.upper_bound_updates.push(BoundUpdateInfo {
1149            bound: new_upper_bound,
1150            checkpoint,
1151            trail_position,
1152        });
1153        self.update_upper_bound_with_respect_to_holes();
1154
1155        true
1156    }
1157
1158    fn update_upper_bound_with_respect_to_holes(&mut self) {
1159        while self.holes.contains_key(&self.upper_bound())
1160            && self.lower_bound() <= self.upper_bound()
1161        {
1162            self.upper_bound_updates.last_mut().unwrap().bound -= 1;
1163        }
1164    }
1165
1166    fn debug_is_valid_lower_bound_domain_update(
1167        &self,
1168        checkpoint: usize,
1169        trail_position: usize,
1170    ) -> bool {
1171        trail_position == 0
1172            || self.lower_bound_updates.last().unwrap().checkpoint <= checkpoint
1173                && self.lower_bound_updates.last().unwrap().trail_position < trail_position
1174    }
1175
1176    fn set_lower_bound(
1177        &mut self,
1178        new_lower_bound: i32,
1179        checkpoint: usize,
1180        trail_position: usize,
1181    ) -> bool {
1182        pumpkin_assert_moderate!(
1183            self.debug_is_valid_lower_bound_domain_update(checkpoint, trail_position)
1184        );
1185
1186        if new_lower_bound <= self.lower_bound() {
1187            return false;
1188        }
1189
1190        self.lower_bound_updates.push(BoundUpdateInfo {
1191            bound: new_lower_bound,
1192            checkpoint,
1193            trail_position,
1194        });
1195        self.update_lower_bound_with_respect_to_holes();
1196
1197        true
1198    }
1199
1200    fn update_lower_bound_with_respect_to_holes(&mut self) {
1201        while self.holes.contains_key(&self.lower_bound())
1202            && self.lower_bound() <= self.upper_bound()
1203        {
1204            self.lower_bound_updates.last_mut().unwrap().bound += 1;
1205        }
1206    }
1207
1208    fn debug_bounds_check(&self) -> bool {
1209        // If the domain is empty, the lower bound will be greater than the upper bound.
1210        if self.lower_bound() > self.upper_bound() {
1211            true
1212        } else {
1213            self.lower_bound() >= self.initial_lower_bound()
1214                && self.upper_bound() <= self.initial_upper_bound()
1215                && !self.holes.contains_key(&self.lower_bound())
1216                && !self.holes.contains_key(&self.upper_bound())
1217        }
1218    }
1219
1220    fn verify_consistency(&self) -> Result<bool, EmptyDomain> {
1221        if self.lower_bound() > self.upper_bound() {
1222            Err(EmptyDomain)
1223        } else {
1224            Ok(false)
1225        }
1226    }
1227
1228    fn undo_trail_entry(&mut self, entry: &ConstraintProgrammingTrailEntry) {
1229        let domain_id = entry.predicate.get_domain();
1230        match entry.predicate.get_predicate_type() {
1231            PredicateType::LowerBound => {
1232                pumpkin_assert_moderate!(domain_id == self.id);
1233
1234                let _ = self.lower_bound_updates.pop();
1235                pumpkin_assert_moderate!(!self.lower_bound_updates.is_empty());
1236            }
1237            PredicateType::UpperBound => {
1238                pumpkin_assert_moderate!(domain_id == self.id);
1239
1240                let _ = self.upper_bound_updates.pop();
1241                pumpkin_assert_moderate!(!self.upper_bound_updates.is_empty());
1242            }
1243            PredicateType::NotEqual => {
1244                pumpkin_assert_moderate!(domain_id == self.id);
1245
1246                let not_equal_constant = entry.predicate.get_right_hand_side();
1247
1248                let hole_update = self
1249                    .hole_updates
1250                    .pop()
1251                    .expect("Must have record of domain removal.");
1252                pumpkin_assert_moderate!(hole_update.removed_value == not_equal_constant);
1253
1254                let _ = self
1255                    .holes
1256                    .remove(&not_equal_constant)
1257                    .expect("Must be present.");
1258
1259                if hole_update.triggered_lower_bound_update {
1260                    let _ = self.lower_bound_updates.pop();
1261                    pumpkin_assert_moderate!(!self.lower_bound_updates.is_empty());
1262                }
1263
1264                if hole_update.triggered_upper_bound_update {
1265                    let _ = self.upper_bound_updates.pop();
1266                    pumpkin_assert_moderate!(!self.upper_bound_updates.is_empty());
1267                }
1268            }
1269            PredicateType::Equal => {
1270                let lower_bound_update = self.lower_bound_updates.last().unwrap();
1271                let upper_bound_update = self.upper_bound_updates.last().unwrap();
1272
1273                if lower_bound_update.trail_position > upper_bound_update.trail_position {
1274                    let _ = self.lower_bound_updates.pop();
1275                } else if upper_bound_update.trail_position > lower_bound_update.trail_position {
1276                    let _ = self.upper_bound_updates.pop();
1277                } else {
1278                    let _ = self.lower_bound_updates.pop();
1279                    let _ = self.upper_bound_updates.pop();
1280                }
1281            }
1282        };
1283
1284        // these asserts will be removed, for now it is a sanity check
1285        // later we may remove the old bound from the trail entry since it is not needed
1286        pumpkin_assert_eq_simple!(self.lower_bound(), entry.old_lower_bound);
1287        pumpkin_assert_eq_simple!(self.upper_bound(), entry.old_upper_bound);
1288
1289        pumpkin_assert_moderate!(self.debug_bounds_check());
1290    }
1291
1292    fn get_update_info(&self, predicate: &Predicate) -> Option<PairDecisionLevelTrailPosition> {
1293        // Perhaps the recursion could be done in a cleaner way,
1294        // e.g., separate functions dependibng on the type of predicate.
1295        // For the initial version, the current version is okay.
1296        let domain_id = predicate.get_domain();
1297        let value = predicate.get_right_hand_side();
1298
1299        match predicate.get_predicate_type() {
1300            PredicateType::LowerBound => {
1301                // Recall that by the nature of the updates,
1302                // the updates are stored in increasing order of the lower bound.
1303
1304                // find the update with smallest lower bound
1305                // that is greater than or equal to the input lower bound
1306                let position = self
1307                    .lower_bound_updates
1308                    .partition_point(|u| u.bound < value);
1309
1310                (position < self.lower_bound_updates.len()).then(|| {
1311                    let u = &self.lower_bound_updates[position];
1312                    PairDecisionLevelTrailPosition {
1313                        checkpoint: u.checkpoint,
1314                        trail_position: u.trail_position,
1315                    }
1316                })
1317            }
1318            PredicateType::UpperBound => {
1319                // Recall that by the nature of the updates,
1320                // the updates are stored in decreasing order of the upper bound.
1321
1322                // find the update with greatest upper bound
1323                // that is smaller than or equal to the input upper bound
1324                let position = self
1325                    .upper_bound_updates
1326                    .partition_point(|u| u.bound > value);
1327
1328                (position < self.upper_bound_updates.len()).then(|| {
1329                    let u = &self.upper_bound_updates[position];
1330                    PairDecisionLevelTrailPosition {
1331                        checkpoint: u.checkpoint,
1332                        trail_position: u.trail_position,
1333                    }
1334                })
1335            }
1336            PredicateType::NotEqual => {
1337                // Check the explictly stored holes.
1338                // If the value has been removed explicitly,
1339                // then the stored time is the first time the value was removed.
1340                if let Some(hole_info) = self.holes.get(&value) {
1341                    Some(*hole_info)
1342                } else {
1343                    // Otherwise, check the case when the lower/upper bound surpassed the value.
1344                    // If this never happened, then report that the predicate is not true.
1345
1346                    // Note that it cannot be that both the lower bound and upper bound surpassed
1347                    // the not equals constant, i.e., at most one of the two may happen.
1348                    // So we can stop as soon as we find one of the two.
1349
1350                    // Check the lower bound first.
1351                    if let Some(trail_position) =
1352                        self.get_update_info(&predicate!(domain_id >= value + 1))
1353                    {
1354                        // The lower bound removed the value from the domain,
1355                        // report the trail position of the lower bound.
1356                        Some(trail_position)
1357                    } else {
1358                        // The lower bound did not surpass the value,
1359                        // now check the upper bound.
1360                        self.get_update_info(&predicate!(domain_id <= value - 1))
1361                    }
1362                }
1363            }
1364            PredicateType::Equal => {
1365                // For equality to hold, both the lower and upper bound predicates must hold.
1366                // Check lower bound first.
1367                if let Some(lb_trail_position) =
1368                    self.get_update_info(&predicate!(domain_id >= value))
1369                {
1370                    // The lower bound found,
1371                    // now the check depends on the upper bound.
1372
1373                    // If both the lower and upper bounds are present,
1374                    // report the trail position of the bound that was set last.
1375                    // Otherwise, return that the predicate is not on the trail.
1376                    self.get_update_info(&predicate!(domain_id <= value))
1377                        .map(|ub_trail_position| {
1378                            if lb_trail_position.trail_position > ub_trail_position.trail_position {
1379                                lb_trail_position
1380                            } else {
1381                                ub_trail_position
1382                            }
1383                        })
1384                }
1385                // If the lower bound is never reached,
1386                // then surely the equality predicate cannot be true.
1387                else {
1388                    None
1389                }
1390            }
1391        }
1392    }
1393
1394    /// Returns the holes which were created on the provided decision level.
1395    pub(crate) fn get_holes_at_checkpoint(
1396        &self,
1397        checkpoint: usize,
1398    ) -> impl Iterator<Item = i32> + '_ {
1399        self.hole_updates
1400            .iter()
1401            .filter(move |entry| entry.checkpoint == checkpoint)
1402            .map(|entry| entry.removed_value)
1403    }
1404
1405    /// Returns the holes which were created on the current decision level.
1406    pub(crate) fn get_holes_from_current_checkpoint(
1407        &self,
1408        current_checkpoint: usize,
1409    ) -> impl Iterator<Item = i32> + '_ {
1410        self.hole_updates
1411            .iter()
1412            .rev()
1413            .take_while(move |entry| entry.checkpoint == current_checkpoint)
1414            .map(|entry| entry.removed_value)
1415    }
1416
1417    /// Returns all of the holes (currently) in the domain of `var` (including ones which were
1418    /// created at previous decision levels).
1419    pub(crate) fn get_holes(&self) -> impl Iterator<Item = i32> + '_ {
1420        self.holes.keys().copied()
1421    }
1422}
1423
1424#[derive(Debug)]
1425pub(crate) struct IntegerDomainIterator<'a> {
1426    domain: &'a IntegerDomain,
1427    current_value: i32,
1428}
1429
1430impl IntegerDomainIterator<'_> {
1431    fn new(domain: &IntegerDomain) -> IntegerDomainIterator<'_> {
1432        IntegerDomainIterator {
1433            domain,
1434            current_value: domain.lower_bound(),
1435        }
1436    }
1437}
1438
1439impl Iterator for IntegerDomainIterator<'_> {
1440    type Item = i32;
1441    fn next(&mut self) -> Option<i32> {
1442        // We would not expect to iterate through inconsistent domains,
1443        // although we support trying to do so. Not sure if this is good a idea?
1444        if self.domain.verify_consistency().is_err() {
1445            return None;
1446        }
1447
1448        // Note that the current value is never a hole. This is guaranteed by 1) having
1449        // a consistent domain, 2) the iterator starts with the lower bound,
1450        // and 3) the while loop after this if statement updates the current value
1451        // to a non-hole value (if there are any left within the bounds).
1452        let result = if self.current_value <= self.domain.upper_bound() {
1453            Some(self.current_value)
1454        } else {
1455            None
1456        };
1457
1458        self.current_value += 1;
1459        // If the current value is within the bounds, but is not in the domain,
1460        // linearly look for the next non-hole value.
1461        while self.current_value <= self.domain.upper_bound()
1462            && !self.domain.contains(self.current_value)
1463        {
1464            self.current_value += 1;
1465        }
1466        result
1467    }
1468}
1469
1470#[cfg(test)]
1471mod tests {
1472    use super::*;
1473    use crate::engine::notifications::DomainEvent;
1474
1475    #[test]
1476    fn jump_in_bound_change_lower_and_upper_bound_event_backtrack() {
1477        let mut notification_engine = NotificationEngine::test_default();
1478        let mut assignment = Assignments::default();
1479        let d1 = assignment.grow(1, 5);
1480        notification_engine.grow();
1481
1482        assignment.new_checkpoint();
1483
1484        let _ = assignment
1485            .post_predicate(predicate!(d1 != 1), None, &mut notification_engine)
1486            .expect("non-empty domain");
1487        let _ = assignment
1488            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1489            .expect("non-empty domain");
1490
1491        let _ = assignment.synchronise(0, &mut notification_engine);
1492
1493        let events = notification_engine
1494            .drain_backtrack_domain_events()
1495            .collect::<Vec<_>>();
1496        assert_eq!(events.len(), 3);
1497
1498        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1499        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1500        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1501    }
1502
1503    #[test]
1504    fn jump_in_bound_change_assign_event_backtrack() {
1505        let mut notification_engine = NotificationEngine::test_default();
1506        let mut assignment = Assignments::default();
1507        let d1 = assignment.grow(1, 5);
1508        notification_engine.grow();
1509
1510        assignment.new_checkpoint();
1511
1512        let _ = assignment
1513            .post_predicate(predicate!(d1 != 2), None, &mut notification_engine)
1514            .expect("non-empty domain");
1515        let _ = assignment
1516            .post_predicate(predicate!(d1 != 3), None, &mut notification_engine)
1517            .expect("non-empty domain");
1518        let _ = assignment
1519            .post_predicate(predicate!(d1 != 4), None, &mut notification_engine)
1520            .expect("non-empty domain");
1521        let _ = assignment
1522            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1523            .expect("non-empty domain");
1524        let _ = assignment
1525            .post_predicate(predicate!(d1 != 1), None, &mut notification_engine)
1526            .expect_err("empty domain");
1527
1528        let _ = assignment.synchronise(0, &mut notification_engine);
1529
1530        let events = notification_engine
1531            .drain_backtrack_domain_events()
1532            .collect::<Vec<_>>();
1533        assert_eq!(events.len(), 4);
1534
1535        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1536        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1537        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1538        assert_contains_events(&events, d1, [DomainEvent::Assign]);
1539    }
1540
1541    #[test]
1542    fn jump_in_bound_change_upper_bound_event_backtrack() {
1543        let mut notification_engine = NotificationEngine::test_default();
1544        let mut assignment = Assignments::default();
1545        let d1 = assignment.grow(1, 5);
1546        notification_engine.grow();
1547
1548        assignment.new_checkpoint();
1549
1550        let _ = assignment
1551            .post_predicate(predicate!(d1 != 3), None, &mut notification_engine)
1552            .expect("non-empty domain");
1553        let _ = assignment
1554            .post_predicate(predicate!(d1 != 4), None, &mut notification_engine)
1555            .expect("non-empty domain");
1556        let _ = assignment
1557            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1558            .expect("non-empty domain");
1559
1560        let _ = assignment.synchronise(0, &mut notification_engine);
1561
1562        let events = notification_engine
1563            .drain_backtrack_domain_events()
1564            .collect::<Vec<_>>();
1565        assert_eq!(events.len(), 2);
1566
1567        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1568        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1569    }
1570
1571    #[test]
1572    fn jump_in_bound_change_lower_bound_event_backtrack() {
1573        let mut notification_engine = NotificationEngine::test_default();
1574        let mut assignment = Assignments::default();
1575        let d1 = assignment.grow(1, 5);
1576        notification_engine.grow();
1577
1578        assignment.new_checkpoint();
1579
1580        let _ = assignment
1581            .remove_value_from_domain(d1, 3, None)
1582            .expect("non-empty domain");
1583        let _ = assignment
1584            .remove_value_from_domain(d1, 2, None)
1585            .expect("non-empty domain");
1586        let _ = assignment
1587            .remove_value_from_domain(d1, 1, None)
1588            .expect("non-empty domain");
1589
1590        let _ = assignment.synchronise(0, &mut notification_engine);
1591
1592        let events = notification_engine
1593            .drain_backtrack_domain_events()
1594            .collect::<Vec<_>>();
1595        assert_eq!(events.len(), 2);
1596
1597        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1598        assert_contains_events(&events, d1, [DomainEvent::Removal]);
1599    }
1600
1601    #[test]
1602    fn lower_bound_change_lower_bound_event() {
1603        let mut notification_engine = NotificationEngine::default();
1604        let mut assignment = Assignments::default();
1605        let d1 = assignment.grow(1, 5);
1606        notification_engine.grow();
1607
1608        let _ = assignment
1609            .post_predicate(predicate!(d1 >= 2), None, &mut notification_engine)
1610            .expect("non-empty domain");
1611
1612        let events = notification_engine
1613            .drain_domain_events()
1614            .collect::<Vec<_>>();
1615        assert_eq!(events.len(), 1);
1616
1617        assert_contains_events(&events, d1, [DomainEvent::LowerBound]);
1618    }
1619
1620    #[test]
1621    fn upper_bound_change_triggers_upper_bound_event() {
1622        let mut notification_engine = NotificationEngine::default();
1623        let mut assignment = Assignments::default();
1624        let d1 = assignment.grow(1, 5);
1625        notification_engine.grow();
1626
1627        let _ = assignment
1628            .post_predicate(predicate!(d1 <= 2), None, &mut notification_engine)
1629            .expect("non-empty domain");
1630
1631        let events = notification_engine
1632            .drain_domain_events()
1633            .collect::<Vec<_>>();
1634        assert_eq!(events.len(), 1);
1635        assert_contains_events(&events, d1, [DomainEvent::UpperBound]);
1636    }
1637
1638    #[test]
1639    fn bounds_change_can_also_trigger_assign_event() {
1640        let mut notification_engine = NotificationEngine::default();
1641        let mut assignment = Assignments::default();
1642
1643        let d1 = assignment.grow(1, 5);
1644        let d2 = assignment.grow(1, 5);
1645        notification_engine.grow();
1646        notification_engine.grow();
1647
1648        let _ = assignment
1649            .post_predicate(predicate!(d1 >= 5), None, &mut notification_engine)
1650            .expect("non-empty domain");
1651        let _ = assignment
1652            .post_predicate(predicate!(d2 <= 1), None, &mut notification_engine)
1653            .expect("non-empty domain");
1654
1655        let events = notification_engine
1656            .drain_domain_events()
1657            .collect::<Vec<_>>();
1658        assert_eq!(events.len(), 4, "expected more than 4 events: {events:?}");
1659
1660        assert_contains_events(&events, d1, [DomainEvent::LowerBound, DomainEvent::Assign]);
1661        assert_contains_events(&events, d2, [DomainEvent::UpperBound, DomainEvent::Assign]);
1662    }
1663
1664    #[test]
1665    fn making_assignment_triggers_appropriate_events() {
1666        let mut notification_engine = NotificationEngine::default();
1667        let mut assignment = Assignments::default();
1668
1669        let d1 = assignment.grow(1, 5);
1670        let d2 = assignment.grow(1, 5);
1671        let d3 = assignment.grow(1, 5);
1672        notification_engine.grow();
1673        notification_engine.grow();
1674        notification_engine.grow();
1675
1676        let _ = assignment
1677            .post_predicate(predicate!(d1 == 1), None, &mut notification_engine)
1678            .expect("non-empty domain");
1679        let _ = assignment
1680            .post_predicate(predicate!(d2 == 5), None, &mut notification_engine)
1681            .expect("non-empty domain");
1682        let _ = assignment
1683            .post_predicate(predicate!(d3 == 3), None, &mut notification_engine)
1684            .expect("non-empty domain");
1685
1686        let events = notification_engine
1687            .drain_domain_events()
1688            .collect::<Vec<_>>();
1689        assert_eq!(events.len(), 7);
1690
1691        assert_contains_events(&events, d1, [DomainEvent::Assign, DomainEvent::UpperBound]);
1692        assert_contains_events(&events, d2, [DomainEvent::Assign, DomainEvent::LowerBound]);
1693        assert_contains_events(
1694            &events,
1695            d3,
1696            [
1697                DomainEvent::Assign,
1698                DomainEvent::LowerBound,
1699                DomainEvent::UpperBound,
1700            ],
1701        );
1702    }
1703
1704    #[test]
1705    fn removal_triggers_removal_event() {
1706        let mut notification_engine = NotificationEngine::default();
1707        let mut assignment = Assignments::default();
1708        let d1 = assignment.grow(1, 5);
1709        notification_engine.grow();
1710
1711        let _ = assignment
1712            .post_predicate(predicate!(d1 != 2), None, &mut notification_engine)
1713            .expect("non-empty domain");
1714
1715        let events = notification_engine
1716            .drain_domain_events()
1717            .collect::<Vec<_>>();
1718        assert_eq!(events.len(), 1);
1719        assert!(events.contains(&(DomainEvent::Removal, d1)));
1720    }
1721
1722    #[test]
1723    fn value_can_be_removed_from_domains() {
1724        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1725        let _ = domain.remove_value(1, 1, 2);
1726
1727        assert!(domain.contains(2));
1728        assert!(!domain.contains(1));
1729    }
1730
1731    #[test]
1732    fn removing_the_lower_bound_updates_that_lower_bound() {
1733        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1734        let _ = domain.remove_value(1, 1, 1);
1735        let _ = domain.remove_value(2, 1, 2);
1736
1737        assert_eq!(3, domain.lower_bound());
1738    }
1739
1740    #[test]
1741    fn removing_the_upper_bound_updates_the_upper_bound() {
1742        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1743        let _ = domain.remove_value(4, 0, 1);
1744        let _ = domain.remove_value(5, 0, 2);
1745
1746        assert_eq!(3, domain.upper_bound());
1747    }
1748
1749    #[test]
1750    fn an_empty_domain_accepts_removal_operations() {
1751        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1752        let _ = domain.remove_value(4, 0, 1);
1753        let _ = domain.remove_value(1, 0, 2);
1754        let _ = domain.remove_value(1, 0, 3);
1755    }
1756
1757    #[test]
1758    fn setting_lower_bound_rounds_up_to_nearest_value_in_domain() {
1759        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1760        let _ = domain.remove_value(2, 1, 2);
1761        let _ = domain.remove_value(3, 1, 3);
1762        let _ = domain.set_lower_bound(2, 1, 4);
1763
1764        assert_eq!(4, domain.lower_bound());
1765    }
1766
1767    #[test]
1768    fn setting_upper_bound_rounds_down_to_nearest_value_in_domain() {
1769        let mut domain = IntegerDomain::new(1, 0, 5, 1, DomainId::new(0));
1770        let _ = domain.remove_value(4, 0, 1);
1771        let _ = domain.set_upper_bound(4, 0, 2);
1772
1773        assert_eq!(3, domain.upper_bound());
1774    }
1775
1776    #[test]
1777    fn undo_removal_at_bounds_indexes_into_values_domain_correctly() {
1778        let mut notification_engine = NotificationEngine::default();
1779        let mut assignment = Assignments::default();
1780        let d1 = assignment.grow(1, 5);
1781        notification_engine.grow();
1782
1783        assignment.new_checkpoint();
1784
1785        let _ = assignment
1786            .post_predicate(predicate!(d1 != 5), None, &mut notification_engine)
1787            .expect("non-empty domain");
1788
1789        let _ = assignment.synchronise(0, &mut notification_engine);
1790
1791        assert_eq!(5, assignment.get_upper_bound(d1));
1792    }
1793
1794    fn assert_contains_events(
1795        slice: &[(DomainEvent, DomainId)],
1796        domain: DomainId,
1797        required_events: impl IntoIterator<Item = DomainEvent>,
1798    ) {
1799        for event in required_events {
1800            assert!(slice.contains(&(event, domain)));
1801        }
1802    }
1803
1804    fn get_domain1() -> (DomainId, IntegerDomain) {
1805        let domain_id = DomainId::new(0);
1806        let mut domain = IntegerDomain::new(0, 0, 100, 1, domain_id);
1807        let _ = domain.set_lower_bound(1, 0, 1);
1808        let _ = domain.set_lower_bound(5, 1, 2);
1809        let _ = domain.set_lower_bound(10, 2, 10);
1810        let _ = domain.set_lower_bound(20, 5, 50);
1811        let _ = domain.set_lower_bound(50, 10, 70);
1812
1813        (domain_id, domain)
1814    }
1815
1816    #[test]
1817    fn lower_bound_trail_position_inbetween_value() {
1818        let (domain_id, domain) = get_domain1();
1819
1820        assert_eq!(
1821            domain
1822                .get_update_info(&predicate!(domain_id >= 12))
1823                .unwrap()
1824                .trail_position,
1825            50
1826        );
1827    }
1828
1829    #[test]
1830    fn lower_bound_trail_position_last_bound() {
1831        let (domain_id, domain) = get_domain1();
1832
1833        assert_eq!(
1834            domain
1835                .get_update_info(&predicate!(domain_id >= 50))
1836                .unwrap()
1837                .trail_position,
1838            70
1839        );
1840    }
1841
1842    #[test]
1843    fn lower_bound_trail_position_beyond_value() {
1844        let (domain_id, domain) = get_domain1();
1845
1846        assert!(
1847            domain
1848                .get_update_info(&predicate!(domain_id >= 101))
1849                .is_none()
1850        );
1851    }
1852
1853    #[test]
1854    fn lower_bound_trail_position_trivial() {
1855        let (domain_id, domain) = get_domain1();
1856
1857        assert_eq!(
1858            domain
1859                .get_update_info(&predicate!(domain_id >= -10))
1860                .unwrap()
1861                .trail_position,
1862            0
1863        );
1864    }
1865
1866    #[test]
1867    fn lower_bound_trail_position_with_removals() {
1868        let (domain_id, mut domain) = get_domain1();
1869        let _ = domain.remove_value(50, 11, 75);
1870        let _ = domain.remove_value(51, 11, 77);
1871        let _ = domain.remove_value(52, 11, 80);
1872
1873        assert_eq!(
1874            domain
1875                .get_update_info(&predicate!(domain_id >= 52))
1876                .unwrap()
1877                .trail_position,
1878            77
1879        );
1880    }
1881
1882    #[test]
1883    fn removal_trail_position() {
1884        let (domain_id, mut domain) = get_domain1();
1885        let _ = domain.remove_value(50, 11, 75);
1886        let _ = domain.remove_value(51, 11, 77);
1887        let _ = domain.remove_value(52, 11, 80);
1888
1889        assert_eq!(
1890            domain
1891                .get_update_info(&predicate!(domain_id != 50))
1892                .unwrap()
1893                .trail_position,
1894            75
1895        );
1896    }
1897
1898    #[test]
1899    fn removal_trail_position_after_lower_bound() {
1900        let (domain_id, mut domain) = get_domain1();
1901        let _ = domain.remove_value(50, 11, 75);
1902        let _ = domain.remove_value(51, 11, 77);
1903        let _ = domain.remove_value(52, 11, 80);
1904        let _ = domain.set_lower_bound(60, 11, 150);
1905
1906        assert_eq!(
1907            domain
1908                .get_update_info(&predicate!(domain_id != 55))
1909                .unwrap()
1910                .trail_position,
1911            150
1912        );
1913    }
1914
1915    #[test]
1916    fn lower_bound_change_backtrack() {
1917        let mut notification_engine = NotificationEngine::default();
1918        let mut assignment = Assignments::default();
1919        let domain_id1 = assignment.grow(0, 100);
1920        let domain_id2 = assignment.grow(0, 50);
1921        notification_engine.grow();
1922        notification_engine.grow();
1923
1924        // decision level 1
1925        assignment.new_checkpoint();
1926        let _ = assignment
1927            .post_predicate(predicate!(domain_id1 >= 2), None, &mut notification_engine)
1928            .expect("");
1929        let _ = assignment
1930            .post_predicate(predicate!(domain_id2 >= 25), None, &mut notification_engine)
1931            .expect("");
1932
1933        // decision level 2
1934        assignment.new_checkpoint();
1935        let _ = assignment
1936            .post_predicate(predicate!(domain_id1 >= 5), None, &mut notification_engine)
1937            .expect("");
1938
1939        // decision level 3
1940        assignment.new_checkpoint();
1941        let _ = assignment
1942            .post_predicate(predicate!(domain_id1 >= 7), None, &mut notification_engine)
1943            .expect("");
1944
1945        assert_eq!(assignment.get_lower_bound(domain_id1), 7);
1946
1947        let _ = assignment.synchronise(1, &mut notification_engine);
1948
1949        assert_eq!(assignment.get_lower_bound(domain_id1), 2);
1950    }
1951
1952    #[test]
1953    fn lower_bound_inbetween_updates() {
1954        let (_, domain) = get_domain1();
1955        assert_eq!(domain.lower_bound_at_trail_position(25), 10);
1956    }
1957
1958    #[test]
1959    fn lower_bound_beyond_trail_position() {
1960        let (_, domain) = get_domain1();
1961        assert_eq!(domain.lower_bound_at_trail_position(1000), 50);
1962    }
1963
1964    #[test]
1965    fn lower_bound_at_update() {
1966        let (_, domain) = get_domain1();
1967        assert_eq!(domain.lower_bound_at_trail_position(50), 20);
1968    }
1969
1970    #[test]
1971    fn lower_bound_at_trail_position_after_removals() {
1972        let (_, mut domain) = get_domain1();
1973        let _ = domain.remove_value(50, 11, 75);
1974        let _ = domain.remove_value(51, 11, 77);
1975        let _ = domain.remove_value(52, 11, 80);
1976
1977        assert_eq!(domain.lower_bound_at_trail_position(77), 52);
1978    }
1979
1980    #[test]
1981    fn lower_bound_at_trail_position_after_removals_and_bound_update() {
1982        let (_, mut domain) = get_domain1();
1983        let _ = domain.remove_value(50, 11, 75);
1984        let _ = domain.remove_value(51, 11, 77);
1985        let _ = domain.remove_value(52, 11, 80);
1986        let _ = domain.set_lower_bound(60, 11, 150);
1987
1988        assert_eq!(domain.lower_bound_at_trail_position(100), 53);
1989    }
1990
1991    #[test]
1992    fn inconsistent_bound_updates() {
1993        let domain_id = DomainId::new(0);
1994        let mut domain = IntegerDomain::new(0, 0, 2, 1, domain_id);
1995        let _ = domain.set_lower_bound(2, 1, 1);
1996        let _ = domain.set_upper_bound(1, 1, 2);
1997        assert!(domain.verify_consistency().is_err());
1998    }
1999
2000    #[test]
2001    fn inconsistent_domain_removals() {
2002        let domain_id = DomainId::new(0);
2003        let mut domain = IntegerDomain::new(0, 0, 2, 1, domain_id);
2004        let _ = domain.remove_value(1, 1, 1);
2005        let _ = domain.remove_value(2, 1, 2);
2006        let _ = domain.remove_value(0, 1, 3);
2007        assert!(domain.verify_consistency().is_err());
2008    }
2009
2010    #[test]
2011    fn domain_iterator_simple() {
2012        let domain_id = DomainId::new(0);
2013        let domain = IntegerDomain::new(0, 0, 5, 1, domain_id);
2014        let mut iter = domain.domain_iterator();
2015        assert_eq!(iter.next(), Some(0));
2016        assert_eq!(iter.next(), Some(1));
2017        assert_eq!(iter.next(), Some(2));
2018        assert_eq!(iter.next(), Some(3));
2019        assert_eq!(iter.next(), Some(4));
2020        assert_eq!(iter.next(), Some(5));
2021        assert_eq!(iter.next(), None);
2022    }
2023
2024    #[test]
2025    fn domain_iterator_skip_holes() {
2026        let domain_id = DomainId::new(0);
2027        let mut domain = IntegerDomain::new(0, 0, 5, 1, domain_id);
2028        let _ = domain.remove_value(1, 0, 5);
2029        let _ = domain.remove_value(4, 0, 10);
2030
2031        let mut iter = domain.domain_iterator();
2032        assert_eq!(iter.next(), Some(0));
2033        assert_eq!(iter.next(), Some(2));
2034        assert_eq!(iter.next(), Some(3));
2035        assert_eq!(iter.next(), Some(5));
2036        assert_eq!(iter.next(), None);
2037    }
2038
2039    #[test]
2040    fn domain_iterator_removed_bounds() {
2041        let domain_id = DomainId::new(0);
2042        let mut domain = IntegerDomain::new(0, 0, 5, 1, domain_id);
2043        let _ = domain.remove_value(0, 0, 1);
2044        let _ = domain.remove_value(5, 0, 10);
2045
2046        let mut iter = domain.domain_iterator();
2047        assert_eq!(iter.next(), Some(1));
2048        assert_eq!(iter.next(), Some(2));
2049        assert_eq!(iter.next(), Some(3));
2050        assert_eq!(iter.next(), Some(4));
2051        assert_eq!(iter.next(), None);
2052    }
2053
2054    #[test]
2055    fn domain_iterator_removed_values_present_beyond_bounds() {
2056        let domain_id = DomainId::new(0);
2057        let mut domain = IntegerDomain::new(0, 0, 10, 1, domain_id);
2058        let _ = domain.remove_value(7, 0, 1);
2059        let _ = domain.remove_value(9, 0, 5);
2060        let _ = domain.remove_value(2, 0, 10);
2061        let _ = domain.set_upper_bound(6, 1, 10);
2062
2063        let mut iter = domain.domain_iterator();
2064        assert_eq!(iter.next(), Some(0));
2065        assert_eq!(iter.next(), Some(1));
2066        assert_eq!(iter.next(), Some(3));
2067        assert_eq!(iter.next(), Some(4));
2068        assert_eq!(iter.next(), Some(5));
2069        assert_eq!(iter.next(), Some(6));
2070        assert_eq!(iter.next(), None);
2071    }
2072
2073    #[test]
2074    fn various_tests_evaluate_predicate() {
2075        let mut notification_engine = NotificationEngine::default();
2076        let mut assignments = Assignments::default();
2077        // Create the domain {0, 1, 3, 4, 5, 6}
2078        let domain_id = assignments.grow(0, 10);
2079        notification_engine.grow();
2080
2081        let _ =
2082            assignments.post_predicate(predicate!(domain_id != 7), None, &mut notification_engine);
2083        let _ =
2084            assignments.post_predicate(predicate!(domain_id != 9), None, &mut notification_engine);
2085        let _ =
2086            assignments.post_predicate(predicate!(domain_id != 2), None, &mut notification_engine);
2087        let _ =
2088            assignments.post_predicate(predicate!(domain_id <= 6), None, &mut notification_engine);
2089
2090        let lb_predicate = |lower_bound: i32| -> Predicate { predicate!(domain_id >= lower_bound) };
2091        let ub_predicate = |upper_bound: i32| -> Predicate { predicate!(domain_id <= upper_bound) };
2092        let eq_predicate =
2093            |equality_constant: i32| -> Predicate { predicate!(domain_id == equality_constant) };
2094        let neq_predicate =
2095            |not_equal_constant: i32| -> Predicate { predicate!(domain_id != not_equal_constant) };
2096
2097        assert!(
2098            assignments
2099                .evaluate_predicate(lb_predicate(0))
2100                .is_some_and(|x| x)
2101        );
2102        assert!(assignments.evaluate_predicate(lb_predicate(1)).is_none());
2103        assert!(assignments.evaluate_predicate(lb_predicate(2)).is_none());
2104        assert!(assignments.evaluate_predicate(lb_predicate(3)).is_none());
2105        assert!(assignments.evaluate_predicate(lb_predicate(4)).is_none());
2106        assert!(assignments.evaluate_predicate(lb_predicate(5)).is_none());
2107        assert!(assignments.evaluate_predicate(lb_predicate(6)).is_none());
2108        assert!(
2109            assignments
2110                .evaluate_predicate(lb_predicate(7))
2111                .is_some_and(|x| !x)
2112        );
2113        assert!(
2114            assignments
2115                .evaluate_predicate(lb_predicate(8))
2116                .is_some_and(|x| !x)
2117        );
2118        assert!(
2119            assignments
2120                .evaluate_predicate(lb_predicate(9))
2121                .is_some_and(|x| !x)
2122        );
2123        assert!(
2124            assignments
2125                .evaluate_predicate(lb_predicate(10))
2126                .is_some_and(|x| !x)
2127        );
2128
2129        assert!(assignments.evaluate_predicate(ub_predicate(0)).is_none());
2130        assert!(assignments.evaluate_predicate(ub_predicate(1)).is_none());
2131        assert!(assignments.evaluate_predicate(ub_predicate(2)).is_none());
2132        assert!(assignments.evaluate_predicate(ub_predicate(3)).is_none());
2133        assert!(assignments.evaluate_predicate(ub_predicate(4)).is_none());
2134        assert!(assignments.evaluate_predicate(ub_predicate(5)).is_none());
2135        assert!(
2136            assignments
2137                .evaluate_predicate(ub_predicate(6))
2138                .is_some_and(|x| x)
2139        );
2140        assert!(
2141            assignments
2142                .evaluate_predicate(ub_predicate(7))
2143                .is_some_and(|x| x)
2144        );
2145        assert!(
2146            assignments
2147                .evaluate_predicate(ub_predicate(8))
2148                .is_some_and(|x| x)
2149        );
2150        assert!(
2151            assignments
2152                .evaluate_predicate(ub_predicate(9))
2153                .is_some_and(|x| x)
2154        );
2155        assert!(
2156            assignments
2157                .evaluate_predicate(ub_predicate(10))
2158                .is_some_and(|x| x)
2159        );
2160
2161        assert!(assignments.evaluate_predicate(neq_predicate(0)).is_none());
2162        assert!(assignments.evaluate_predicate(neq_predicate(1)).is_none());
2163        assert!(
2164            assignments
2165                .evaluate_predicate(neq_predicate(2))
2166                .is_some_and(|x| x)
2167        );
2168        assert!(assignments.evaluate_predicate(neq_predicate(3)).is_none());
2169        assert!(assignments.evaluate_predicate(neq_predicate(4)).is_none());
2170        assert!(assignments.evaluate_predicate(neq_predicate(5)).is_none());
2171        assert!(assignments.evaluate_predicate(neq_predicate(6)).is_none());
2172        assert!(
2173            assignments
2174                .evaluate_predicate(neq_predicate(7))
2175                .is_some_and(|x| x)
2176        );
2177        assert!(
2178            assignments
2179                .evaluate_predicate(neq_predicate(8))
2180                .is_some_and(|x| x)
2181        );
2182        assert!(
2183            assignments
2184                .evaluate_predicate(neq_predicate(9))
2185                .is_some_and(|x| x)
2186        );
2187        assert!(
2188            assignments
2189                .evaluate_predicate(neq_predicate(10))
2190                .is_some_and(|x| x)
2191        );
2192
2193        assert!(assignments.evaluate_predicate(eq_predicate(0)).is_none());
2194        assert!(assignments.evaluate_predicate(eq_predicate(1)).is_none());
2195        assert!(
2196            assignments
2197                .evaluate_predicate(eq_predicate(2))
2198                .is_some_and(|x| !x)
2199        );
2200        assert!(assignments.evaluate_predicate(eq_predicate(3)).is_none());
2201        assert!(assignments.evaluate_predicate(eq_predicate(4)).is_none());
2202        assert!(assignments.evaluate_predicate(eq_predicate(5)).is_none());
2203        assert!(assignments.evaluate_predicate(eq_predicate(6)).is_none());
2204        assert!(
2205            assignments
2206                .evaluate_predicate(eq_predicate(7))
2207                .is_some_and(|x| !x)
2208        );
2209        assert!(
2210            assignments
2211                .evaluate_predicate(eq_predicate(8))
2212                .is_some_and(|x| !x)
2213        );
2214        assert!(
2215            assignments
2216                .evaluate_predicate(eq_predicate(9))
2217                .is_some_and(|x| !x)
2218        );
2219        assert!(
2220            assignments
2221                .evaluate_predicate(eq_predicate(10))
2222                .is_some_and(|x| !x)
2223        );
2224
2225        let _ =
2226            assignments.post_predicate(predicate!(domain_id >= 6), None, &mut notification_engine);
2227
2228        assert!(
2229            assignments
2230                .evaluate_predicate(neq_predicate(6))
2231                .is_some_and(|x| !x)
2232        );
2233        assert!(
2234            assignments
2235                .evaluate_predicate(eq_predicate(6))
2236                .is_some_and(|x| x)
2237        );
2238        assert!(
2239            assignments
2240                .evaluate_predicate(lb_predicate(6))
2241                .is_some_and(|x| x)
2242        );
2243        assert!(
2244            assignments
2245                .evaluate_predicate(ub_predicate(6))
2246                .is_some_and(|x| x)
2247        );
2248    }
2249}