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