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 bounds: KeyedVec<DomainId, (i32, i32)>,
23 domains: KeyedVec<DomainId, IntegerDomain>,
24 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 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 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 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 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 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 pub(crate) fn grow(&mut self, lower_bound: i32, upper_bound: i32) -> DomainId {
118 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 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
230impl 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 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 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 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 for hole in &self.domains[domain_id].holes {
330 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 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 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
397impl 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 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 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 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 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 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 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 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 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 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 pumpkin_assert_simple!(assigned_value == value);
648 Some(false)
649 } else {
650 None
651 }
652 }
653 PredicateType::Equal => {
654 if !self.is_value_in_domain(domain_id, value) {
655 Some(false)
656 } else if let Some(assigned_value) = self.get_assigned_value(&domain_id) {
657 pumpkin_assert_moderate!(assigned_value == value);
658 Some(true)
659 } else {
660 None
661 }
662 }
663 }
664 }
665
666 pub(crate) fn 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 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 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 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 unfixed_variables.push((domain_id, lower_bound_before));
815 }
816 });
817
818 notification_engine.clear_events();
821
822 unfixed_variables
823 }
824
825 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 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 pub(crate) old_lower_bound: i32,
872 pub(crate) old_upper_bound: i32,
873 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#[derive(Clone, Debug)]
908struct IntegerDomain {
909 id: DomainId,
910 lower_bound_updates: Vec<BoundUpdateInfo>,
912 upper_bound_updates: Vec<BoundUpdateInfo>,
913 hole_updates: Vec<HoleUpdateInfo>,
914 holes: HashMap<i32, PairDecisionLevelTrailPosition>,
919 initial_bounds_below_trail: usize,
921 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 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 self.lower_bound_updates[0].bound
977 }
978
979 fn lower_bound_at_trail_position(&self, trail_position: usize) -> i32 {
980 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 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 self.upper_bound_updates[0].bound
1017 }
1018
1019 fn upper_bound_at_trail_position(&self, trail_position: usize) -> i32 {
1020 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 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 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 if let Some(hole_info) = self.holes.get(&value)
1066 && hole_info.trail_position <= trail_position
1067 {
1068 return false;
1069 }
1070
1071 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 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 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 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 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(¬_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 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 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 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 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 if let Some(hole_info) = self.holes.get(&value) {
1341 Some(*hole_info)
1342 } else {
1343 if let Some(trail_position) =
1352 self.get_update_info(&predicate!(domain_id >= value + 1))
1353 {
1354 Some(trail_position)
1357 } else {
1358 self.get_update_info(&predicate!(domain_id <= value - 1))
1361 }
1362 }
1363 }
1364 PredicateType::Equal => {
1365 if let Some(lb_trail_position) =
1368 self.get_update_info(&predicate!(domain_id >= value))
1369 {
1370 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 else {
1388 None
1389 }
1390 }
1391 }
1392 }
1393
1394 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 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 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 if self.domain.verify_consistency().is_err() {
1445 return None;
1446 }
1447
1448 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 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 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 assignment.new_checkpoint();
1935 let _ = assignment
1936 .post_predicate(predicate!(domain_id1 >= 5), None, &mut notification_engine)
1937 .expect("");
1938
1939 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 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}