1use std::cmp::max;
4use std::collections::VecDeque;
5use std::fmt::Debug;
6use std::time::Instant;
7
8use rand::rngs::SmallRng;
9use rand::SeedableRng;
10
11use super::conflict_analysis::AnalysisMode;
12use super::conflict_analysis::ConflictAnalysisContext;
13use super::conflict_analysis::LearnedNogood;
14use super::conflict_analysis::NoLearningResolver;
15use super::conflict_analysis::SemanticMinimiser;
16use super::notifications::NotificationEngine;
17use super::propagation::constructor::PropagatorConstructor;
18use super::propagation::store::PropagatorStore;
19use super::propagation::PropagatorId;
20use super::solver_statistics::SolverStatistics;
21use super::termination::TerminationCondition;
22use super::variables::IntegerVariable;
23use super::variables::Literal;
24use super::Lbd;
25use super::ResolutionResolver;
26use super::TrailedValues;
27use super::VariableNames;
28use crate::basic_types::moving_averages::MovingAverage;
29use crate::basic_types::CSPSolverExecutionFlag;
30use crate::basic_types::ConstraintOperationError;
31use crate::basic_types::Inconsistency;
32use crate::basic_types::PredicateId;
33use crate::basic_types::PropositionalConjunction;
34use crate::basic_types::Random;
35use crate::basic_types::SolutionReference;
36use crate::basic_types::StoredConflictInfo;
37use crate::branching::Brancher;
38use crate::branching::SelectionContext;
39use crate::containers::HashMap;
40use crate::declare_inference_label;
41use crate::engine::conflict_analysis::ConflictResolver as Resolver;
42use crate::engine::cp::PropagatorQueue;
43use crate::engine::predicates::predicate::Predicate;
44use crate::engine::propagation::constructor::PropagatorConstructorContext;
45use crate::engine::propagation::store::PropagatorHandle;
46use crate::engine::propagation::ExplanationContext;
47use crate::engine::propagation::PropagationContext;
48use crate::engine::propagation::PropagationContextMut;
49use crate::engine::propagation::Propagator;
50use crate::engine::reason::ReasonStore;
51use crate::engine::variables::DomainId;
52use crate::engine::Assignments;
53use crate::engine::DebugHelper;
54use crate::engine::RestartOptions;
55use crate::engine::RestartStrategy;
56use crate::predicate;
57use crate::predicates::PredicateType;
58use crate::proof::explain_root_assignment;
59use crate::proof::finalize_proof;
60use crate::proof::ConstraintTag;
61use crate::proof::FinalizingContext;
62use crate::proof::InferenceCode;
63use crate::proof::ProofLog;
64use crate::proof::RootExplanationContext;
65use crate::propagators::nogoods::LearningOptions;
66use crate::propagators::nogoods::NogoodPropagator;
67use crate::pumpkin_assert_advanced;
68use crate::pumpkin_assert_eq_simple;
69use crate::pumpkin_assert_extreme;
70use crate::pumpkin_assert_moderate;
71use crate::pumpkin_assert_simple;
72use crate::statistics::statistic_logger::StatisticLogger;
73use crate::statistics::statistic_logging::should_log_statistics;
74#[cfg(doc)]
75use crate::Solver;
76
77#[derive(Debug)]
107pub struct ConstraintSatisfactionSolver {
108 pub(crate) state: CSPSolverState,
111 propagators: PropagatorStore,
114 nogood_propagator_handle: PropagatorHandle<NogoodPropagator>,
115
116 restart_strategy: RestartStrategy,
120 assumptions: Vec<Predicate>,
122 semantic_minimiser: SemanticMinimiser,
123 pub(crate) assignments: Assignments,
125 propagator_queue: PropagatorQueue,
127 pub(crate) reason_store: ReasonStore,
130 solver_statistics: SolverStatistics,
132 internal_parameters: SatisfactionSolverOptions,
134 variable_names: VariableNames,
136 lbd_helper: Lbd,
138 unit_nogood_inference_codes: HashMap<Predicate, InferenceCode>,
140 conflict_resolver: Box<dyn Resolver>,
142 pub(crate) trailed_values: TrailedValues,
144 notification_engine: NotificationEngine,
147}
148
149impl Default for ConstraintSatisfactionSolver {
150 fn default() -> Self {
151 ConstraintSatisfactionSolver::new(SatisfactionSolverOptions::default())
152 }
153}
154
155#[derive(Debug, Clone)]
163pub enum CoreExtractionResult {
164 ConflictingAssumption(Predicate),
167 Core(Vec<Predicate>),
170}
171
172#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
179#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
180pub enum ConflictResolver {
181 NoLearning,
182 #[default]
183 UIP,
184}
185
186#[derive(Debug)]
188pub struct SatisfactionSolverOptions {
189 pub restart_options: RestartOptions,
191 pub learning_clause_minimisation: bool,
193 pub random_generator: SmallRng,
195 pub proof_log: ProofLog,
197 pub conflict_resolver: ConflictResolver,
199 pub learning_options: LearningOptions,
201 pub memory_preallocated: usize,
203}
204
205impl Default for SatisfactionSolverOptions {
206 fn default() -> Self {
207 SatisfactionSolverOptions {
208 restart_options: RestartOptions::default(),
209 learning_clause_minimisation: true,
210 random_generator: SmallRng::seed_from_u64(42),
211 proof_log: ProofLog::default(),
212 conflict_resolver: ConflictResolver::default(),
213 learning_options: LearningOptions::default(),
214 memory_preallocated: 1000,
215 }
216 }
217}
218
219impl ConstraintSatisfactionSolver {
220 pub fn get_solution_reference(&self) -> SolutionReference<'_> {
222 SolutionReference::new(&self.assignments)
223 }
224
225 pub fn conclude_proof_unsat(&mut self) -> std::io::Result<()> {
229 let proof = std::mem::take(&mut self.internal_parameters.proof_log);
230 proof.unsat(&self.variable_names)
231 }
232
233 pub fn conclude_proof_optimal(&mut self, bound: Predicate) -> std::io::Result<()> {
237 let proof = std::mem::take(&mut self.internal_parameters.proof_log);
238 proof.optimal(bound, &self.variable_names)
239 }
240
241 fn complete_proof(&mut self) {
242 let conflict = match self.state.get_conflict_info() {
243 StoredConflictInfo::Propagator(conflict) => {
244 let _ = self.internal_parameters.proof_log.log_inference(
245 conflict.inference_code,
246 conflict.conjunction.iter().copied(),
247 None,
248 &self.variable_names,
249 );
250
251 conflict.conjunction.clone()
252 }
253 StoredConflictInfo::EmptyDomain { conflict_nogood } => conflict_nogood.clone(),
254 StoredConflictInfo::RootLevelConflict(_) => {
255 unreachable!("There should always be a specified conflicting constraint.")
256 }
257 };
258
259 let context = FinalizingContext {
260 conflict,
261 propagators: &mut self.propagators,
262 proof_log: &mut self.internal_parameters.proof_log,
263 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
264 assignments: &self.assignments,
265 reason_store: &mut self.reason_store,
266 notification_engine: &mut self.notification_engine,
267 variable_names: &self.variable_names,
268 };
269
270 finalize_proof(context);
271 }
272
273 pub(crate) fn is_logging_full_proof(&self) -> bool {
274 self.internal_parameters.proof_log.is_logging_inferences()
275 }
276}
277
278impl ConstraintSatisfactionSolver {
280 pub fn new(solver_options: SatisfactionSolverOptions) -> Self {
281 let mut propagators = PropagatorStore::default();
282
283 let nogood_propagator_slot = propagators.new_propagator();
284 let nogood_propagator = NogoodPropagator::with_options(
285 nogood_propagator_slot.key(),
286 (solver_options.memory_preallocated * 1_000_000) / size_of::<PredicateId>(),
288 solver_options.learning_options,
289 );
290
291 let nogood_propagator_handle = nogood_propagator_slot.populate(nogood_propagator);
292
293 let mut csp_solver = ConstraintSatisfactionSolver {
294 state: CSPSolverState::default(),
295 assumptions: Vec::default(),
296 assignments: Assignments::default(),
297 propagator_queue: PropagatorQueue::new(5),
298 reason_store: ReasonStore::default(),
299 restart_strategy: RestartStrategy::new(solver_options.restart_options),
300 propagators,
301 nogood_propagator_handle,
302 solver_statistics: SolverStatistics::default(),
303 variable_names: VariableNames::default(),
304 semantic_minimiser: SemanticMinimiser::default(),
305 lbd_helper: Lbd::default(),
306 unit_nogood_inference_codes: Default::default(),
307 conflict_resolver: match solver_options.conflict_resolver {
308 ConflictResolver::NoLearning => Box::new(NoLearningResolver),
309 ConflictResolver::UIP => Box::new(ResolutionResolver::default()),
310 },
311 internal_parameters: solver_options,
312 trailed_values: TrailedValues::default(),
313 notification_engine: NotificationEngine::default(),
314 };
315
316 let dummy_id = Predicate::trivially_true().get_domain();
320
321 csp_solver
322 .variable_names
323 .add_integer(dummy_id, "Dummy".to_owned());
324
325 assert!(dummy_id.id() == 0);
326 assert!(csp_solver.assignments.get_lower_bound(dummy_id) == 1);
327 assert!(csp_solver.assignments.get_upper_bound(dummy_id) == 1);
328
329 csp_solver
330 }
331
332 pub fn solve(
333 &mut self,
334 termination: &mut impl TerminationCondition,
335 brancher: &mut impl Brancher,
336 ) -> CSPSolverExecutionFlag {
337 let dummy_assumptions: Vec<Predicate> = vec![];
338 self.solve_under_assumptions(&dummy_assumptions, termination, brancher)
339 }
340
341 pub fn solve_under_assumptions(
342 &mut self,
343 assumptions: &[Predicate],
344 termination: &mut impl TerminationCondition,
345 brancher: &mut impl Brancher,
346 ) -> CSPSolverExecutionFlag {
347 if self.state.is_inconsistent() {
348 return CSPSolverExecutionFlag::Infeasible;
349 }
350
351 let start_time = Instant::now();
352
353 self.initialise(assumptions);
354 let result = self.solve_internal(termination, brancher);
355
356 self.solver_statistics
357 .engine_statistics
358 .time_spent_in_solver += start_time.elapsed();
359
360 result
361 }
362
363 pub fn get_state(&self) -> &CSPSolverState {
364 &self.state
365 }
366
367 pub fn get_random_generator(&mut self) -> &mut impl Random {
368 &mut self.internal_parameters.random_generator
369 }
370
371 pub fn log_statistics(&self, verbose: bool) {
372 if !should_log_statistics() {
375 return;
376 }
377
378 self.solver_statistics.log(
379 &self.assignments,
380 &self.propagators,
381 StatisticLogger::default(),
382 verbose,
383 );
384 if verbose {
385 for (index, propagator) in self.propagators.iter_propagators().enumerate() {
386 propagator.log_statistics(StatisticLogger::new([
387 propagator.name(),
388 "number",
389 index.to_string().as_str(),
390 ]));
391 }
392 }
393 }
394
395 pub fn create_new_literal(&mut self, name: Option<String>) -> Literal {
396 let domain_id = self.create_new_integer_variable(0, 1, name);
397 Literal::new(domain_id)
398 }
399
400 pub fn create_new_literal_for_predicate(
401 &mut self,
402 predicate: Predicate,
403 name: Option<String>,
404 constraint_tag: ConstraintTag,
405 ) -> Literal {
406 let literal = self.create_new_literal(name);
407
408 self.internal_parameters
409 .proof_log
410 .reify_predicate(literal, predicate);
411
412 let _ = self.add_clause(
414 vec![!literal.get_true_predicate(), predicate],
415 constraint_tag,
416 );
417
418 let _ = self.add_clause(
420 vec![!literal.get_false_predicate(), !predicate],
421 constraint_tag,
422 );
423
424 literal
425 }
426
427 pub fn create_new_integer_variable(
429 &mut self,
430 lower_bound: i32,
431 upper_bound: i32,
432 name: Option<String>,
433 ) -> DomainId {
434 assert!(
435 !self.state.is_inconsistent(),
436 "Variables cannot be created in an inconsistent state"
437 );
438
439 let domain_id = self.assignments.grow(lower_bound, upper_bound);
440 self.notification_engine.grow();
441
442 if let Some(name) = name {
443 self.variable_names.add_integer(domain_id, name);
444 }
445
446 domain_id
447 }
448
449 pub fn create_new_integer_variable_sparse(
451 &mut self,
452 values: Vec<i32>,
453 name: Option<String>,
454 ) -> DomainId {
455 let domain_id = self.assignments.create_new_integer_variable_sparse(values);
456
457 self.notification_engine.grow();
458
459 if let Some(name) = name {
460 self.variable_names.add_integer(domain_id, name);
461 }
462
463 domain_id
464 }
465
466 pub fn new_constraint_tag(&mut self) -> ConstraintTag {
468 self.internal_parameters.proof_log.new_constraint_tag()
469 }
470
471 pub fn extract_clausal_core(&mut self, brancher: &mut impl Brancher) -> CoreExtractionResult {
537 if self.state.is_infeasible() {
538 return CoreExtractionResult::Core(vec![]);
539 }
540
541 self.assumptions
542 .iter()
543 .enumerate()
544 .find(|(index, assumption)| {
545 self.assumptions
546 .iter()
547 .skip(index + 1)
548 .any(|other_assumptiion| {
549 assumption.is_mutually_exclusive_with(*other_assumptiion)
550 })
551 })
552 .map(|(_, conflicting_assumption)| {
553 CoreExtractionResult::ConflictingAssumption(*conflicting_assumption)
554 })
555 .unwrap_or_else(|| {
556 let mut conflict_analysis_context = ConflictAnalysisContext {
557 assignments: &mut self.assignments,
558 counters: &mut self.solver_statistics,
559 solver_state: &mut self.state,
560 reason_store: &mut self.reason_store,
561 brancher,
562 semantic_minimiser: &mut self.semantic_minimiser,
563 propagators: &mut self.propagators,
564 notification_engine: &mut self.notification_engine,
565 propagator_queue: &mut self.propagator_queue,
566 should_minimise: self.internal_parameters.learning_clause_minimisation,
567 proof_log: &mut self.internal_parameters.proof_log,
568 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
569 trailed_values: &mut self.trailed_values,
570 variable_names: &self.variable_names,
571 rng: &mut self.internal_parameters.random_generator,
572 };
573
574 let mut resolver = ResolutionResolver::with_mode(AnalysisMode::AllDecision);
575 let learned_nogood = resolver
576 .resolve_conflict(&mut conflict_analysis_context)
577 .expect("Expected core extraction to be able to extract a core");
578
579 CoreExtractionResult::Core(learned_nogood.predicates.clone())
580 })
581 }
582
583 pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
584 let literal_is_true = self
585 .assignments
586 .is_predicate_satisfied(literal.get_true_predicate());
587 let opposite_literal_is_true = self
588 .assignments
589 .is_predicate_satisfied((!literal).get_true_predicate());
590
591 pumpkin_assert_moderate!(!(literal_is_true && opposite_literal_is_true));
592
593 if !literal_is_true && !opposite_literal_is_true {
596 None
597 } else {
598 Some(literal_is_true)
599 }
600 }
601
602 pub fn get_lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
604 variable.lower_bound(&self.assignments)
605 }
606
607 pub fn get_upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
609 variable.upper_bound(&self.assignments)
610 }
611
612 pub fn integer_variable_contains(&self, variable: &impl IntegerVariable, value: i32) -> bool {
614 variable.contains(&self.assignments, value)
615 }
616
617 pub fn get_assigned_integer_value(&self, variable: &impl IntegerVariable) -> Option<i32> {
619 let lb = self.get_lower_bound(variable);
620 let ub = self.get_upper_bound(variable);
621
622 if lb == ub {
623 Some(lb)
624 } else {
625 None
626 }
627 }
628
629 pub fn restore_state_at_root(&mut self, brancher: &mut impl Brancher) {
630 if self.assignments.get_decision_level() != 0 {
631 ConstraintSatisfactionSolver::backtrack(
632 &mut self.notification_engine,
633 &mut self.assignments,
634 &mut self.reason_store,
635 &mut self.propagator_queue,
636 &mut self.propagators,
637 0,
638 brancher,
639 &mut self.trailed_values,
640 &mut self.internal_parameters.random_generator,
641 );
642 self.state.declare_ready();
643 } else if self.state.internal_state == CSPSolverStateInternal::ContainsSolution {
644 self.state.declare_ready();
645 }
646 }
647}
648
649impl ConstraintSatisfactionSolver {
651 fn initialise(&mut self, assumptions: &[Predicate]) {
652 pumpkin_assert_simple!(
653 !self.state.is_infeasible_under_assumptions(),
654 "Solver is not expected to be in the infeasible under assumptions state when initialising.
655 Missed extracting the core?"
656 );
657 self.state.declare_solving();
658 assumptions.clone_into(&mut self.assumptions);
659 }
660
661 fn solve_internal(
662 &mut self,
663 termination: &mut impl TerminationCondition,
664 brancher: &mut impl Brancher,
665 ) -> CSPSolverExecutionFlag {
666 loop {
667 if termination.should_stop() {
668 self.state.declare_timeout();
669 return CSPSolverExecutionFlag::Timeout;
670 }
671
672 self.propagate();
673
674 if self.state.no_conflict() {
675 if self.get_decision_level() > self.assumptions.len()
681 && self.restart_strategy.should_restart()
682 {
683 self.restart_during_search(brancher);
684 }
685
686 let branching_result = self.make_next_decision(brancher);
687
688 self.solver_statistics.engine_statistics.peak_depth = max(
689 self.solver_statistics.engine_statistics.peak_depth,
690 self.assignments.get_decision_level() as u64,
691 );
692
693 match branching_result {
694 Err(CSPSolverExecutionFlag::Infeasible) => {
695 pumpkin_assert_simple!(self.state.is_infeasible_under_assumptions());
700
701 self.complete_proof();
702 return CSPSolverExecutionFlag::Infeasible;
703 }
704
705 Err(flag) => return flag,
706 Ok(()) => {}
707 }
708
709 if let Err(flag) = branching_result {
710 return flag;
711 }
712 } else {
713 if self.get_decision_level() == 0 {
714 self.complete_proof();
715 self.state.declare_infeasible();
716
717 return CSPSolverExecutionFlag::Infeasible;
718 }
719
720 self.resolve_conflict_with_nogood(brancher);
721
722 brancher.on_conflict();
723 self.decay_nogood_activities();
724 }
725 }
726 }
727
728 fn decay_nogood_activities(&mut self) {
729 match self
730 .propagators
731 .get_propagator_mut(self.nogood_propagator_handle)
732 {
733 Some(nogood_propagator) => {
734 nogood_propagator.decay_nogood_activities();
735 }
736 None => panic!("Provided propagator should be the nogood propagator"),
737 }
738 }
739
740 fn make_next_decision(
741 &mut self,
742 brancher: &mut impl Brancher,
743 ) -> Result<(), CSPSolverExecutionFlag> {
744 if let Some(assumption_literal) = self.peek_next_assumption_predicate() {
748 self.declare_new_decision_level();
749
750 let _ = self
751 .assignments
752 .post_predicate(assumption_literal, None, &mut self.notification_engine)
753 .map_err(|_| {
754 self.state
755 .declare_infeasible_under_assumptions(assumption_literal);
756 CSPSolverExecutionFlag::Infeasible
757 })?;
758
759 return Ok(());
760 }
761
762 let context = &mut SelectionContext::new(
764 &self.assignments,
765 &mut self.internal_parameters.random_generator,
766 );
767
768 let Some(decision_predicate) = brancher.next_decision(context) else {
770 self.state.declare_solution_found();
774 return Err(CSPSolverExecutionFlag::Feasible);
775 };
776
777 self.declare_new_decision_level();
778
779 pumpkin_assert_moderate!(
783 !self.assignments.is_predicate_satisfied(decision_predicate),
784 "Decision should not already be assigned; double check the brancher"
785 );
786
787 self.solver_statistics.engine_statistics.num_decisions += 1;
788 let update_occurred = self
789 .assignments
790 .post_predicate(decision_predicate, None, &mut self.notification_engine)
791 .expect("Decisions are expected not to fail.");
792 pumpkin_assert_simple!(update_occurred);
793
794 Ok(())
795 }
796
797 pub(crate) fn declare_new_decision_level(&mut self) {
798 self.assignments.increase_decision_level();
799 self.notification_engine.increase_decision_level();
800 self.trailed_values.increase_decision_level();
801 self.reason_store.increase_decision_level();
802 }
803
804 fn resolve_conflict_with_nogood(&mut self, brancher: &mut impl Brancher) {
815 pumpkin_assert_moderate!(self.state.is_conflicting());
816
817 let current_decision_level = self.get_decision_level();
818
819 let mut conflict_analysis_context = ConflictAnalysisContext {
820 assignments: &mut self.assignments,
821 counters: &mut self.solver_statistics,
822 solver_state: &mut self.state,
823 reason_store: &mut self.reason_store,
824 brancher,
825 semantic_minimiser: &mut self.semantic_minimiser,
826 notification_engine: &mut self.notification_engine,
827 propagators: &mut self.propagators,
828 propagator_queue: &mut self.propagator_queue,
829 should_minimise: self.internal_parameters.learning_clause_minimisation,
830 proof_log: &mut self.internal_parameters.proof_log,
831 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
832 trailed_values: &mut self.trailed_values,
833 variable_names: &self.variable_names,
834 rng: &mut self.internal_parameters.random_generator,
835 };
836
837 let learned_nogood = self
838 .conflict_resolver
839 .resolve_conflict(&mut conflict_analysis_context);
840
841 if let Some(learned_nogood) = learned_nogood.as_ref() {
845 conflict_analysis_context
846 .counters
847 .learned_clause_statistics
848 .average_backtrack_amount
849 .add_term((current_decision_level - learned_nogood.backjump_level) as u64);
850
851 conflict_analysis_context
852 .counters
853 .engine_statistics
854 .sum_of_backjumps +=
855 current_decision_level as u64 - 1 - learned_nogood.backjump_level as u64;
856 if current_decision_level - learned_nogood.backjump_level > 1 {
857 conflict_analysis_context
858 .counters
859 .engine_statistics
860 .num_backjumps += 1;
861 }
862
863 self.restart_strategy.notify_conflict(
864 self.lbd_helper.compute_lbd(
865 &learned_nogood.predicates,
866 conflict_analysis_context.assignments,
867 ),
868 conflict_analysis_context
869 .assignments
870 .get_pruned_value_count(),
871 );
872 }
873
874 let result = self
875 .conflict_resolver
876 .process(&mut conflict_analysis_context, &learned_nogood);
877 if result.is_err() {
878 unreachable!("Cannot resolve nogood and reach error")
879 }
880
881 if let Some(learned_nogood) = learned_nogood {
882 let constraint_tag = self
883 .internal_parameters
884 .proof_log
885 .log_deduction(
886 learned_nogood.predicates.iter().copied(),
887 &self.variable_names,
888 )
889 .expect("Failed to write proof log");
890
891 let inference_code = self
892 .internal_parameters
893 .proof_log
894 .create_inference_code(constraint_tag, NogoodLabel);
895
896 if learned_nogood.predicates.len() == 1 {
897 let _ = self
898 .unit_nogood_inference_codes
899 .insert(!learned_nogood.predicates[0], inference_code);
900 }
901
902 self.solver_statistics
903 .learned_clause_statistics
904 .num_unit_nogoods_learned += (learned_nogood.predicates.len() == 1) as u64;
905
906 self.solver_statistics
907 .learned_clause_statistics
908 .average_learned_nogood_length
909 .add_term(learned_nogood.predicates.len() as u64);
910
911 self.add_learned_nogood(learned_nogood, inference_code);
912 }
913
914 self.state.declare_solving();
915 }
916
917 fn add_learned_nogood(&mut self, learned_nogood: LearnedNogood, inference_code: InferenceCode) {
918 let mut context = PropagationContextMut::new(
919 &mut self.trailed_values,
920 &mut self.assignments,
921 &mut self.reason_store,
922 &mut self.semantic_minimiser,
923 &mut self.notification_engine,
924 self.nogood_propagator_handle.propagator_id(),
925 );
926
927 let nogood_propagator = self
928 .propagators
929 .get_propagator_mut(self.nogood_propagator_handle)
930 .expect("nogood propagator handle should refer to nogood propagator");
931
932 nogood_propagator.add_asserting_nogood(
933 learned_nogood.predicates,
934 inference_code,
935 &mut context,
936 &mut self.solver_statistics,
937 );
938 }
939
940 fn restart_during_search(&mut self, brancher: &mut impl Brancher) {
950 pumpkin_assert_simple!(
951 self.get_decision_level() > self.assumptions.len(),
952 "Sanity check: restarts should not trigger whilst assigning assumptions"
953 );
954
955 if self.get_decision_level() <= self.assumptions.len() {
957 return;
958 }
959
960 if brancher.is_restart_pointless() {
961 return;
964 }
965
966 self.solver_statistics.engine_statistics.num_restarts += 1;
967
968 ConstraintSatisfactionSolver::backtrack(
969 &mut self.notification_engine,
970 &mut self.assignments,
971 &mut self.reason_store,
972 &mut self.propagator_queue,
973 &mut self.propagators,
974 0,
975 brancher,
976 &mut self.trailed_values,
977 &mut self.internal_parameters.random_generator,
978 );
979
980 self.restart_strategy.notify_restart();
981 }
982
983 #[allow(
984 clippy::too_many_arguments,
985 reason = "This method requires this many arguments, though a backtracking context could be considered; for now this function needs to be used by conflict analysis"
986 )]
987 pub(crate) fn backtrack<BrancherType: Brancher + ?Sized>(
988 notification_engine: &mut NotificationEngine,
989 assignments: &mut Assignments,
990 reason_store: &mut ReasonStore,
991 propagator_queue: &mut PropagatorQueue,
992 propagators: &mut PropagatorStore,
993 backtrack_level: usize,
994 brancher: &mut BrancherType,
995 trailed_values: &mut TrailedValues,
996 rng: &mut dyn Random,
997 ) {
998 pumpkin_assert_simple!(backtrack_level < assignments.get_decision_level());
999
1000 brancher.on_backtrack();
1001
1002 assignments
1003 .synchronise(backtrack_level, notification_engine)
1004 .iter()
1005 .for_each(|(domain_id, previous_value)| {
1006 brancher.on_unassign_integer(*domain_id, *previous_value)
1007 });
1008
1009 trailed_values.synchronise(backtrack_level);
1010
1011 reason_store.synchronise(backtrack_level);
1012 propagator_queue.clear();
1013 for propagator in propagators.iter_propagators_mut() {
1018 let context = PropagationContext::new(assignments);
1019 propagator.synchronise(context);
1020 }
1021
1022 brancher.synchronise(&mut SelectionContext::new(assignments, rng));
1023
1024 let _ = notification_engine.process_backtrack_events(assignments, propagators);
1025 notification_engine.clear_event_drain();
1026
1027 notification_engine.update_last_notified_index(assignments);
1028 notification_engine.synchronise(backtrack_level, assignments, trailed_values);
1030 }
1031
1032 pub(crate) fn compute_reason_for_empty_domain(&mut self) -> PropositionalConjunction {
1033 let entry = self.assignments.get_last_entry_on_trail();
1040 let (entry_reason, entry_inference_code) = entry
1041 .reason
1042 .expect("Cannot cause an empty domain using a decision.");
1043 let conflict_domain = entry.predicate.get_domain();
1044 assert!(
1045 entry.old_lower_bound != self.assignments.get_lower_bound(conflict_domain)
1046 || entry.old_upper_bound != self.assignments.get_upper_bound(conflict_domain),
1047 "One of the two bounds had to change."
1048 );
1049
1050 let mut empty_domain_reason: Vec<Predicate> = vec![];
1053 let _ = self.reason_store.get_or_compute(
1054 entry_reason,
1055 ExplanationContext::without_working_nogood(
1056 &self.assignments,
1057 self.assignments.num_trail_entries() - 1,
1058 &mut self.notification_engine,
1059 ),
1060 &mut self.propagators,
1061 &mut empty_domain_reason,
1062 );
1063
1064 let _ = self.internal_parameters.proof_log.log_inference(
1066 entry_inference_code,
1067 empty_domain_reason.iter().copied(),
1068 Some(entry.predicate),
1069 &self.variable_names,
1070 );
1071
1072 match entry.predicate.get_predicate_type() {
1074 PredicateType::LowerBound => {
1075 empty_domain_reason.push(predicate!(
1081 conflict_domain <= entry.predicate.get_right_hand_side() - 1
1082 ));
1083 }
1084 PredicateType::UpperBound => {
1085 empty_domain_reason.push(predicate!(
1091 conflict_domain >= entry.predicate.get_right_hand_side() + 1
1092 ));
1093 }
1094 PredicateType::NotEqual => {
1095 pumpkin_assert_eq_simple!(entry.old_upper_bound, entry.old_lower_bound);
1098
1099 empty_domain_reason.push(predicate!(conflict_domain == entry.old_lower_bound));
1100 }
1101 PredicateType::Equal => {
1102 if entry.predicate.get_right_hand_side() < entry.old_lower_bound {
1104 empty_domain_reason.push(predicate!(
1109 conflict_domain >= entry.predicate.get_right_hand_side() + 1
1110 ));
1111 } else if entry.predicate.get_right_hand_side() > entry.old_upper_bound {
1112 empty_domain_reason.push(predicate!(
1117 conflict_domain <= entry.predicate.get_right_hand_side() - 1
1118 ));
1119 } else {
1120 empty_domain_reason.push(predicate!(
1122 conflict_domain != entry.predicate.get_right_hand_side()
1123 ))
1124 }
1125 }
1126 }
1127
1128 empty_domain_reason.into()
1129 }
1130
1131 pub(crate) fn propagate(&mut self) {
1133 let num_assigned_variables_old = self.assignments.num_trail_entries();
1135 self.notification_engine
1137 .notify_propagators_about_domain_events(
1138 &mut self.assignments,
1139 &mut self.trailed_values,
1140 &mut self.propagators,
1141 self.nogood_propagator_handle,
1142 &mut self.propagator_queue,
1143 );
1144 while let Some(propagator_id) = self.propagator_queue.pop() {
1146 self.solver_statistics
1147 .engine_statistics
1148 .num_propagators_called += 1;
1149
1150 let num_trail_entries_before = self.assignments.num_trail_entries();
1151
1152 let propagation_status = {
1153 let propagator = &mut self.propagators[propagator_id];
1154 let context = PropagationContextMut::new(
1155 &mut self.trailed_values,
1156 &mut self.assignments,
1157 &mut self.reason_store,
1158 &mut self.semantic_minimiser,
1159 &mut self.notification_engine,
1160 propagator_id,
1161 );
1162 propagator.propagate(context)
1163 };
1164
1165 if self.assignments.get_decision_level() == 0 {
1166 self.handle_root_propagation(num_trail_entries_before);
1167 }
1168 match propagation_status {
1169 Ok(_) => {
1170 self.notification_engine
1172 .notify_propagators_about_domain_events(
1173 &mut self.assignments,
1174 &mut self.trailed_values,
1175 &mut self.propagators,
1176 self.nogood_propagator_handle,
1177 &mut self.propagator_queue,
1178 );
1179 }
1180 Err(inconsistency) => match inconsistency {
1181 Inconsistency::EmptyDomain => {
1183 self.prepare_for_conflict_resolution();
1184 break;
1185 }
1186 Inconsistency::Conflict(conflict) => {
1188 pumpkin_assert_advanced!(DebugHelper::debug_reported_failure(
1189 &self.trailed_values,
1190 &self.assignments,
1191 &conflict.conjunction,
1192 &self.propagators[propagator_id],
1193 propagator_id,
1194 &self.notification_engine
1195 ));
1196
1197 let stored_conflict_info = StoredConflictInfo::Propagator(conflict);
1198 self.state.declare_conflict(stored_conflict_info);
1199 break;
1200 }
1201 },
1202 }
1203 pumpkin_assert_extreme!(
1204 DebugHelper::debug_check_propagations(
1205 num_trail_entries_before,
1206 propagator_id,
1207 &self.trailed_values,
1208 &self.assignments,
1209 &mut self.reason_store,
1210 &mut self.propagators,
1211 &self.notification_engine
1212 ),
1213 "Checking the propagations performed by the propagator led to inconsistencies!"
1214 );
1215 }
1216 self.solver_statistics.engine_statistics.num_conflicts +=
1218 self.state.is_conflicting() as u64;
1219 self.solver_statistics.engine_statistics.num_propagations +=
1220 self.assignments.num_trail_entries() as u64 - num_assigned_variables_old as u64;
1221 pumpkin_assert_extreme!(
1224 self.state.is_conflicting()
1225 || DebugHelper::debug_fixed_point_propagation(
1226 &self.trailed_values,
1227 &self.assignments,
1228 &self.propagators,
1229 &self.notification_engine
1230 )
1231 );
1232 }
1233
1234 fn handle_root_propagation(&mut self, start_trail_index: usize) {
1241 pumpkin_assert_eq_simple!(self.get_decision_level(), 0);
1242
1243 for trail_idx in start_trail_index..self.assignments.num_trail_entries() {
1244 let entry = self.assignments.get_trail_entry(trail_idx);
1245 let (reason_ref, inference_code) = entry
1246 .reason
1247 .expect("Added by a propagator and must therefore have a reason");
1248
1249 if !self.internal_parameters.proof_log.is_logging_inferences() {
1250 let _ = self
1254 .unit_nogood_inference_codes
1255 .insert(entry.predicate, inference_code);
1256 continue;
1257 }
1258
1259 let mut reason = vec![];
1261 let _ = self.reason_store.get_or_compute(
1262 reason_ref,
1263 ExplanationContext::without_working_nogood(
1264 &self.assignments,
1265 trail_idx,
1266 &mut self.notification_engine,
1267 ),
1268 &mut self.propagators,
1269 &mut reason,
1270 );
1271
1272 let propagated = entry.predicate;
1273
1274 let inference_premises = reason.iter().copied().chain(std::iter::once(!propagated));
1276 let _ = self.internal_parameters.proof_log.log_inference(
1277 inference_code,
1278 inference_premises,
1279 None,
1280 &self.variable_names,
1281 );
1282
1283 let mut to_explain: VecDeque<Predicate> = reason.iter().copied().collect();
1294
1295 while let Some(premise) = to_explain.pop_front() {
1296 pumpkin_assert_simple!(self.assignments.is_predicate_satisfied(premise));
1297
1298 let mut context = RootExplanationContext {
1299 propagators: &mut self.propagators,
1300 proof_log: &mut self.internal_parameters.proof_log,
1301 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
1302 assignments: &self.assignments,
1303 reason_store: &mut self.reason_store,
1304 notification_engine: &mut self.notification_engine,
1305 variable_names: &self.variable_names,
1306 };
1307
1308 explain_root_assignment(&mut context, premise);
1309 }
1310
1311 let constraint_tag = self
1313 .internal_parameters
1314 .proof_log
1315 .log_deduction([!propagated], &self.variable_names);
1316
1317 if let Ok(constraint_tag) = constraint_tag {
1318 let inference_code = self
1319 .internal_parameters
1320 .proof_log
1321 .create_inference_code(constraint_tag, NogoodLabel);
1322
1323 let _ = self
1324 .unit_nogood_inference_codes
1325 .insert(propagated, inference_code);
1326 }
1327 }
1328 }
1329
1330 fn peek_next_assumption_predicate(&self) -> Option<Predicate> {
1331 let next_assumption_index = self.get_decision_level();
1334 self.assumptions.get(next_assumption_index).copied()
1335 }
1336}
1337
1338impl ConstraintSatisfactionSolver {
1340 pub(crate) fn add_propagator<Constructor>(
1342 &mut self,
1343 constructor: Constructor,
1344 ) -> Result<PropagatorHandle<Constructor::PropagatorImpl>, ConstraintOperationError>
1345 where
1346 Constructor: PropagatorConstructor,
1347 Constructor::PropagatorImpl: 'static,
1348 {
1349 if self.state.is_inconsistent() {
1350 return Err(ConstraintOperationError::InfeasiblePropagator);
1351 }
1352
1353 let propagator_slot = self.propagators.new_propagator();
1354
1355 let constructor_context = PropagatorConstructorContext::new(
1356 &mut self.notification_engine,
1357 &mut self.trailed_values,
1358 &mut self.internal_parameters.proof_log,
1359 propagator_slot.key().propagator_id(),
1360 &mut self.assignments,
1361 );
1362
1363 let propagator = constructor.create(constructor_context);
1364
1365 pumpkin_assert_simple!(
1366 propagator.priority() <= 3,
1367 "The propagator priority exceeds 3.
1368 Currently we only support values up to 3,
1369 but this can easily be changed if there is a good reason."
1370 );
1371 let new_propagator_id = propagator_slot.populate(propagator);
1372
1373 let new_propagator = &mut self.propagators[new_propagator_id.propagator_id()];
1374
1375 self.propagator_queue
1376 .enqueue_propagator(new_propagator_id.propagator_id(), new_propagator.priority());
1377
1378 self.propagate();
1379
1380 if self.state.no_conflict() {
1381 Ok(new_propagator_id)
1382 } else {
1383 self.complete_proof();
1384 let _ = self.conclude_proof_unsat();
1385 Err(ConstraintOperationError::InfeasiblePropagator)
1386 }
1387 }
1388
1389 pub fn post_predicate(&mut self, predicate: Predicate) -> Result<(), ConstraintOperationError> {
1390 assert!(
1391 self.get_decision_level() == 0,
1392 "Can only post predicates at the root level."
1393 );
1394
1395 if self.state.is_infeasible() {
1396 Err(ConstraintOperationError::InfeasibleState)
1397 } else {
1398 match self
1399 .assignments
1400 .post_predicate(predicate, None, &mut self.notification_engine)
1401 {
1402 Ok(_) => Ok(()),
1403 Err(_) => Err(ConstraintOperationError::InfeasibleNogood),
1404 }
1405 }
1406 }
1407
1408 fn add_nogood(
1409 &mut self,
1410 nogood: Vec<Predicate>,
1411 inference_code: InferenceCode,
1412 ) -> Result<(), ConstraintOperationError> {
1413 pumpkin_assert_eq_simple!(self.get_decision_level(), 0);
1414 let num_trail_entries = self.assignments.num_trail_entries();
1415
1416 let mut propagation_context = PropagationContextMut::new(
1417 &mut self.trailed_values,
1418 &mut self.assignments,
1419 &mut self.reason_store,
1420 &mut self.semantic_minimiser,
1421 &mut self.notification_engine,
1422 self.nogood_propagator_handle.propagator_id(),
1423 );
1424
1425 let nogood_propagator = self
1426 .propagators
1427 .get_propagator_mut(self.nogood_propagator_handle)
1428 .expect("nogood propagator handle should refer to nogood propagator");
1429
1430 let propagation_status =
1431 nogood_propagator.add_nogood(nogood, inference_code, &mut propagation_context);
1432
1433 if propagation_status.is_err() || self.state.is_conflicting() {
1434 self.prepare_for_conflict_resolution();
1435 self.handle_root_propagation(num_trail_entries);
1436 self.complete_proof();
1437 return Err(ConstraintOperationError::InfeasibleNogood);
1438 }
1439
1440 self.handle_root_propagation(num_trail_entries);
1441
1442 self.propagator_queue.enqueue_propagator(PropagatorId(0), 0);
1444 self.propagate();
1445
1446 self.handle_root_propagation(num_trail_entries);
1447
1448 if self.state.is_infeasible() {
1449 self.prepare_for_conflict_resolution();
1450 self.complete_proof();
1451 Err(ConstraintOperationError::InfeasibleState)
1452 } else {
1453 Ok(())
1454 }
1455 }
1456
1457 fn prepare_for_conflict_resolution(&mut self) {
1458 if self.state.is_conflicting() {
1459 return;
1460 }
1461
1462 let empty_domain_reason = self.compute_reason_for_empty_domain();
1463
1464 self.assignments.remove_last_trail_element();
1469
1470 let stored_conflict_info = StoredConflictInfo::EmptyDomain {
1471 conflict_nogood: empty_domain_reason,
1472 };
1473 self.state.declare_conflict(stored_conflict_info);
1474 }
1475
1476 pub fn add_clause(
1482 &mut self,
1483 predicates: impl IntoIterator<Item = Predicate>,
1484 constraint_tag: ConstraintTag,
1485 ) -> Result<(), ConstraintOperationError> {
1486 pumpkin_assert_simple!(
1487 self.get_decision_level() == 0,
1488 "Clauses can only be added in the root"
1489 );
1490
1491 if self.state.is_inconsistent() {
1492 return Err(ConstraintOperationError::InfeasiblePropagator);
1493 }
1494
1495 let mut are_all_falsified_at_root = true;
1500 let predicates = predicates
1501 .into_iter()
1502 .map(|predicate| {
1503 are_all_falsified_at_root &= self.assignments.is_predicate_falsified(predicate);
1504 !predicate
1505 })
1506 .collect::<Vec<_>>();
1507
1508 if predicates.is_empty() {
1509 self.state
1512 .declare_conflict(StoredConflictInfo::RootLevelConflict(
1513 ConstraintOperationError::InfeasibleClause,
1514 ));
1515 return Err(ConstraintOperationError::InfeasibleClause);
1516 }
1517
1518 if are_all_falsified_at_root {
1519 finalize_proof(FinalizingContext {
1520 conflict: predicates.into(),
1521 propagators: &mut self.propagators,
1522 proof_log: &mut self.internal_parameters.proof_log,
1523 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
1524 assignments: &self.assignments,
1525 reason_store: &mut self.reason_store,
1526 notification_engine: &mut self.notification_engine,
1527 variable_names: &self.variable_names,
1528 });
1529 self.state
1530 .declare_conflict(StoredConflictInfo::RootLevelConflict(
1531 ConstraintOperationError::InfeasibleClause,
1532 ));
1533 return Err(ConstraintOperationError::InfeasibleClause);
1534 }
1535
1536 let inference_code = self
1537 .internal_parameters
1538 .proof_log
1539 .create_inference_code(constraint_tag, NogoodLabel);
1540 if let Err(constraint_operation_error) = self.add_nogood(predicates, inference_code) {
1541 let _ = self.conclude_proof_unsat();
1542
1543 self.state
1544 .declare_conflict(StoredConflictInfo::RootLevelConflict(
1545 constraint_operation_error,
1546 ));
1547 return Err(constraint_operation_error);
1548 }
1549 Ok(())
1550 }
1551
1552 pub(crate) fn get_decision_level(&self) -> usize {
1553 self.assignments.get_decision_level()
1554 }
1555}
1556
1557#[derive(Default, Debug, PartialEq, Eq)]
1558enum CSPSolverStateInternal {
1559 #[default]
1560 Ready,
1561 Solving,
1562 ContainsSolution,
1563 Conflict {
1564 conflict_info: StoredConflictInfo,
1565 },
1566 Infeasible,
1567 InfeasibleUnderAssumptions {
1568 violated_assumption: Predicate,
1569 },
1570 Timeout,
1571}
1572
1573#[derive(Default, Debug)]
1574pub struct CSPSolverState {
1575 internal_state: CSPSolverStateInternal,
1576}
1577
1578impl CSPSolverState {
1579 pub fn is_ready(&self) -> bool {
1580 matches!(self.internal_state, CSPSolverStateInternal::Ready)
1581 }
1582
1583 pub fn no_conflict(&self) -> bool {
1584 !self.is_conflicting()
1585 }
1586
1587 pub fn is_conflicting(&self) -> bool {
1588 matches!(
1589 self.internal_state,
1590 CSPSolverStateInternal::Conflict { conflict_info: _ }
1591 )
1592 }
1593
1594 pub fn is_infeasible(&self) -> bool {
1595 matches!(self.internal_state, CSPSolverStateInternal::Infeasible)
1596 }
1597
1598 pub fn is_inconsistent(&self) -> bool {
1601 self.is_conflicting() || self.is_infeasible() || self.is_infeasible_under_assumptions()
1602 }
1603
1604 pub fn is_infeasible_under_assumptions(&self) -> bool {
1605 matches!(
1606 self.internal_state,
1607 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1608 violated_assumption: _
1609 }
1610 )
1611 }
1612
1613 pub fn get_violated_assumption(&self) -> Predicate {
1614 if let CSPSolverStateInternal::InfeasibleUnderAssumptions {
1615 violated_assumption,
1616 } = self.internal_state
1617 {
1618 violated_assumption
1619 } else {
1620 panic!(
1621 "Cannot extract violated assumption without getting the solver into the infeasible
1622 under assumptions state."
1623 );
1624 }
1625 }
1626
1627 pub(crate) fn get_conflict_info(&self) -> StoredConflictInfo {
1628 match &self.internal_state {
1629 CSPSolverStateInternal::Conflict { conflict_info } => conflict_info.clone(),
1630 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1631 violated_assumption,
1632 } => StoredConflictInfo::EmptyDomain {
1633 conflict_nogood: vec![*violated_assumption, !(*violated_assumption)].into(),
1634 },
1635 _ => {
1636 panic!("Cannot extract conflict clause if solver is not in a conflict.");
1637 }
1638 }
1639 }
1640
1641 pub fn timeout(&self) -> bool {
1642 matches!(self.internal_state, CSPSolverStateInternal::Timeout)
1643 }
1644
1645 pub fn has_solution(&self) -> bool {
1646 matches!(
1647 self.internal_state,
1648 CSPSolverStateInternal::ContainsSolution
1649 )
1650 }
1651
1652 pub(crate) fn declare_ready(&mut self) {
1653 self.internal_state = CSPSolverStateInternal::Ready;
1654 }
1655
1656 pub fn declare_solving(&mut self) {
1657 pumpkin_assert_simple!((self.is_ready() || self.is_conflicting()) && !self.is_infeasible());
1658 self.internal_state = CSPSolverStateInternal::Solving;
1659 }
1660
1661 fn declare_infeasible(&mut self) {
1662 self.internal_state = CSPSolverStateInternal::Infeasible;
1663 }
1664
1665 fn declare_conflict(&mut self, conflict_info: StoredConflictInfo) {
1666 self.internal_state = CSPSolverStateInternal::Conflict { conflict_info };
1667 }
1668
1669 fn declare_solution_found(&mut self) {
1670 pumpkin_assert_simple!(!self.is_infeasible());
1671 self.internal_state = CSPSolverStateInternal::ContainsSolution;
1672 }
1673
1674 fn declare_timeout(&mut self) {
1675 pumpkin_assert_simple!(!self.is_infeasible());
1676 self.internal_state = CSPSolverStateInternal::Timeout;
1677 }
1678
1679 fn declare_infeasible_under_assumptions(&mut self, violated_assumption: Predicate) {
1680 pumpkin_assert_simple!(!self.is_infeasible());
1681 self.internal_state = CSPSolverStateInternal::InfeasibleUnderAssumptions {
1682 violated_assumption,
1683 }
1684 }
1685}
1686
1687declare_inference_label!(NogoodLabel, "nogood");
1688
1689#[cfg(test)]
1690mod tests {
1691 use super::ConstraintSatisfactionSolver;
1692 use super::CoreExtractionResult;
1693 use crate::basic_types::CSPSolverExecutionFlag;
1694 use crate::predicate;
1695 use crate::predicates::Predicate;
1696 use crate::propagators::linear_not_equal::LinearNotEqualPropagatorArgs;
1697 use crate::termination::Indefinite;
1698 use crate::variables::TransformableVariable;
1699 use crate::DefaultBrancher;
1700
1701 fn is_same_core(core1: &[Predicate], core2: &[Predicate]) -> bool {
1702 core1.len() == core2.len() && core2.iter().all(|lit| core1.contains(lit))
1703 }
1704
1705 fn is_result_the_same(res1: &CoreExtractionResult, res2: &CoreExtractionResult) -> bool {
1706 match (res1, res2) {
1707 (
1708 CoreExtractionResult::ConflictingAssumption(assumption1),
1709 CoreExtractionResult::ConflictingAssumption(assumption2),
1710 ) => assumption1 == assumption2,
1711 (CoreExtractionResult::Core(core1), CoreExtractionResult::Core(core2)) => {
1712 is_same_core(core1, core2)
1713 }
1714 _ => false,
1715 }
1716 }
1717
1718 fn run_test(
1719 mut solver: ConstraintSatisfactionSolver,
1720 assumptions: Vec<Predicate>,
1721 expected_flag: CSPSolverExecutionFlag,
1722 expected_result: CoreExtractionResult,
1723 ) {
1724 let mut brancher = DefaultBrancher::default_over_all_variables(&solver.assignments);
1725 let flag = solver.solve_under_assumptions(&assumptions, &mut Indefinite, &mut brancher);
1726 assert_eq!(flag, expected_flag, "The flags do not match.");
1727
1728 if matches!(flag, CSPSolverExecutionFlag::Infeasible) {
1729 assert!(
1730 is_result_the_same(
1731 &solver.extract_clausal_core(&mut brancher),
1732 &expected_result
1733 ),
1734 "The result is not the same"
1735 );
1736 }
1737 }
1738
1739 fn create_instance1() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1740 let mut solver = ConstraintSatisfactionSolver::default();
1741 let constraint_tag = solver.new_constraint_tag();
1742 let lit1 = solver.create_new_literal(None).get_true_predicate();
1743 let lit2 = solver.create_new_literal(None).get_true_predicate();
1744
1745 let _ = solver.add_clause([lit1, lit2], constraint_tag);
1746 let _ = solver.add_clause([lit1, !lit2], constraint_tag);
1747 let _ = solver.add_clause([!lit1, lit2], constraint_tag);
1748 (solver, vec![lit1, lit2])
1749 }
1750
1751 #[test]
1752 fn core_extraction_unit_core() {
1753 let mut solver = ConstraintSatisfactionSolver::default();
1754 let constraint_tag = solver.new_constraint_tag();
1755 let lit1 = solver.create_new_literal(None).get_true_predicate();
1756 let _ = solver.add_clause(vec![lit1], constraint_tag);
1757
1758 run_test(
1759 solver,
1760 vec![!lit1],
1761 CSPSolverExecutionFlag::Infeasible,
1762 CoreExtractionResult::Core(vec![!lit1]),
1763 )
1764 }
1765
1766 #[test]
1767 fn simple_core_extraction_1_1() {
1768 let (solver, lits) = create_instance1();
1769 run_test(
1770 solver,
1771 vec![!lits[0], !lits[1]],
1772 CSPSolverExecutionFlag::Infeasible,
1773 CoreExtractionResult::Core(vec![!lits[0]]),
1774 )
1775 }
1776
1777 #[test]
1778 fn simple_core_extraction_1_2() {
1779 let (solver, lits) = create_instance1();
1780 run_test(
1781 solver,
1782 vec![!lits[1], !lits[0]],
1783 CSPSolverExecutionFlag::Infeasible,
1784 CoreExtractionResult::Core(vec![!lits[1]]),
1785 );
1786 }
1787
1788 #[test]
1789 fn simple_core_extraction_1_infeasible() {
1790 let (mut solver, lits) = create_instance1();
1791 let constraint_tag = solver.new_constraint_tag();
1792 let _ = solver.add_clause([!lits[0], !lits[1]], constraint_tag);
1793 run_test(
1794 solver,
1795 vec![!lits[1], !lits[0]],
1796 CSPSolverExecutionFlag::Infeasible,
1797 CoreExtractionResult::Core(vec![]),
1798 );
1799 }
1800
1801 #[test]
1802 fn simple_core_extraction_1_core_conflicting() {
1803 let (solver, lits) = create_instance1();
1804 run_test(
1805 solver,
1806 vec![!lits[1], lits[1]],
1807 CSPSolverExecutionFlag::Infeasible,
1808 CoreExtractionResult::ConflictingAssumption(!lits[1]),
1809 );
1810 }
1811 fn create_instance2() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1812 let mut solver = ConstraintSatisfactionSolver::default();
1813 let constraint_tag = solver.new_constraint_tag();
1814 let lit1 = solver.create_new_literal(None).get_true_predicate();
1815 let lit2 = solver.create_new_literal(None).get_true_predicate();
1816 let lit3 = solver.create_new_literal(None).get_true_predicate();
1817
1818 let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1819 let _ = solver.add_clause([lit1, !lit2, lit3], constraint_tag);
1820 (solver, vec![lit1, lit2, lit3])
1821 }
1822
1823 #[test]
1824 fn simple_core_extraction_2_1() {
1825 let (solver, lits) = create_instance2();
1826 run_test(
1827 solver,
1828 vec![!lits[0], lits[1], !lits[2]],
1829 CSPSolverExecutionFlag::Infeasible,
1830 CoreExtractionResult::Core(vec![!lits[0], lits[1], !lits[2]]),
1831 );
1832 }
1833
1834 #[test]
1835 fn simple_core_extraction_2_long_assumptions_with_inconsistency_at_the_end() {
1836 let (solver, lits) = create_instance2();
1837 run_test(
1838 solver,
1839 vec![!lits[0], lits[1], !lits[2], lits[0]],
1840 CSPSolverExecutionFlag::Infeasible,
1841 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1842 );
1843 }
1844
1845 #[test]
1846 fn simple_core_extraction_2_inconsistent_long_assumptions() {
1847 let (solver, lits) = create_instance2();
1848 run_test(
1849 solver,
1850 vec![!lits[0], !lits[0], !lits[1], !lits[1], lits[0]],
1851 CSPSolverExecutionFlag::Infeasible,
1852 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1853 );
1854 }
1855 fn create_instance3() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1856 let mut solver = ConstraintSatisfactionSolver::default();
1857 let constraint_tag = solver.new_constraint_tag();
1858
1859 let lit1 = solver.create_new_literal(None).get_true_predicate();
1860 let lit2 = solver.create_new_literal(None).get_true_predicate();
1861 let lit3 = solver.create_new_literal(None).get_true_predicate();
1862
1863 let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1864 (solver, vec![lit1, lit2, lit3])
1865 }
1866
1867 #[test]
1868 fn simple_core_extraction_3_1() {
1869 let (solver, lits) = create_instance3();
1870 run_test(
1871 solver,
1872 vec![!lits[0], !lits[1], !lits[2]],
1873 CSPSolverExecutionFlag::Infeasible,
1874 CoreExtractionResult::Core(vec![!lits[0], !lits[1], !lits[2]]),
1875 );
1876 }
1877
1878 #[test]
1879 fn simple_core_extraction_3_2() {
1880 let (solver, lits) = create_instance3();
1881 run_test(
1882 solver,
1883 vec![!lits[0], !lits[1]],
1884 CSPSolverExecutionFlag::Feasible,
1885 CoreExtractionResult::Core(vec![]), );
1887 }
1888
1889 #[test]
1890 fn core_extraction_equality_assumption() {
1891 let mut solver = ConstraintSatisfactionSolver::default();
1892
1893 let x = solver.create_new_integer_variable(0, 10, None);
1894 let y = solver.create_new_integer_variable(0, 10, None);
1895 let z = solver.create_new_integer_variable(0, 10, None);
1896
1897 let constraint_tag = solver.new_constraint_tag();
1898
1899 let result = solver.add_propagator(LinearNotEqualPropagatorArgs {
1900 terms: [x.scaled(1), y.scaled(-1)].into(),
1901 rhs: 0,
1902 constraint_tag,
1903 });
1904 assert!(result.is_ok());
1905 run_test(
1906 solver,
1907 vec![
1908 predicate!(x >= 5),
1909 predicate!(z != 10),
1910 predicate!(y == 5),
1911 predicate!(x <= 5),
1912 ],
1913 CSPSolverExecutionFlag::Infeasible,
1914 CoreExtractionResult::Core(vec![predicate!(x == 5), predicate!(y == 5)]),
1915 )
1916 }
1917
1918 #[test]
1919 fn new_domain_with_negative_lower_bound() {
1920 let lb = -2;
1921 let ub = 2;
1922
1923 let mut solver = ConstraintSatisfactionSolver::default();
1924 let domain_id = solver.create_new_integer_variable(lb, ub, None);
1925
1926 assert_eq!(lb, solver.assignments.get_lower_bound(domain_id));
1927
1928 assert_eq!(ub, solver.assignments.get_upper_bound(domain_id));
1929
1930 assert!(!solver
1931 .assignments
1932 .is_predicate_satisfied(predicate![domain_id == lb]));
1933
1934 for value in (lb + 1)..ub {
1935 let predicate = predicate![domain_id >= value];
1936
1937 assert!(!solver.assignments.is_predicate_satisfied(predicate));
1938
1939 assert!(!solver
1940 .assignments
1941 .is_predicate_satisfied(predicate![domain_id == value]));
1942 }
1943
1944 assert!(!solver
1945 .assignments
1946 .is_predicate_satisfied(predicate![domain_id == ub]));
1947 }
1948
1949 #[test]
1950 fn check_can_compute_1uip_with_propagator_initialisation_conflict() {
1951 let mut solver = ConstraintSatisfactionSolver::default();
1952
1953 let x = solver.create_new_integer_variable(1, 1, None);
1954 let y = solver.create_new_integer_variable(2, 2, None);
1955
1956 let constraint_tag = solver.new_constraint_tag();
1957
1958 let propagator = LinearNotEqualPropagatorArgs {
1959 terms: vec![x, y].into(),
1960 rhs: 3,
1961 constraint_tag,
1962 };
1963 let result = solver.add_propagator(propagator);
1964 assert!(result.is_err());
1965 }
1966}