pumpkin_propagators/propagators/arithmetic/
maximum.rs1use pumpkin_checking::AtomicConstraint;
2use pumpkin_checking::CheckerVariable;
3use pumpkin_checking::InferenceChecker;
4use pumpkin_checking::IntExt;
5use pumpkin_core::conjunction;
6use pumpkin_core::declare_inference_label;
7use pumpkin_core::predicate;
8use pumpkin_core::predicates::PropositionalConjunction;
9use pumpkin_core::proof::ConstraintTag;
10use pumpkin_core::proof::InferenceCode;
11use pumpkin_core::propagation::DomainEvents;
12use pumpkin_core::propagation::InferenceCheckers;
13use pumpkin_core::propagation::LocalId;
14use pumpkin_core::propagation::Priority;
15use pumpkin_core::propagation::PropagationContext;
16use pumpkin_core::propagation::Propagator;
17use pumpkin_core::propagation::PropagatorConstructor;
18use pumpkin_core::propagation::PropagatorConstructorContext;
19use pumpkin_core::propagation::ReadDomains;
20use pumpkin_core::state::PropagationStatusCP;
21use pumpkin_core::variables::IntegerVariable;
22
23#[derive(Clone, Debug)]
24pub struct MaximumArgs<ElementVar, Rhs> {
25 pub array: Box<[ElementVar]>,
26 pub rhs: Rhs,
27 pub constraint_tag: ConstraintTag,
28}
29
30declare_inference_label!(Maximum);
31
32impl<ElementVar, Rhs> PropagatorConstructor for MaximumArgs<ElementVar, Rhs>
33where
34 ElementVar: IntegerVariable + 'static,
35 Rhs: IntegerVariable + 'static,
36{
37 type PropagatorImpl = MaximumPropagator<ElementVar, Rhs>;
38
39 fn add_inference_checkers(&self, mut checkers: InferenceCheckers<'_>) {
40 checkers.add_inference_checker(
41 InferenceCode::new(self.constraint_tag, Maximum),
42 Box::new(MaximumChecker {
43 array: self.array.clone(),
44 rhs: self.rhs.clone(),
45 }),
46 );
47 }
48
49 fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl {
50 let MaximumArgs {
51 array,
52 rhs,
53 constraint_tag,
54 } = self;
55
56 for (idx, var) in array.iter().enumerate() {
57 context.register(var.clone(), DomainEvents::BOUNDS, LocalId::from(idx as u32));
58 }
59
60 context.register(
61 rhs.clone(),
62 DomainEvents::BOUNDS,
63 LocalId::from(array.len() as u32),
64 );
65
66 let inference_code = InferenceCode::new(constraint_tag, Maximum);
67
68 MaximumPropagator {
69 array,
70 rhs,
71 inference_code,
72 }
73 }
74}
75
76#[derive(Clone, Debug)]
79pub struct MaximumPropagator<ElementVar, Rhs> {
80 array: Box<[ElementVar]>,
81 rhs: Rhs,
82 inference_code: InferenceCode,
83}
84
85impl<ElementVar: IntegerVariable + 'static, Rhs: IntegerVariable + 'static> Propagator
86 for MaximumPropagator<ElementVar, Rhs>
87{
88 fn priority(&self) -> Priority {
89 Priority::High
90 }
91
92 fn name(&self) -> &str {
93 "Maximum"
94 }
95
96 fn propagate_from_scratch(&self, mut context: PropagationContext) -> PropagationStatusCP {
97 let rhs_ub = context.upper_bound(&self.rhs);
101 let mut max_ub = context.upper_bound(&self.array[0]);
102 let mut max_lb = context.lower_bound(&self.array[0]);
103 let mut lb_reason = predicate![self.array[0] >= max_lb];
104 for var in self.array.iter() {
105 context.post(
108 predicate![var <= rhs_ub],
109 (conjunction!([self.rhs <= rhs_ub]), &self.inference_code),
110 )?;
111
112 let var_lb = context.lower_bound(var);
113 let var_ub = context.upper_bound(var);
114
115 if var_lb > max_lb {
116 max_lb = var_lb;
117 lb_reason = predicate![var >= var_lb];
118 }
119
120 if var_ub > max_ub {
121 max_ub = var_ub;
122 }
123 }
124 context.post(
127 predicate![self.rhs >= max_lb],
128 (
129 PropositionalConjunction::from(lb_reason),
130 &self.inference_code,
131 ),
132 )?;
133
134 if rhs_ub > max_ub {
139 let ub_reason: PropositionalConjunction = self
140 .array
141 .iter()
142 .map(|var| predicate![var <= max_ub])
143 .collect();
144 context.post(
145 predicate![self.rhs <= max_ub],
146 (ub_reason, &self.inference_code),
147 )?;
148 }
149
150 let rhs_lb = context.lower_bound(&self.rhs);
154 let mut propagating_variable: Option<&ElementVar> = None;
155 let mut propagation_reason = PropositionalConjunction::default();
156 for var in self.array.iter() {
157 if context.upper_bound(var) >= rhs_lb {
158 if propagating_variable.is_none() {
159 propagating_variable = Some(var);
160 } else {
161 propagating_variable = None;
162 break;
163 }
164 } else {
165 propagation_reason.push(predicate![var <= rhs_lb - 1]);
166 }
167 }
168 if let Some(propagating_variable) = propagating_variable {
173 let var_lb = context.lower_bound(propagating_variable);
174 if var_lb < rhs_lb {
175 propagation_reason.push(predicate![self.rhs >= rhs_lb]);
176 context.post(
177 predicate![propagating_variable >= rhs_lb],
178 (propagation_reason, &self.inference_code),
179 )?;
180 }
181 }
182
183 Ok(())
184 }
185}
186
187#[derive(Clone, Debug)]
188pub struct MaximumChecker<ElementVar, Rhs> {
189 pub array: Box<[ElementVar]>,
190 pub rhs: Rhs,
191}
192
193impl<ElementVar, Rhs, Atomic> InferenceChecker<Atomic> for MaximumChecker<ElementVar, Rhs>
194where
195 Atomic: AtomicConstraint,
196 ElementVar: CheckerVariable<Atomic>,
197 Rhs: CheckerVariable<Atomic>,
198{
199 fn check(
200 &self,
201 state: pumpkin_checking::VariableState<Atomic>,
202 _: &[Atomic],
203 _: Option<&Atomic>,
204 ) -> bool {
205 let lowest_maximum = self
206 .array
207 .iter()
208 .map(|element| element.induced_lower_bound(&state))
209 .max()
210 .unwrap_or(IntExt::NegativeInf);
211 let highest_maximum = self
212 .array
213 .iter()
214 .map(|element| element.induced_upper_bound(&state))
215 .max()
216 .unwrap_or(IntExt::PositiveInf);
217
218 lowest_maximum > self.rhs.induced_upper_bound(&state)
222 || highest_maximum < self.rhs.induced_lower_bound(&state)
223 }
224}
225
226#[cfg(test)]
227mod tests {
228 use pumpkin_core::predicate;
229 use pumpkin_core::predicates::Predicate;
230 use pumpkin_core::predicates::PropositionalConjunction;
231 use pumpkin_core::propagation::CurrentNogood;
232 use pumpkin_core::state::State;
233
234 use super::*;
235 use crate::StateExt;
236
237 #[test]
238 fn upper_bound_of_rhs_matches_maximum_upper_bound_of_array_at_initialise() {
239 let mut state = State::default();
240
241 let a = state.new_interval_variable(1, 3, None);
242 let b = state.new_interval_variable(1, 4, None);
243 let c = state.new_interval_variable(1, 5, None);
244
245 let rhs = state.new_interval_variable(1, 10, None);
246 let constraint_tag = state.new_constraint_tag();
247
248 let _ = state.add_propagator(MaximumArgs {
249 array: [a, b, c].into(),
250 rhs,
251 constraint_tag,
252 });
253 state.propagate_to_fixed_point().expect("no empty domain");
254
255 state.assert_bounds(rhs, 1, 5);
256
257 let mut reason_buffer: Vec<Predicate> = vec![];
258 let _ = state.get_propagation_reason(
259 predicate![rhs <= 5],
260 &mut reason_buffer,
261 CurrentNogood::empty(),
262 );
263 let reason: PropositionalConjunction = reason_buffer.into();
264 assert_eq!(conjunction!([a <= 5] & [b <= 5] & [c <= 5]), reason);
265 }
266
267 #[test]
268 fn lower_bound_of_rhs_is_maximum_of_lower_bounds_in_array() {
269 let mut state = State::default();
270
271 let a = state.new_interval_variable(3, 10, None);
272 let b = state.new_interval_variable(4, 10, None);
273 let c = state.new_interval_variable(5, 10, None);
274
275 let rhs = state.new_interval_variable(1, 10, None);
276 let constraint_tag = state.new_constraint_tag();
277
278 let _ = state.add_propagator(MaximumArgs {
279 array: [a, b, c].into(),
280 rhs,
281 constraint_tag,
282 });
283 state.propagate_to_fixed_point().expect("no empty domain");
284
285 state.assert_bounds(rhs, 5, 10);
286
287 let mut reason_buffer: Vec<Predicate> = vec![];
288 let _ = state.get_propagation_reason(
289 predicate![rhs >= 5],
290 &mut reason_buffer,
291 CurrentNogood::empty(),
292 );
293 let reason: PropositionalConjunction = reason_buffer.into();
294 assert_eq!(conjunction!([c >= 5]), reason);
295 }
296
297 #[test]
298 fn upper_bound_of_all_array_elements_at_most_rhs_max_at_initialise() {
299 let mut state = State::default();
300
301 let array = (1..=5)
302 .map(|idx| state.new_interval_variable(1, 4 + idx, None))
303 .collect::<Box<_>>();
304
305 let rhs = state.new_interval_variable(1, 3, None);
306 let constraint_tag = state.new_constraint_tag();
307
308 let _ = state.add_propagator(MaximumArgs {
309 array: array.clone(),
310 rhs,
311 constraint_tag,
312 });
313 state.propagate_to_fixed_point().expect("no empty domain");
314
315 for var in array.iter() {
316 state.assert_bounds(*var, 1, 3);
317
318 let mut reason_buffer: Vec<Predicate> = vec![];
319 let _ = state.get_propagation_reason(
320 predicate![var <= 3],
321 &mut reason_buffer,
322 CurrentNogood::empty(),
323 );
324 let reason: PropositionalConjunction = reason_buffer.into();
325 assert_eq!(conjunction!([rhs <= 3]), reason);
326 }
327 }
328
329 #[test]
330 fn single_variable_propagate() {
331 let mut state = State::default();
332
333 let array = (1..=5)
334 .map(|idx| state.new_interval_variable(1, 1 + 10 * idx, None))
335 .collect::<Box<_>>();
336
337 let rhs = state.new_interval_variable(45, 60, None);
338 let constraint_tag = state.new_constraint_tag();
339
340 let _ = state.add_propagator(MaximumArgs {
341 array: array.clone(),
342 rhs,
343 constraint_tag,
344 });
345 state.propagate_to_fixed_point().expect("no empty domain");
346
347 state.assert_bounds(*array.last().unwrap(), 45, 51);
348 state.assert_bounds(rhs, 45, 51);
349 }
350}