pumpkin_core/conflict_resolving/
conflict_analysis_context.rs1use std::fmt::Debug;
2
3use crate::Random;
4use crate::basic_types::StoredConflictInfo;
5use crate::branching::Brancher;
6#[cfg(doc)]
7use crate::branching::branchers::autonomous_search::AutonomousSearch;
8#[cfg(doc)]
9use crate::conflict_resolving::ConflictResolver;
10use crate::conflict_resolving::LearnedNogood;
11use crate::containers::HashMap;
12use crate::engine::Assignments;
13use crate::engine::ConstraintSatisfactionSolver;
14use crate::engine::EmptyDomainConflict;
15use crate::engine::RestartStrategy;
16use crate::engine::State;
17use crate::engine::TrailedValues;
18use crate::engine::constraint_satisfaction_solver::CSPSolverState;
19use crate::engine::constraint_satisfaction_solver::NogoodLabel;
20use crate::engine::predicates::predicate::Predicate;
21use crate::engine::predicates::predicate::PredicateType;
22use crate::predicate;
23use crate::predicates::PropositionalConjunction;
24use crate::proof::ConstraintTag;
25use crate::proof::InferenceCode;
26use crate::proof::ProofLog;
27use crate::proof::RootExplanationContext;
28use crate::proof::explain_root_assignment;
29use crate::propagation::CurrentNogood;
30use crate::propagation::ExplanationContext;
31use crate::propagation::HasAssignments;
32use crate::propagators::nogoods::NogoodChecker;
33use crate::propagators::nogoods::NogoodPropagator;
34use crate::pumpkin_assert_eq_simple;
35use crate::state::EmptyDomain;
36use crate::state::PropagatorHandle;
37
38pub struct ConflictAnalysisContext<'a> {
42 pub(crate) solver_state: &'a mut CSPSolverState,
43 pub(crate) brancher: &'a mut dyn Brancher,
44
45 pub(crate) proof_log: &'a mut ProofLog,
46
47 pub(crate) unit_nogood_inference_codes: &'a mut HashMap<Predicate, InferenceCode>,
48
49 pub(crate) restart_strategy: &'a mut RestartStrategy,
50 pub(crate) state: &'a mut State,
51
52 pub(crate) nogood_propagator_handle: PropagatorHandle<NogoodPropagator>,
53
54 pub(crate) rng: &'a mut dyn Random,
55}
56
57impl Debug for ConflictAnalysisContext<'_> {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 f.debug_struct(std::any::type_name::<Self>()).finish()
60 }
61}
62
63impl ConflictAnalysisContext<'_> {
64 pub fn get_state(&self) -> &State {
65 self.state
66 }
67
68 pub fn post(&mut self, predicate: Predicate) -> Result<bool, EmptyDomain> {
75 self.state.post(predicate)
76 }
77
78 pub fn restore_to(&mut self, checkpoint: usize) {
86 ConstraintSatisfactionSolver::backtrack(self.state, checkpoint, self.brancher, self.rng);
87 }
88
89 pub fn get_conflict_nogood(&mut self) -> Vec<Predicate> {
92 let conflict_nogood = match self.solver_state.get_conflict_info() {
93 StoredConflictInfo::Propagator(conflict) => {
94 let _ = self.proof_log.log_inference(
95 &mut self.state.constraint_tags,
96 conflict.inference_code,
97 conflict.conjunction.iter().copied(),
98 None,
99 &self.state.variable_names,
100 &self.state.assignments,
101 );
102
103 conflict.conjunction
104 }
105 StoredConflictInfo::EmptyDomain(conflict) => self.compute_conflict_nogood(conflict),
106 StoredConflictInfo::RootLevelConflict(_) => {
107 unreachable!("Should never attempt to learn a nogood from a root level conflict")
108 }
109 StoredConflictInfo::InconsistentAssumptions(predicate) => {
110 vec![predicate, !predicate].into()
111 }
112 };
113
114 for &predicate in conflict_nogood.iter() {
115 let predicate_dl = self
116 .state
117 .get_checkpoint_for_predicate(predicate)
118 .expect("all predicates in the conflict nogood should be assigned to true");
119
120 if predicate_dl == 0 {
121 explain_root_assignment(
122 &mut RootExplanationContext {
123 proof_log: self.proof_log,
124 unit_nogood_inference_codes: self.unit_nogood_inference_codes,
125 state: self.state,
126 },
127 predicate,
128 );
129 }
130 }
131
132 conflict_nogood
133 .into_iter()
134 .filter(|&p| self.state.get_checkpoint_for_predicate(p).unwrap() > 0)
135 .collect()
136 }
137
138 pub fn get_propagation_reason(
143 &mut self,
144 predicate: Predicate,
145 current_nogood: CurrentNogood<'_>,
146 reason_buffer: &mut (impl Extend<Predicate> + AsRef<[Predicate]>),
147 ) {
148 Self::get_propagation_reason_inner(
149 predicate,
150 current_nogood,
151 self.proof_log,
152 self.unit_nogood_inference_codes,
153 reason_buffer,
154 self.state,
155 );
156 }
157
158 pub fn find_last_decision(&mut self) -> Option<Predicate> {
160 self.state.assignments.find_last_decision()
161 }
162}
163
164impl ConflictAnalysisContext<'_> {
166 pub fn explain_root_assignment(&mut self, predicate: Predicate) {
168 explain_root_assignment(
169 &mut RootExplanationContext {
170 proof_log: self.proof_log,
171 unit_nogood_inference_codes: self.unit_nogood_inference_codes,
172 state: self.state,
173 },
174 predicate,
175 );
176 }
177
178 pub fn log_deduction(
183 &mut self,
184 premises: impl IntoIterator<Item = Predicate>,
185 ) -> ConstraintTag {
186 self.proof_log
187 .log_deduction(
188 premises,
189 &self.state.variable_names,
190 &mut self.state.constraint_tags,
191 &self.state.assignments,
192 )
193 .expect("Failed to write proof log")
194 }
195}
196
197impl ConflictAnalysisContext<'_> {
199 pub fn predicate_appeared_in_conflict(&mut self, predicate: Predicate) {
204 self.brancher.on_appearance_in_conflict_predicate(predicate);
205 }
206}
207
208impl ConflictAnalysisContext<'_> {
209 pub fn process_learned_nogood(
212 &mut self,
213 learned_nogood_predicates: Vec<Predicate>,
214 lbd: u32,
215 ) -> usize {
216 self.restart_strategy
220 .notify_conflict(lbd, self.state.assignments.get_pruned_value_count());
221
222 let learned_nogood = LearnedNogood::create_from_vec(learned_nogood_predicates, self);
223
224 let constraint_tag = self.log_deduction(learned_nogood.predicates.iter().copied());
225 let inference_code = InferenceCode::new(constraint_tag, NogoodLabel);
226
227 self.state.add_inference_checker(
228 inference_code.clone(),
229 Box::new(NogoodChecker {
230 nogood: learned_nogood.predicates.clone().into(),
231 }),
232 );
233
234 self.restore_to(learned_nogood.backtrack_level);
235
236 if learned_nogood.len() == 1 {
237 let _ = self
238 .unit_nogood_inference_codes
239 .insert(!learned_nogood[0], inference_code.clone());
240 }
241
242 #[cfg(feature = "check-propagations")]
243 let trail_len_before_nogood = self.state.trail_len();
244
245 let (nogood_propagator, mut propagation_context) = self
246 .state
247 .get_propagator_mut_with_context(self.nogood_propagator_handle);
248 let nogood_propagator =
249 nogood_propagator.expect("nogood propagator handle should refer to nogood propagator");
250
251 nogood_propagator.add_asserting_nogood(
252 learned_nogood.to_vec(),
253 inference_code,
254 &mut propagation_context,
255 );
256
257 #[cfg(feature = "check-propagations")]
258 self.state.check_propagations(trail_len_before_nogood);
259
260 learned_nogood.backtrack_level
261 }
262
263 pub(crate) fn get_propagation_reason_inner(
268 predicate: Predicate,
269 current_nogood: CurrentNogood<'_>,
270 proof_log: &mut ProofLog,
271 unit_nogood_inference_codes: &HashMap<Predicate, InferenceCode>,
272 reason_buffer: &mut (impl Extend<Predicate> + AsRef<[Predicate]>),
273 state: &mut State,
274 ) {
275 let trail_index = state.get_propagation_reason(predicate, reason_buffer, current_nogood);
276
277 if let Some(trail_index) = trail_index {
278 let trail_entry = state.assignments.get_trail_entry(trail_index);
279 let (reason_ref, inference_code) = trail_entry
280 .reason
281 .expect("Cannot be a null reason for propagation.");
282
283 let propagator_id = state.reason_store.get_propagator(reason_ref);
284
285 if state
286 .propagators
287 .as_propagator_handle::<NogoodPropagator>(propagator_id)
288 .is_some()
289 && reason_buffer.as_ref().is_empty()
290 {
291 let inference_code = unit_nogood_inference_codes
297 .get(&predicate)
298 .or_else(|| {
299 let domain_id = predicate.get_domain();
303 let right_hand_side = predicate.get_right_hand_side();
304
305 unit_nogood_inference_codes.get(&predicate!(domain_id == right_hand_side))
306 })
307 .expect("Expected to be able to retrieve step id for unit nogood");
308
309 let _ = proof_log.log_inference(
310 &mut state.constraint_tags,
311 inference_code.clone(),
312 [],
313 Some(predicate),
314 &state.variable_names,
315 &state.assignments,
316 );
317 } else {
318 let _ = proof_log.log_inference(
320 &mut state.constraint_tags,
321 inference_code,
322 reason_buffer.as_ref().iter().copied(),
323 Some(predicate),
324 &state.variable_names,
325 &state.assignments,
326 );
327 }
328 }
329 }
330
331 fn compute_conflict_nogood(
332 &mut self,
333 conflict: EmptyDomainConflict,
334 ) -> PropositionalConjunction {
335 let conflict_domain = conflict.domain();
336
337 let mut empty_domain_reason: Vec<Predicate> = vec![];
340 let _ = self.state.reason_store.get_or_compute(
341 conflict.trigger_reason,
342 ExplanationContext::without_working_nogood(
343 &self.state.assignments,
344 self.state.assignments.num_trail_entries(), &mut self.state.notification_engine,
348 ),
349 &mut self.state.propagators,
350 &mut empty_domain_reason,
351 );
352
353 let _ = self.proof_log.log_inference(
355 &mut self.state.constraint_tags,
356 conflict.trigger_inference_code,
357 empty_domain_reason.iter().copied(),
358 Some(conflict.trigger_predicate),
359 &self.state.variable_names,
360 &self.state.assignments,
361 );
362
363 let old_lower_bound = self.state.lower_bound(conflict_domain);
364 let old_upper_bound = self.state.upper_bound(conflict_domain);
365
366 match conflict.trigger_predicate.get_predicate_type() {
367 PredicateType::LowerBound => {
368 empty_domain_reason.push(predicate!(
374 conflict_domain <= conflict.trigger_predicate.get_right_hand_side() - 1
375 ));
376 }
377 PredicateType::UpperBound => {
378 empty_domain_reason.push(predicate!(
384 conflict_domain >= conflict.trigger_predicate.get_right_hand_side() + 1
385 ));
386 }
387 PredicateType::NotEqual => {
388 pumpkin_assert_eq_simple!(old_upper_bound, old_lower_bound);
391
392 empty_domain_reason.push(predicate!(conflict_domain == old_lower_bound));
393 }
394 PredicateType::Equal => {
395 if conflict.trigger_predicate.get_right_hand_side() < old_lower_bound {
397 empty_domain_reason.push(predicate!(
402 conflict_domain >= conflict.trigger_predicate.get_right_hand_side() + 1
403 ));
404 } else if conflict.trigger_predicate.get_right_hand_side() > old_upper_bound {
405 empty_domain_reason.push(predicate!(
410 conflict_domain <= conflict.trigger_predicate.get_right_hand_side() - 1
411 ));
412 } else {
413 empty_domain_reason.push(predicate!(
415 conflict_domain != conflict.trigger_predicate.get_right_hand_side()
416 ))
417 }
418 }
419 }
420
421 empty_domain_reason.into()
422 }
423}
424
425impl HasAssignments for ConflictAnalysisContext<'_> {
426 fn assignments(&self) -> &Assignments {
427 &self.state.assignments
428 }
429
430 fn trailed_values(&self) -> &TrailedValues {
431 &self.state.trailed_values
432 }
433
434 fn trailed_values_mut(&mut self) -> &mut TrailedValues {
435 &mut self.state.trailed_values
436 }
437}