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::results::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]),
110 &self.inference_code,
111 )?;
112
113 let var_lb = context.lower_bound(var);
114 let var_ub = context.upper_bound(var);
115
116 if var_lb > max_lb {
117 max_lb = var_lb;
118 lb_reason = predicate![var >= var_lb];
119 }
120
121 if var_ub > max_ub {
122 max_ub = var_ub;
123 }
124 }
125 context.post(
128 predicate![self.rhs >= max_lb],
129 PropositionalConjunction::from(lb_reason),
130 &self.inference_code,
131 )?;
132
133 if rhs_ub > max_ub {
138 let ub_reason: PropositionalConjunction = self
139 .array
140 .iter()
141 .map(|var| predicate![var <= max_ub])
142 .collect();
143 context.post(
144 predicate![self.rhs <= max_ub],
145 ub_reason,
146 &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,
179 &self.inference_code,
180 )?;
181 }
182 }
183
184 Ok(())
185 }
186}
187
188#[derive(Clone, Debug)]
189pub struct MaximumChecker<ElementVar, Rhs> {
190 pub array: Box<[ElementVar]>,
191 pub rhs: Rhs,
192}
193
194impl<ElementVar, Rhs, Atomic> InferenceChecker<Atomic> for MaximumChecker<ElementVar, Rhs>
195where
196 Atomic: AtomicConstraint,
197 ElementVar: CheckerVariable<Atomic>,
198 Rhs: CheckerVariable<Atomic>,
199{
200 fn check(
201 &self,
202 state: pumpkin_checking::VariableState<Atomic>,
203 _: &[Atomic],
204 _: Option<&Atomic>,
205 ) -> bool {
206 let lowest_maximum = self
207 .array
208 .iter()
209 .map(|element| element.induced_lower_bound(&state))
210 .max()
211 .unwrap_or(IntExt::NegativeInf);
212 let highest_maximum = self
213 .array
214 .iter()
215 .map(|element| element.induced_upper_bound(&state))
216 .max()
217 .unwrap_or(IntExt::PositiveInf);
218
219 lowest_maximum > self.rhs.induced_upper_bound(&state)
223 || highest_maximum < self.rhs.induced_lower_bound(&state)
224 }
225}
226
227#[allow(deprecated, reason = "Will be refactored")]
228#[cfg(test)]
229mod tests {
230 use pumpkin_core::TestSolver;
231
232 use super::*;
233
234 #[test]
235 fn upper_bound_of_rhs_matches_maximum_upper_bound_of_array_at_initialise() {
236 let mut solver = TestSolver::default();
237
238 let a = solver.new_variable(1, 3);
239 let b = solver.new_variable(1, 4);
240 let c = solver.new_variable(1, 5);
241
242 let rhs = solver.new_variable(1, 10);
243 let constraint_tag = solver.new_constraint_tag();
244
245 let _ = solver
246 .new_propagator(MaximumArgs {
247 array: [a, b, c].into(),
248 rhs,
249 constraint_tag,
250 })
251 .expect("no empty domain");
252
253 solver.assert_bounds(rhs, 1, 5);
254
255 let reason = solver.get_reason_int(predicate![rhs <= 5]);
256 assert_eq!(conjunction!([a <= 5] & [b <= 5] & [c <= 5]), reason);
257 }
258
259 #[test]
260 fn lower_bound_of_rhs_is_maximum_of_lower_bounds_in_array() {
261 let mut solver = TestSolver::default();
262
263 let a = solver.new_variable(3, 10);
264 let b = solver.new_variable(4, 10);
265 let c = solver.new_variable(5, 10);
266
267 let rhs = solver.new_variable(1, 10);
268 let constraint_tag = solver.new_constraint_tag();
269
270 let _ = solver
271 .new_propagator(MaximumArgs {
272 array: [a, b, c].into(),
273 rhs,
274 constraint_tag,
275 })
276 .expect("no empty domain");
277
278 solver.assert_bounds(rhs, 5, 10);
279
280 let reason = solver.get_reason_int(predicate![rhs >= 5]);
281 assert_eq!(conjunction!([c >= 5]), reason);
282 }
283
284 #[test]
285 fn upper_bound_of_all_array_elements_at_most_rhs_max_at_initialise() {
286 let mut solver = TestSolver::default();
287
288 let array = (1..=5)
289 .map(|idx| solver.new_variable(1, 4 + idx))
290 .collect::<Box<_>>();
291
292 let rhs = solver.new_variable(1, 3);
293 let constraint_tag = solver.new_constraint_tag();
294
295 let _ = solver
296 .new_propagator(MaximumArgs {
297 array: array.clone(),
298 rhs,
299 constraint_tag,
300 })
301 .expect("no empty domain");
302
303 for var in array.iter() {
304 solver.assert_bounds(*var, 1, 3);
305 let reason = solver.get_reason_int(predicate![var <= 3]);
306 assert_eq!(conjunction!([rhs <= 3]), reason);
307 }
308 }
309
310 #[test]
311 fn single_variable_propagate() {
312 let mut solver = TestSolver::default();
313
314 let array = (1..=5)
315 .map(|idx| solver.new_variable(1, 1 + 10 * idx))
316 .collect::<Box<_>>();
317
318 let rhs = solver.new_variable(45, 60);
319 let constraint_tag = solver.new_constraint_tag();
320
321 let _ = solver
322 .new_propagator(MaximumArgs {
323 array: array.clone(),
324 rhs,
325 constraint_tag,
326 })
327 .expect("no empty domain");
328
329 solver.assert_bounds(*array.last().unwrap(), 45, 51);
330 solver.assert_bounds(rhs, 45, 51);
331 }
332}