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