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