pumpkin_core/propagators/hypercube_linear/
hypercube.rs1use pumpkin_checking::IntExt;
2use pumpkin_checking::VariableState;
3
4use crate::predicate;
5use crate::predicates::Predicate;
6use crate::variables::DomainId;
7
8#[derive(Clone, Copy, Debug, thiserror::Error, PartialEq, Eq)]
13#[error("domain {0} is empty in the hypercube")]
14pub struct InconsistentHypercube(DomainId);
15
16#[derive(Clone, Debug)]
20pub struct Hypercube {
21 state: VariableState<Predicate>,
22}
23
24impl Hypercube {
25 pub fn new(
29 predicates: impl IntoIterator<Item = Predicate>,
30 ) -> Result<Self, InconsistentHypercube> {
31 let state = VariableState::prepare_for_conflict_check(predicates, None)
35 .map_err(InconsistentHypercube)?;
36
37 Ok(Hypercube { state })
38 }
39
40 pub fn iter_predicates(&self) -> impl Iterator<Item = Predicate> + '_ {
42 self.state.domains().flat_map(|domain_id| {
43 let lower_bound_predicate =
44 if let IntExt::Int(lower_bound) = self.state.lower_bound(domain_id) {
45 Some(predicate![domain_id >= lower_bound])
46 } else {
47 None
48 };
49 let upper_bound_predicate =
50 if let IntExt::Int(upper_bound) = self.state.upper_bound(domain_id) {
51 Some(predicate![domain_id <= upper_bound])
52 } else {
53 None
54 };
55
56 [lower_bound_predicate, upper_bound_predicate]
57 .into_iter()
58 .flatten()
59 .chain(
60 self.state
61 .holes(domain_id)
62 .map(|value| predicate![domain_id != value]),
63 )
64 })
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71 use crate::containers::HashSet;
72 use crate::predicate;
73 use crate::state::State;
74
75 #[test]
76 fn consistent_hypercube_can_be_created() {
77 let mut state = State::default();
78
79 let x = state.new_interval_variable(1, 10, Some("x".into()));
80 let y = state.new_interval_variable(1, 10, Some("y".into()));
81
82 let maybe_hypercube = Hypercube::new([predicate![x >= 2], predicate![y >= 2]]);
83
84 assert!(maybe_hypercube.is_ok());
85 }
86
87 #[test]
88 fn inconsistent_hypercube_can_be_created() {
89 let mut state = State::default();
90
91 let x = state.new_interval_variable(1, 10, Some("x".into()));
92 let y = state.new_interval_variable(1, 10, Some("y".into()));
93
94 let error = Hypercube::new([predicate![x >= 2], predicate![y >= 2], predicate![x <= 1]])
95 .expect_err("hypercube is inconsistent");
96
97 assert_eq!(InconsistentHypercube(x), error);
98 }
99
100 #[test]
101 fn hypercube_iters_predicates_from_constructor() {
102 let mut state = State::default();
103
104 let x = state.new_interval_variable(1, 10, Some("x".into()));
105 let y = state.new_interval_variable(1, 10, Some("y".into()));
106
107 let hypercube =
108 Hypercube::new([predicate![x >= 2], predicate![y >= 2]]).expect("not inconsistent");
109
110 assert_eq!(
111 [predicate![x >= 2], predicate![y >= 2]]
112 .into_iter()
113 .collect::<HashSet<_>>(),
114 hypercube.iter_predicates().collect::<HashSet<_>>(),
115 );
116 }
117
118 #[test]
119 fn iterating_predicates_ignores_subsumed_predicates() {
120 let mut state = State::default();
121
122 let x = state.new_interval_variable(1, 10, Some("x".into()));
123
124 let hypercube =
125 Hypercube::new([predicate![x >= 2], predicate![x >= 4]]).expect("not inconsistent");
126
127 assert_eq!(
128 [predicate![x >= 4]].into_iter().collect::<HashSet<_>>(),
129 hypercube.iter_predicates().collect::<HashSet<_>>(),
130 );
131 }
132}