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