Skip to main content

pumpkin_core/engine/predicates/
predicate.rs

1use enumset::EnumSetType;
2use pumpkin_checking::AtomicConstraint;
3
4use crate::engine::Assignments;
5use crate::engine::variables::DomainId;
6use crate::predicate;
7use crate::propagation::DomainEvent;
8
9/// Representation of a domain operation, also known as an atomic constraint. It is a triple
10/// ([`DomainId`], [`PredicateType`], value).
11///
12/// To create a [`Predicate`], use [Predicate::new] or the more concise [predicate!] macro.
13///
14/// ## Order
15/// Predicates have a well-defined order. They are first ordered by the domain, and then by
16/// predicate type, and finally by the value. The order is chosen such that for a fixed domain `x`,
17/// predicates are ordered as follows:
18/// [>= 5], [>= 7], [!= 2], [!= 3], [== 5], [!= 7], [<= 6], [<= 10]
19///
20/// From the order, we get the lower-bound predicates first, ordered by non-decreasing bound, then
21/// the (not-)equal predicates, ordered by non-decreasing bound, then the upper-bound predicates,
22/// ordered by non-increasing bounds.
23#[derive(Clone, PartialEq, Eq, Copy, Hash)]
24pub struct Predicate {
25    /// The two most significant bits of the id stored in the [`Predicate`] contains the type of
26    /// predicate.
27    id: u32,
28    value: i32,
29}
30
31const LOWER_BOUND_CODE: u8 = PredicateType::LowerBound as u8;
32const UPPER_BOUND_CODE: u8 = PredicateType::UpperBound as u8;
33const NOT_EQUAL_CODE: u8 = PredicateType::NotEqual as u8;
34const EQUAL_CODE: u8 = PredicateType::Equal as u8;
35
36impl Predicate {
37    /// Creates a new [`Predicate`] (also known as atomic constraint) which represents a domain
38    /// operation.
39    pub fn new(id: DomainId, predicate_type: PredicateType, value: i32) -> Self {
40        let code = predicate_type as u8;
41        let id = id.id() | (code as u32) << 30;
42        Self { id, value }
43    }
44
45    /// Returns `true` if `self` implies `other`.
46    ///
47    /// # Example
48    /// ```
49    /// # use pumpkin_core::variables::DomainId;
50    /// # use pumpkin_core::predicate;
51    /// let x = DomainId::new(0);
52    ///
53    /// assert!(predicate![x >= 5].implies(predicate![x >= 3]));
54    /// assert!(predicate![x >= 5].implies(predicate![x != 1]));
55    /// assert!(predicate![x == 5].implies(predicate![x <= 5]));
56    /// ```
57    pub fn implies(&self, other: Predicate) -> bool {
58        if self.get_domain() != other.get_domain() {
59            // Predicates only imply other predicates on the same domain.
60            return false;
61        }
62
63        match self.get_predicate_type() {
64            PredicateType::LowerBound => match other.get_predicate_type() {
65                PredicateType::LowerBound => {
66                    self.get_right_hand_side() >= other.get_right_hand_side()
67                }
68                PredicateType::NotEqual => self.get_right_hand_side() > other.get_right_hand_side(),
69                PredicateType::UpperBound | PredicateType::Equal => false,
70            },
71            PredicateType::UpperBound => match other.get_predicate_type() {
72                PredicateType::UpperBound => {
73                    self.get_right_hand_side() <= other.get_right_hand_side()
74                }
75                PredicateType::NotEqual => self.get_right_hand_side() < other.get_right_hand_side(),
76                PredicateType::LowerBound | PredicateType::Equal => false,
77            },
78            PredicateType::NotEqual => {
79                other.get_predicate_type() == PredicateType::NotEqual
80                    && self.get_right_hand_side() == other.get_right_hand_side()
81            }
82            PredicateType::Equal => match other.get_predicate_type() {
83                PredicateType::LowerBound => {
84                    self.get_right_hand_side() >= other.get_right_hand_side()
85                }
86                PredicateType::UpperBound => {
87                    self.get_right_hand_side() <= other.get_right_hand_side()
88                }
89                PredicateType::NotEqual => {
90                    self.get_right_hand_side() != other.get_right_hand_side()
91                }
92                PredicateType::Equal => self.get_right_hand_side() == other.get_right_hand_side(),
93            },
94        }
95    }
96
97    fn get_type_code(&self) -> u8 {
98        (self.id >> 30) as u8
99    }
100
101    pub fn get_predicate_type(&self) -> PredicateType {
102        (*self).into()
103    }
104
105    fn is_bound_predicate(&self) -> bool {
106        self.is_upper_bound_predicate() || self.is_lower_bound_predicate()
107    }
108}
109
110impl PartialOrd for Predicate {
111    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
112        Some(self.cmp(other))
113    }
114}
115
116impl Ord for Predicate {
117    /// See [`Predicate`] for details on the order.
118    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
119        match self.get_domain().cmp(&other.get_domain()) {
120            std::cmp::Ordering::Equal => {
121                if self.is_bound_predicate() || other.is_bound_predicate() {
122                    match self.get_type_code().cmp(&other.get_type_code()) {
123                        std::cmp::Ordering::Equal => {
124                            self.get_right_hand_side().cmp(&other.get_right_hand_side())
125                        }
126                        ordering @ (std::cmp::Ordering::Less | std::cmp::Ordering::Greater) => {
127                            ordering
128                        }
129                    }
130                } else {
131                    self.get_right_hand_side().cmp(&other.get_right_hand_side())
132                }
133            }
134
135            ordering @ (std::cmp::Ordering::Less | std::cmp::Ordering::Greater) => ordering,
136        }
137    }
138}
139
140#[derive(Debug, Hash, EnumSetType)]
141#[repr(u8)]
142#[enumset(repr = "u8")]
143pub enum PredicateType {
144    // Should correspond with the codes defined previously; `EnumSetType` requires that literals
145    // are used and not expressions
146    LowerBound = 0,
147    NotEqual = 1,
148    Equal = 2,
149    UpperBound = 3,
150}
151
152impl From<DomainEvent> for PredicateType {
153    fn from(value: DomainEvent) -> Self {
154        match value {
155            DomainEvent::Assign => PredicateType::Equal,
156            DomainEvent::LowerBound => PredicateType::LowerBound,
157            DomainEvent::UpperBound => PredicateType::UpperBound,
158            DomainEvent::Removal => PredicateType::NotEqual,
159        }
160    }
161}
162
163impl PredicateType {
164    pub fn is_lower_bound(&self) -> bool {
165        matches!(self, PredicateType::LowerBound)
166    }
167
168    pub fn is_upper_bound(&self) -> bool {
169        matches!(self, PredicateType::UpperBound)
170    }
171
172    pub fn is_disequality(&self) -> bool {
173        matches!(self, PredicateType::NotEqual)
174    }
175
176    pub(crate) fn into_predicate(
177        self,
178        domain: DomainId,
179        assignments: &Assignments,
180        removed_value: Option<i32>,
181    ) -> Predicate {
182        match self {
183            PredicateType::LowerBound => {
184                predicate!(domain >= assignments.get_lower_bound(domain))
185            }
186            PredicateType::UpperBound => predicate!(domain <= assignments.get_upper_bound(domain)),
187            PredicateType::NotEqual => predicate!(
188                domain
189                    != removed_value
190                        .expect("For a `NotEqual`, the removed value should be provided")
191            ),
192            PredicateType::Equal => predicate!(
193                domain
194                    == assignments.get_assigned_value(&domain).expect(
195                        "Expected domain to be assigned when creating an `Equal` predicate"
196                    )
197            ),
198        }
199    }
200}
201
202impl From<Predicate> for PredicateType {
203    fn from(value: Predicate) -> Self {
204        match value.get_type_code() {
205            LOWER_BOUND_CODE => Self::LowerBound,
206            UPPER_BOUND_CODE => Self::UpperBound,
207            EQUAL_CODE => Self::Equal,
208            NOT_EQUAL_CODE => Self::NotEqual,
209            code => panic!("Unknown type code {code}"),
210        }
211    }
212}
213
214impl PredicateType {
215    pub const fn into_bits(self) -> u8 {
216        self as _
217    }
218
219    pub const fn from_bits(value: u8) -> PredicateType {
220        match value {
221            LOWER_BOUND_CODE => PredicateType::LowerBound,
222            UPPER_BOUND_CODE => PredicateType::UpperBound,
223            EQUAL_CODE => PredicateType::Equal,
224            NOT_EQUAL_CODE => PredicateType::NotEqual,
225            _ => panic!("Unknown code"),
226        }
227    }
228}
229
230impl Predicate {
231    pub(crate) fn is_mutually_exclusive_with(self, other: Predicate) -> bool {
232        let domain_id = self.get_domain();
233        let rhs = self.get_right_hand_side();
234
235        let domain_id_other = other.get_domain();
236        let rhs_other = other.get_right_hand_side();
237
238        if domain_id != domain_id_other {
239            // Domain Ids do not match
240            return false;
241        }
242
243        match (self.get_predicate_type(), other.get_predicate_type()) {
244            (PredicateType::LowerBound, PredicateType::LowerBound)
245            | (PredicateType::LowerBound, PredicateType::NotEqual)
246            | (PredicateType::UpperBound, PredicateType::UpperBound)
247            | (PredicateType::UpperBound, PredicateType::NotEqual)
248            | (PredicateType::NotEqual, PredicateType::LowerBound)
249            | (PredicateType::NotEqual, PredicateType::UpperBound)
250            | (PredicateType::NotEqual, PredicateType::NotEqual) => false,
251            (PredicateType::LowerBound, PredicateType::UpperBound) => rhs > rhs_other,
252            (PredicateType::UpperBound, PredicateType::LowerBound) => rhs_other > rhs,
253            (PredicateType::LowerBound, PredicateType::Equal) => rhs > rhs_other,
254            (PredicateType::Equal, PredicateType::LowerBound) => rhs_other > rhs,
255            (PredicateType::UpperBound, PredicateType::Equal) => rhs < rhs_other,
256            (PredicateType::Equal, PredicateType::UpperBound) => rhs_other < rhs,
257            (PredicateType::NotEqual, PredicateType::Equal)
258            | (PredicateType::Equal, PredicateType::NotEqual) => rhs == rhs_other,
259            (PredicateType::Equal, PredicateType::Equal) => rhs != rhs_other,
260        }
261    }
262    pub fn is_equality_predicate(&self) -> bool {
263        self.get_type_code() == EQUAL_CODE
264    }
265
266    pub fn is_lower_bound_predicate(&self) -> bool {
267        self.get_type_code() == LOWER_BOUND_CODE
268    }
269
270    pub fn is_upper_bound_predicate(&self) -> bool {
271        self.get_type_code() == UPPER_BOUND_CODE
272    }
273
274    pub fn is_not_equal_predicate(&self) -> bool {
275        self.get_type_code() == NOT_EQUAL_CODE
276    }
277
278    /// Returns the [`DomainId`] of the [`Predicate`]
279    pub fn get_domain(&self) -> DomainId {
280        DomainId::new(0b00111111_11111111_11111111_11111111 & self.id)
281    }
282
283    pub fn get_right_hand_side(&self) -> i32 {
284        self.value
285    }
286
287    pub fn trivially_true() -> Predicate {
288        // By convention, there is a dummy 0-1 variable set to one at root.
289        // We use it to denote the trivially true predicate.
290        let domain_id = DomainId::new(0);
291        predicate!(domain_id == 1)
292    }
293
294    pub fn trivially_false() -> Predicate {
295        // By convention, there is a dummy 0-1 variable set to one at root.
296        // We use it to denote the trivially true predicate.
297        let domain_id = DomainId::new(0);
298        predicate!(domain_id != 1)
299    }
300}
301
302impl std::ops::Not for Predicate {
303    type Output = Predicate;
304
305    fn not(self) -> Self::Output {
306        let domain_id = self.get_domain();
307        let value = self.get_right_hand_side();
308
309        match self.get_predicate_type() {
310            PredicateType::LowerBound => predicate!(domain_id <= value - 1),
311            PredicateType::UpperBound => predicate!(domain_id >= value + 1),
312            PredicateType::NotEqual => predicate!(domain_id == value),
313            PredicateType::Equal => predicate!(domain_id != value),
314        }
315    }
316}
317
318impl std::fmt::Display for Predicate {
319    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
320        if *self == Predicate::trivially_true() {
321            write!(f, "[True]")
322        } else if *self == Predicate::trivially_false() {
323            write!(f, "[False]")
324        } else {
325            let domain_id = self.get_domain();
326            let rhs = self.get_right_hand_side();
327
328            match self.get_predicate_type() {
329                PredicateType::LowerBound => write!(f, "[{domain_id} >= {rhs}]"),
330                PredicateType::UpperBound => write!(f, "[{domain_id} <= {rhs}]"),
331                PredicateType::NotEqual => write!(f, "[{domain_id} != {rhs}]"),
332                PredicateType::Equal => write!(f, "[{domain_id} == {rhs}]"),
333            }
334        }
335    }
336}
337
338impl std::fmt::Debug for Predicate {
339    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
340        write!(f, "{self}")
341    }
342}
343
344impl AtomicConstraint for Predicate {
345    type Identifier = DomainId;
346
347    fn identifier(&self) -> Self::Identifier {
348        self.get_domain()
349    }
350
351    fn comparison(&self) -> pumpkin_checking::Comparison {
352        match self.get_predicate_type() {
353            PredicateType::LowerBound => pumpkin_checking::Comparison::GreaterEqual,
354            PredicateType::UpperBound => pumpkin_checking::Comparison::LessEqual,
355            PredicateType::NotEqual => pumpkin_checking::Comparison::NotEqual,
356            PredicateType::Equal => pumpkin_checking::Comparison::Equal,
357        }
358    }
359
360    fn value(&self) -> i32 {
361        self.get_right_hand_side()
362    }
363
364    fn negate(&self) -> Self {
365        !*self
366    }
367}
368
369#[cfg(test)]
370mod test {
371    use super::Predicate;
372    use crate::predicate;
373    use crate::variables::DomainId;
374
375    #[test]
376    fn are_mutually_exclusive() {
377        let domain_id = DomainId::new(0);
378
379        assert!(!predicate!(domain_id >= 5).is_mutually_exclusive_with(predicate!(domain_id >= 7)));
380        assert!(!predicate!(domain_id >= 5).is_mutually_exclusive_with(predicate!(domain_id != 2)));
381        assert!(!predicate!(domain_id <= 5).is_mutually_exclusive_with(predicate!(domain_id <= 8)));
382        assert!(!predicate!(domain_id <= 5).is_mutually_exclusive_with(predicate!(domain_id != 8)));
383        assert!(!predicate!(domain_id != 9).is_mutually_exclusive_with(predicate!(domain_id >= 8)));
384        assert!(!predicate!(domain_id != 9).is_mutually_exclusive_with(predicate!(domain_id <= 8)));
385        assert!(!predicate!(domain_id != 9).is_mutually_exclusive_with(predicate!(domain_id != 8)));
386
387        assert!(predicate!(domain_id <= 7).is_mutually_exclusive_with(predicate!(domain_id >= 8)));
388        assert!(predicate!(domain_id >= 8).is_mutually_exclusive_with(predicate!(domain_id <= 7)));
389
390        assert!(predicate!(domain_id >= 8).is_mutually_exclusive_with(predicate!(domain_id == 7)));
391        assert!(predicate!(domain_id == 7).is_mutually_exclusive_with(predicate!(domain_id >= 8)));
392
393        assert!(predicate!(domain_id == 7).is_mutually_exclusive_with(predicate!(domain_id <= 6)));
394        assert!(predicate!(domain_id <= 6).is_mutually_exclusive_with(predicate!(domain_id == 7)));
395
396        assert!(predicate!(domain_id != 8).is_mutually_exclusive_with(predicate!(domain_id == 8)));
397        assert!(predicate!(domain_id == 8).is_mutually_exclusive_with(predicate!(domain_id != 8)));
398
399        assert!(predicate!(domain_id == 7).is_mutually_exclusive_with(predicate!(domain_id == 8)));
400    }
401
402    #[test]
403    fn negating_trivially_true_predicate() {
404        let trivially_true = Predicate::trivially_true();
405        let trivially_false = Predicate::trivially_false();
406        assert!(!trivially_true == trivially_false);
407    }
408
409    #[test]
410    fn negating_trivially_false_predicate() {
411        let trivially_true = Predicate::trivially_true();
412        let trivially_false = Predicate::trivially_false();
413        assert!(!trivially_false == trivially_true);
414    }
415
416    #[test]
417    fn predicates_over_same_domain_are_ordered_by_increasing_lower_bound() {
418        let x = DomainId::new(0);
419        let p1 = predicate![x >= 4];
420        let p2 = predicate![x >= 6];
421        assert!(p1 < p2);
422    }
423
424    #[test]
425    fn not_equal_predicates_are_bigger_than_lower_bounds() {
426        let x = DomainId::new(0);
427        let p1 = predicate![x >= 4];
428        let p2 = predicate![x != 6];
429        let p3 = predicate![x != 2];
430
431        assert!(p1 < p2);
432        assert!(p1 < p3);
433    }
434
435    #[test]
436    fn not_equal_predicates_are_ordered_by_rhs() {
437        let x = DomainId::new(0);
438        let p1 = predicate![x != 6];
439        let p2 = predicate![x != 2];
440
441        assert!(p1 > p2);
442    }
443
444    #[test]
445    fn equal_predicates_are_ordered_by_rhs() {
446        let x = DomainId::new(0);
447        let p1 = predicate![x == 6];
448        let p2 = predicate![x == 2];
449
450        assert!(p1 > p2);
451    }
452
453    #[test]
454    fn equal_predicates_bigger_than_lower_bounds() {
455        let x = DomainId::new(0);
456        let p1 = predicate![x == 6];
457        let p2 = predicate![x >= 2];
458
459        assert!(p1 > p2);
460    }
461
462    #[test]
463    fn equal_predicates_smaller_than_upper_bounds() {
464        let x = DomainId::new(0);
465        let p1 = predicate![x == 6];
466        let p2 = predicate![x <= 2];
467
468        assert!(p1 < p2);
469    }
470
471    #[test]
472    fn tighter_upper_bound_is_smaller() {
473        let x = DomainId::new(0);
474        let p1 = predicate![x <= 6];
475        let p2 = predicate![x <= 2];
476
477        assert!(p1 > p2);
478    }
479
480    #[test]
481    fn implies_over_different_domains_is_false() {
482        let x = DomainId::new(0);
483        let y = DomainId::new(1);
484
485        assert!(!predicate![x >= 5].implies(predicate![y >= 4]));
486    }
487
488    #[test]
489    fn lower_bound_implies() {
490        let x = DomainId::new(0);
491
492        // Implies weaker bounds
493        assert!(predicate![x >= 5].implies(predicate![x >= 5]));
494        assert!(predicate![x >= 5].implies(predicate![x >= 4]));
495
496        // Implies not-equals below bound
497        assert!(predicate![x >= 5].implies(predicate![x != 4]));
498        assert!(predicate![x >= 5].implies(predicate![x != 3]));
499
500        // Does not imply stronger bounds
501        assert!(!predicate![x >= 5].implies(predicate![x >= 6]));
502
503        // Does not imply not-equals at or above bound
504        assert!(!predicate![x >= 5].implies(predicate![x != 6]));
505        assert!(!predicate![x >= 5].implies(predicate![x != 5]));
506
507        // Does not imply equals
508        assert!(!predicate![x >= 5].implies(predicate![x == 6]));
509        assert!(!predicate![x >= 5].implies(predicate![x == 5]));
510        assert!(!predicate![x >= 5].implies(predicate![x == 4]));
511    }
512
513    #[test]
514    fn upper_bound_implies() {
515        let x = DomainId::new(0);
516
517        // Implies weaker bounds
518        assert!(predicate![x <= 5].implies(predicate![x <= 5]));
519        assert!(predicate![x <= 5].implies(predicate![x <= 6]));
520
521        // Implies not-equals above bound
522        assert!(predicate![x <= 5].implies(predicate![x != 6]));
523        assert!(predicate![x <= 5].implies(predicate![x != 7]));
524
525        // Does not imply stronger bounds
526        assert!(!predicate![x <= 5].implies(predicate![x <= 4]));
527
528        // Does not imply not-equals at or below bound
529        assert!(!predicate![x <= 5].implies(predicate![x != 4]));
530        assert!(!predicate![x <= 5].implies(predicate![x != 5]));
531
532        // Does not imply equals
533        assert!(!predicate![x <= 5].implies(predicate![x == 6]));
534        assert!(!predicate![x <= 5].implies(predicate![x == 5]));
535        assert!(!predicate![x <= 5].implies(predicate![x == 4]));
536    }
537
538    #[test]
539    fn equals_implies() {
540        let x = DomainId::new(0);
541
542        // Implies lower bounds at or below
543        assert!(predicate![x == 5].implies(predicate![x >= 5]));
544        assert!(predicate![x == 5].implies(predicate![x >= 4]));
545
546        // Implies upper bounds at or above
547        assert!(predicate![x == 5].implies(predicate![x <= 5]));
548        assert!(predicate![x == 5].implies(predicate![x <= 6]));
549
550        // Implies not-equals
551        assert!(predicate![x == 5].implies(predicate![x != 4]));
552        assert!(predicate![x == 5].implies(predicate![x != 6]));
553
554        // Does not imply not-equals at bound
555        assert!(!predicate![x == 5].implies(predicate![x != 5]));
556
557        // Does not lower bounds above value
558        assert!(!predicate![x == 5].implies(predicate![x >= 6]));
559
560        // Does not upper bounds below value
561        assert!(!predicate![x == 5].implies(predicate![x <= 4]));
562    }
563
564    #[test]
565    fn not_equals_implies_nothing() {
566        let x = DomainId::new(0);
567
568        assert!(!predicate![x != 5].implies(predicate![x <= 4]));
569        assert!(!predicate![x != 5].implies(predicate![x <= 5]));
570        assert!(!predicate![x != 5].implies(predicate![x <= 6]));
571
572        assert!(!predicate![x != 5].implies(predicate![x >= 4]));
573        assert!(!predicate![x != 5].implies(predicate![x >= 5]));
574        assert!(!predicate![x != 5].implies(predicate![x >= 6]));
575
576        assert!(!predicate![x != 5].implies(predicate![x == 4]));
577        assert!(!predicate![x != 5].implies(predicate![x == 5]));
578        assert!(!predicate![x != 5].implies(predicate![x == 6]));
579
580        assert!(!predicate![x != 5].implies(predicate![x != 4]));
581        assert!(!predicate![x != 5].implies(predicate![x != 6]));
582
583        assert!(predicate![x != 5].implies(predicate![x != 5]));
584    }
585}