pumpkin_core/propagators/
reified_propagator.rs1use pumpkin_checking::AtomicConstraint;
2use pumpkin_checking::BoxedChecker;
3use pumpkin_checking::CheckerVariable;
4use pumpkin_checking::InferenceChecker;
5
6use crate::engine::PropagationStatusCP;
7use crate::engine::notifications::OpaqueDomainEvent;
8use crate::predicates::Predicate;
9use crate::propagation::DomainEvents;
10use crate::propagation::Domains;
11use crate::propagation::EnqueueDecision;
12use crate::propagation::ExplanationContext;
13use crate::propagation::InferenceCheckers;
14use crate::propagation::LazyExplanation;
15use crate::propagation::LocalId;
16use crate::propagation::NotificationContext;
17use crate::propagation::Priority;
18use crate::propagation::PropagationContext;
19use crate::propagation::Propagator;
20use crate::propagation::PropagatorConstructor;
21use crate::propagation::PropagatorConstructorContext;
22use crate::propagation::ReadDomains;
23use crate::pumpkin_assert_simple;
24use crate::state::Conflict;
25use crate::variables::Literal;
26
27#[derive(Clone, Debug)]
29pub struct ReifiedPropagatorArgs<WrappedArgs> {
30 pub propagator: WrappedArgs,
31 pub reification_literal: Literal,
32}
33
34impl<WrappedArgs, WrappedPropagator> PropagatorConstructor for ReifiedPropagatorArgs<WrappedArgs>
35where
36 WrappedArgs: PropagatorConstructor<PropagatorImpl = WrappedPropagator>,
37 WrappedPropagator: Propagator + Clone,
38{
39 type PropagatorImpl = ReifiedPropagator<WrappedPropagator>;
40
41 fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl {
42 let ReifiedPropagatorArgs {
43 propagator,
44 reification_literal,
45 } = self;
46
47 let propagator = propagator.create(context.reborrow());
48 let reification_literal_id = context.get_next_local_id();
49
50 context.register(
51 self.reification_literal,
52 DomainEvents::BOUNDS,
53 reification_literal_id,
54 );
55
56 let name = format!("Reified({})", propagator.name());
57
58 ReifiedPropagator {
59 propagator,
60 reification_literal,
61 reification_literal_id,
62 name,
63 reason_buffer: vec![],
64 }
65 }
66
67 fn add_inference_checkers(&self, mut checkers: InferenceCheckers<'_>) {
68 checkers.with_reification_literal(self.reification_literal);
69
70 self.propagator.add_inference_checkers(checkers);
71 }
72}
73
74#[derive(Clone, Debug)]
82pub struct ReifiedPropagator<WrappedPropagator> {
83 propagator: WrappedPropagator,
84 reification_literal: Literal,
85 name: String,
87 reification_literal_id: LocalId,
90
91 reason_buffer: Vec<Predicate>,
93}
94
95impl<WrappedPropagator: Propagator + Clone> Propagator for ReifiedPropagator<WrappedPropagator> {
96 fn notify(
97 &mut self,
98 mut context: NotificationContext,
99 local_id: LocalId,
100 event: OpaqueDomainEvent,
101 ) -> EnqueueDecision {
102 if local_id < self.reification_literal_id {
103 let decision = self.propagator.notify(context.reborrow(), local_id, event);
104 self.filter_enqueue_decision(context, decision)
105 } else {
106 pumpkin_assert_simple!(local_id == self.reification_literal_id);
107 EnqueueDecision::Enqueue
108 }
109 }
110
111 fn notify_backtrack(&mut self, context: Domains, local_id: LocalId, event: OpaqueDomainEvent) {
112 if local_id < self.reification_literal_id {
113 self.propagator.notify_backtrack(context, local_id, event)
114 } else {
115 pumpkin_assert_simple!(local_id == self.reification_literal_id);
116 }
117 }
118
119 fn priority(&self) -> Priority {
120 self.propagator.priority()
121 }
122
123 fn synchronise(&mut self, context: NotificationContext<'_>) {
124 self.propagator.synchronise(context);
125 }
126
127 fn propagate(&mut self, mut context: PropagationContext) -> PropagationStatusCP {
128 self.propagate_reification(&mut context)?;
129
130 if context.evaluate_literal(self.reification_literal) == Some(true) {
131 context.with_reification(self.reification_literal);
132
133 let result = self.propagator.propagate(context);
134
135 self.map_propagation_status(result)?;
136 }
137
138 Ok(())
139 }
140
141 fn name(&self) -> &str {
142 &self.name
143 }
144
145 fn propagate_from_scratch(&self, mut context: PropagationContext) -> PropagationStatusCP {
146 self.propagate_reification(&mut context)?;
147
148 if context.evaluate_literal(self.reification_literal) == Some(true) {
149 context.with_reification(self.reification_literal);
150
151 let result = self.propagator.propagate_from_scratch(context);
152
153 self.map_propagation_status(result)?;
154 }
155
156 Ok(())
157 }
158
159 fn lazy_explanation(&mut self, code: u64, context: ExplanationContext) -> LazyExplanation<'_> {
160 let inner = self.propagator.lazy_explanation(code, context);
161 let inference_code = inner.inference_code;
162
163 self.reason_buffer.clear();
164 self.reason_buffer
165 .push(self.reification_literal.get_true_predicate());
166 self.reason_buffer.extend(inner.predicates);
167
168 LazyExplanation {
169 predicates: self.reason_buffer.as_slice(),
170 inference_code,
171 }
172 }
173}
174
175impl<Prop: Propagator + Clone> ReifiedPropagator<Prop> {
176 fn map_propagation_status(&self, mut status: PropagationStatusCP) -> PropagationStatusCP {
177 if let Err(Conflict::Propagator(ref mut conflict)) = status {
178 conflict
179 .conjunction
180 .push(self.reification_literal.get_true_predicate());
181 }
182 status
183 }
184
185 fn propagate_reification(&self, context: &mut PropagationContext<'_>) -> PropagationStatusCP
186 where
187 Prop: Propagator,
188 {
189 if context.evaluate_literal(self.reification_literal) == Some(true) {
190 return Ok(());
191 }
192
193 if let Some(conflict) = self.propagator.detect_inconsistency(context.domains()) {
194 context.post(
195 self.reification_literal.get_false_predicate(),
196 (conflict.conjunction, &conflict.inference_code),
197 )?;
198 }
199
200 Ok(())
201 }
202
203 fn filter_enqueue_decision(
204 &mut self,
205 mut context: NotificationContext<'_>,
206 decision: EnqueueDecision,
207 ) -> EnqueueDecision {
208 if decision == EnqueueDecision::Skip {
209 return EnqueueDecision::Skip;
211 }
212
213 if context.evaluate_literal(self.reification_literal) == Some(true) {
214 return EnqueueDecision::Enqueue;
217 }
218
219 if context.evaluate_literal(self.reification_literal) != Some(false)
220 && self
221 .propagator
222 .detect_inconsistency(context.domains())
223 .is_some()
224 {
225 return EnqueueDecision::Enqueue;
228 }
229
230 EnqueueDecision::Skip
231 }
232}
233
234#[derive(Debug, Clone)]
235pub struct ReifiedChecker<Atomic: AtomicConstraint, Var> {
236 pub inner: BoxedChecker<Atomic>,
237 pub reification_literal: Var,
238}
239
240impl<Atomic: AtomicConstraint + Clone, Var: CheckerVariable<Atomic>> InferenceChecker<Atomic>
241 for ReifiedChecker<Atomic, Var>
242{
243 fn check(
244 &self,
245 state: pumpkin_checking::VariableState<Atomic>,
246 premises: &[Atomic],
247 consequent: Option<&Atomic>,
248 ) -> bool {
249 if self.reification_literal.induced_domain_contains(&state, 0) {
250 return false;
251 }
252
253 if let Some(consequent) = consequent
254 && self
255 .reification_literal
256 .does_atomic_constrain_self(consequent)
257 {
258 self.inner.check(state, premises, None)
259 } else {
260 self.inner.check(state, premises, consequent)
261 }
262 }
263}
264
265#[allow(deprecated, reason = "Will be refactored")]
266#[cfg(test)]
267mod tests {
268 use super::*;
269 use crate::conjunction;
270 use crate::containers::StorageKey;
271 use crate::engine::PropagatorConflict;
272 use crate::engine::test_solver::TestSolver;
273 use crate::predicate;
274 use crate::predicates::PropositionalConjunction;
275 use crate::proof::ConstraintTag;
276 use crate::proof::InferenceCode;
277 use crate::variables::DomainId;
278
279 #[test]
280 fn a_detected_inconsistency_is_given_as_reason_for_propagating_reification_literal_to_false() {
281 let mut solver = TestSolver::default();
282
283 let reification_literal = solver.new_literal();
284 let a = solver.new_variable(1, 1);
285 let b = solver.new_variable(2, 2);
286
287 let triggered_conflict = conjunction!([a == 1] & [b == 2]);
288 let t1 = triggered_conflict.clone();
289 let t2 = triggered_conflict.clone();
290
291 let inference_code = InferenceCode::unknown_label(ConstraintTag::create_from_index(0));
292 solver.accept_inferences_by(inference_code.clone());
293 let i1 = inference_code.clone();
294 let i2 = inference_code.clone();
295
296 let _ = solver
297 .new_propagator(ReifiedPropagatorArgs {
298 propagator: GenericPropagator::new(
299 move |_: PropagationContext| {
300 Err(PropagatorConflict {
301 conjunction: t1.clone(),
302 inference_code: i1.clone(),
303 }
304 .into())
305 },
306 move |_: Domains| {
307 Some(PropagatorConflict {
308 conjunction: t2.clone(),
309 inference_code: i2.clone(),
310 })
311 },
312 ),
313 reification_literal,
314 })
315 .expect("no conflict");
316
317 assert!(solver.is_literal_false(reification_literal));
318
319 let reason = solver.get_reason_bool(reification_literal, false);
320 assert_eq!(reason, triggered_conflict);
321 }
322
323 #[test]
324 fn a_true_literal_is_added_to_reason_for_propagation() {
325 let mut solver = TestSolver::default();
326
327 let reification_literal = solver.new_literal();
328 let var = solver.new_variable(1, 5);
329
330 let propagator = solver
331 .new_propagator(ReifiedPropagatorArgs {
332 propagator: GenericPropagator::new(
333 move |mut ctx: PropagationContext| {
334 ctx.post(
335 predicate![var >= 3],
336 (
337 conjunction!(),
338 &InferenceCode::unknown_label(ConstraintTag::create_from_index(0)),
339 ),
340 )?;
341 Ok(())
342 },
343 |_: Domains| None,
344 ),
345 reification_literal,
346 })
347 .expect("no conflict");
348
349 solver.assert_bounds(var, 1, 5);
350
351 let _ = solver.set_literal(reification_literal, true);
352 solver.propagate(propagator).expect("no conflict");
353
354 solver.assert_bounds(var, 3, 5);
355 let reason = solver.get_reason_int(predicate![var >= 3]);
356 assert_eq!(
357 reason,
358 PropositionalConjunction::from(reification_literal.get_true_predicate())
359 );
360 }
361
362 #[test]
363 fn a_true_literal_is_added_to_a_conflict_conjunction() {
364 let mut solver = TestSolver::default();
365
366 let reification_literal = solver.new_literal();
367 let _ = solver.set_literal(reification_literal, true);
368
369 let var = solver.new_variable(1, 1);
370 let inference_code = InferenceCode::unknown_label(ConstraintTag::create_from_index(0));
371 solver.accept_inferences_by(inference_code.clone());
372
373 let inconsistency = solver
374 .new_propagator(ReifiedPropagatorArgs {
375 propagator: GenericPropagator::new(
376 move |_: PropagationContext| {
377 Err(PropagatorConflict {
378 conjunction: conjunction!([var >= 1]),
379 inference_code: inference_code.clone(),
380 }
381 .into())
382 },
383 |_: Domains| None,
384 ),
385 reification_literal,
386 })
387 .expect_err("eagerly triggered the conflict");
388
389 match inconsistency {
390 Conflict::Propagator(conflict_nogood) => {
391 assert_eq!(
392 conflict_nogood.conjunction,
393 PropositionalConjunction::from(vec![
394 reification_literal.get_true_predicate(),
395 predicate![var >= 1]
396 ])
397 )
398 }
399
400 other => panic!("Inconsistency {other:?} is not expected."),
401 }
402 }
403
404 #[test]
405 fn notify_propagator_is_enqueued_if_inconsistency_can_be_detected() {
406 let mut solver = TestSolver::default();
407
408 let reification_literal = solver.new_literal();
409 let var = solver.new_variable(1, 5);
410
411 let inference_code = InferenceCode::unknown_label(ConstraintTag::create_from_index(0));
412 solver.accept_inferences_by(inference_code.clone());
413
414 let propagator = solver
415 .new_propagator(ReifiedPropagatorArgs {
416 propagator: GenericPropagator::new(
417 |_: PropagationContext| Ok(()),
418 move |context: Domains| {
419 if context.is_fixed(&var) {
420 Some(PropagatorConflict {
421 conjunction: conjunction!([var == 5]),
422 inference_code: inference_code.clone(),
423 })
424 } else {
425 None
426 }
427 },
428 )
429 .with_variables(&[var]),
430 reification_literal,
431 })
432 .expect("No conflict expected");
433
434 let enqueue = solver.increase_lower_bound_and_notify(propagator, 0, var, 5);
435 assert!(matches!(enqueue, EnqueueDecision::Enqueue))
436 }
437
438 #[derive(Clone)]
439 struct GenericPropagator<Propagation, ConsistencyCheck> {
440 propagation: Propagation,
441 consistency_check: ConsistencyCheck,
442 variables_to_register: Vec<DomainId>,
443 }
444
445 impl<Propagation, ConsistencyCheck> PropagatorConstructor
446 for GenericPropagator<Propagation, ConsistencyCheck>
447 where
448 Propagation: Fn(PropagationContext) -> PropagationStatusCP + 'static + Clone,
449 ConsistencyCheck: Fn(Domains) -> Option<PropagatorConflict> + 'static + Clone,
450 {
451 type PropagatorImpl = Self;
452
453 fn create(self, mut context: PropagatorConstructorContext) -> Self::PropagatorImpl {
454 for (index, variable) in self.variables_to_register.iter().enumerate() {
455 context.register(
456 *variable,
457 DomainEvents::ANY_INT,
458 LocalId::from(index as u32),
459 );
460 }
461
462 self
463 }
464 }
465
466 impl<Propagation, ConsistencyCheck> Propagator for GenericPropagator<Propagation, ConsistencyCheck>
467 where
468 Propagation: Fn(PropagationContext) -> PropagationStatusCP + 'static + Clone,
469 ConsistencyCheck: Fn(Domains) -> Option<PropagatorConflict> + 'static + Clone,
470 {
471 fn name(&self) -> &str {
472 "Generic Propagator"
473 }
474
475 fn propagate_from_scratch(&self, context: PropagationContext) -> PropagationStatusCP {
476 (self.propagation)(context)
477 }
478
479 fn detect_inconsistency(&self, domains: Domains) -> Option<PropagatorConflict> {
480 (self.consistency_check)(domains)
481 }
482 }
483
484 impl<Propagation, ConsistencyCheck> GenericPropagator<Propagation, ConsistencyCheck>
485 where
486 Propagation: Fn(PropagationContext) -> PropagationStatusCP,
487 ConsistencyCheck: Fn(Domains) -> Option<PropagatorConflict>,
488 {
489 pub(crate) fn new(propagation: Propagation, consistency_check: ConsistencyCheck) -> Self {
490 GenericPropagator {
491 propagation,
492 consistency_check,
493 variables_to_register: vec![],
494 }
495 }
496
497 pub(crate) fn with_variables(mut self, variables: &[DomainId]) -> Self {
498 self.variables_to_register = variables.into();
500 self
501 }
502 }
503}