1use std::cmp::max;
4use std::collections::VecDeque;
5use std::fmt::Debug;
6use std::sync::Arc;
7
8#[allow(
9 clippy::disallowed_types,
10 reason = "any rand generator is a valid implementation of Random"
11)]
12use rand::SeedableRng;
13use rand::rngs::SmallRng;
14
15use super::solver_statistics::SolverStatistics;
16use super::termination::TerminationCondition;
17use super::variables::IntegerVariable;
18use super::variables::Literal;
19#[cfg(doc)]
20use crate::Solver;
21use crate::basic_types::CSPSolverExecutionFlag;
22use crate::basic_types::ConstraintOperationError;
23use crate::basic_types::PredicateId;
24use crate::basic_types::Random;
25use crate::basic_types::SolutionReference;
26use crate::basic_types::StoredConflictInfo;
27use crate::basic_types::time::Instant;
28use crate::branching::Brancher;
29use crate::branching::SelectionContext;
30use crate::conflict_resolving::ConflictAnalysisContext;
31use crate::conflict_resolving::ConflictResolver;
32use crate::containers::HashMap;
33use crate::containers::HashSet;
34use crate::declare_inference_label;
35use crate::engine::Assignments;
36use crate::engine::RestartOptions;
37use crate::engine::RestartStrategy;
38use crate::engine::State;
39use crate::engine::predicates::predicate::Predicate;
40use crate::options::LearningOptions;
41use crate::proof::ConstraintTag;
42use crate::proof::FinalizingContext;
43use crate::proof::InferenceCode;
44use crate::proof::ProofLog;
45use crate::proof::RootExplanationContext;
46use crate::proof::explain_root_assignment;
47use crate::proof::finalize_proof;
48use crate::propagation::PropagatorConstructor;
49use crate::propagation::store::PropagatorHandle;
50use crate::propagators::nogoods::NogoodChecker;
51use crate::propagators::nogoods::NogoodPropagator;
52use crate::propagators::nogoods::NogoodPropagatorConstructor;
53use crate::propagators::nogoods::PropagationMode;
54use crate::pumpkin_assert_eq_simple;
55use crate::pumpkin_assert_moderate;
56use crate::pumpkin_assert_ne_moderate;
57use crate::pumpkin_assert_simple;
58use crate::state::CurrentNogood;
59use crate::statistics::StatisticLogger;
60use crate::statistics::statistic_logging::should_log_statistics;
61use crate::variables::DomainId;
62
63#[derive(Debug)]
93pub struct ConstraintSatisfactionSolver {
94 pub(crate) solver_state: CSPSolverState,
97 pub(crate) state: State,
98 pub(crate) nogood_propagator_handle: PropagatorHandle<NogoodPropagator>,
99
100 pub(crate) restart_strategy: RestartStrategy,
104 assumptions: Vec<Predicate>,
106 solver_statistics: SolverStatistics,
108 pub(crate) internal_parameters: SatisfactionSolverOptions,
110 pub(crate) unit_nogood_inference_codes: HashMap<Predicate, InferenceCode>,
112}
113
114impl Default for ConstraintSatisfactionSolver {
115 fn default() -> Self {
116 ConstraintSatisfactionSolver::new(SatisfactionSolverOptions::default())
117 }
118}
119
120#[derive(Debug, Clone)]
128pub enum CoreExtractionResult {
129 ConflictingAssumption(Predicate),
132 Core(Vec<Predicate>),
135}
136
137#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
144#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
145pub enum ConflictResolverType {
146 NoLearning,
147 #[default]
151 OneUIP,
152 AllDecision,
155 ExtendedCPIP,
159 BoundsExtendedCPIP,
163}
164
165#[derive(Debug)]
167pub struct SatisfactionSolverOptions {
168 pub restart_options: RestartOptions,
170 pub random_generator: SmallRng,
172 pub proof_log: ProofLog,
174 pub learning_options: LearningOptions,
176 pub memory_preallocated: usize,
178 pub analysis_mode: ConflictResolverType,
179}
180
181impl Default for SatisfactionSolverOptions {
182 fn default() -> Self {
183 SatisfactionSolverOptions {
184 restart_options: RestartOptions::default(),
185 random_generator: SmallRng::seed_from_u64(42),
186 proof_log: ProofLog::default(),
187 learning_options: LearningOptions::default(),
188 memory_preallocated: 50,
189 analysis_mode: ConflictResolverType::default(),
190 }
191 }
192}
193
194impl ConstraintSatisfactionSolver {
195 pub(crate) fn assignments(&self) -> &Assignments {
196 &self.state.assignments
197 }
198
199 pub fn get_solution_reference(&self) -> SolutionReference<'_> {
201 self.state.get_solution_reference()
202 }
203
204 pub fn conclude_proof_unsat(&mut self) -> std::io::Result<()> {
208 let proof = std::mem::take(&mut self.internal_parameters.proof_log);
209 proof.unsat(self.state.variable_names())
210 }
211
212 pub fn conclude_proof_optimal(&mut self, bound: Predicate) -> std::io::Result<()> {
216 let proof = std::mem::take(&mut self.internal_parameters.proof_log);
217 proof.optimal(bound, self.state.variable_names())
218 }
219
220 fn complete_proof(&mut self) {
221 if !self.internal_parameters.proof_log.is_logging_proof() {
222 return;
223 }
224
225 #[derive(Debug)]
226 struct DummyBrancher;
227
228 impl Brancher for DummyBrancher {
229 fn next_decision(&mut self, _: &mut SelectionContext) -> Option<Predicate> {
230 unreachable!()
231 }
232
233 fn subscribe_to_events(&self) -> Vec<crate::branching::BrancherEvent> {
234 unreachable!()
235 }
236 }
237
238 let mut conflict_analysis_context = ConflictAnalysisContext {
239 solver_state: &mut self.solver_state,
240 brancher: &mut DummyBrancher,
241 proof_log: &mut self.internal_parameters.proof_log,
242 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
243 restart_strategy: &mut self.restart_strategy,
244
245 state: &mut self.state,
246 nogood_propagator_handle: self.nogood_propagator_handle,
247
248 rng: &mut self.internal_parameters.random_generator,
249 };
250
251 let conflict = conflict_analysis_context.get_conflict_nogood();
252
253 let context = FinalizingContext {
254 conflict: conflict.into(),
255 proof_log: &mut self.internal_parameters.proof_log,
256 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
257 state: &mut self.state,
258 };
259
260 finalize_proof(context);
261 }
262
263 pub(crate) fn is_logging_proof(&self) -> bool {
264 self.internal_parameters.proof_log.is_logging_proof()
265 }
266}
267
268impl ConstraintSatisfactionSolver {
270 pub fn new(solver_options: SatisfactionSolverOptions) -> Self {
271 let mut state = State::default();
272 let handle = state.add_propagator(NogoodPropagatorConstructor::new(
273 (solver_options.memory_preallocated * 1_000_000) / size_of::<PredicateId>(),
274 solver_options.learning_options,
275 match solver_options.analysis_mode {
276 ConflictResolverType::OneUIP | ConflictResolverType::AllDecision => {
277 PropagationMode::UnitPropagation
278 }
279 ConflictResolverType::ExtendedCPIP | ConflictResolverType::BoundsExtendedCPIP => {
280 PropagationMode::ExtendedNogoodPropagation
281 }
282 ConflictResolverType::NoLearning => PropagationMode::default(),
283 },
284 solver_options.learning_options.nogood_propagator_priority,
285 ));
286
287 ConstraintSatisfactionSolver {
288 solver_state: CSPSolverState::default(),
289 assumptions: Vec::default(),
290 restart_strategy: RestartStrategy::new(solver_options.restart_options),
291 nogood_propagator_handle: handle,
292 solver_statistics: SolverStatistics::default(),
293 unit_nogood_inference_codes: Default::default(),
294 internal_parameters: solver_options,
295 state,
296 }
297 }
298
299 pub fn solve(
300 &mut self,
301 termination: &mut impl TerminationCondition,
302 brancher: &mut impl Brancher,
303 resolver: &mut impl ConflictResolver,
304 ) -> CSPSolverExecutionFlag {
305 let dummy_assumptions: Vec<Predicate> = vec![];
306 self.solve_under_assumptions(&dummy_assumptions, termination, brancher, resolver)
307 }
308
309 pub fn solve_under_assumptions(
310 &mut self,
311 assumptions: &[Predicate],
312 termination: &mut impl TerminationCondition,
313 brancher: &mut impl Brancher,
314 resolver: &mut impl ConflictResolver,
315 ) -> CSPSolverExecutionFlag {
316 if self.solver_state.is_inconsistent() {
317 return CSPSolverExecutionFlag::Infeasible;
318 }
319
320 let start_time = Instant::now();
321
322 self.initialise(assumptions);
323 let result = self.solve_internal(termination, brancher, resolver);
324
325 self.solver_statistics
326 .engine_statistics
327 .time_spent_in_solver += start_time.elapsed();
328
329 result
330 }
331
332 pub fn get_state(&self) -> &CSPSolverState {
333 &self.solver_state
334 }
335
336 pub fn get_random_generator(&mut self) -> &mut impl Random {
337 &mut self.internal_parameters.random_generator
338 }
339
340 pub fn log_statistics(&self, verbose: bool) {
341 if !should_log_statistics() {
344 return;
345 }
346
347 self.solver_statistics
348 .log(StatisticLogger::default(), verbose);
349 self.state.log_statistics(verbose);
350 }
351
352 pub fn new_constraint_tag(&mut self) -> ConstraintTag {
354 self.state.new_constraint_tag()
355 }
356
357 pub fn create_new_literal(&mut self, name: Option<Arc<str>>) -> Literal {
358 self.state.new_literal(name)
359 }
360
361 pub fn create_new_literal_for_predicate(
362 &mut self,
363 predicate: Predicate,
364 name: Option<Arc<str>>,
365 constraint_tag: ConstraintTag,
366 ) -> Literal {
367 let literal = self.state.new_literal(name);
368
369 self.internal_parameters
370 .proof_log
371 .reify_predicate(literal, predicate);
372
373 self.add_clause(
375 vec![!literal.get_true_predicate(), predicate],
376 constraint_tag,
377 );
378
379 self.add_clause(
381 vec![!literal.get_false_predicate(), !predicate],
382 constraint_tag,
383 );
384
385 literal
386 }
387
388 pub fn create_new_integer_variable(
390 &mut self,
391 lower_bound: i32,
392 upper_bound: i32,
393 name: Option<Arc<str>>,
394 ) -> DomainId {
395 assert!(
396 !self.solver_state.is_inconsistent(),
397 "Variables cannot be created in an inconsistent state"
398 );
399
400 self.state
401 .new_interval_variable(lower_bound, upper_bound, name)
402 }
403
404 pub fn create_new_integer_variable_sparse(
406 &mut self,
407 values: Vec<i32>,
408 name: Option<String>,
409 ) -> DomainId {
410 self.state.new_sparse_variable(values, name)
411 }
412
413 pub fn extract_clausal_core(&mut self, brancher: &mut impl Brancher) -> CoreExtractionResult {
430 if self.solver_state.is_infeasible() {
431 return CoreExtractionResult::Core(vec![]);
432 }
433
434 self.assumptions
435 .iter()
436 .enumerate()
437 .find(|(index, assumption)| {
438 self.assumptions
439 .iter()
440 .skip(index + 1)
441 .any(|other_assumption| {
442 assumption.is_mutually_exclusive_with(*other_assumption)
443 })
444 })
445 .map(|(_, conflicting_assumption)| {
446 CoreExtractionResult::ConflictingAssumption(*conflicting_assumption)
447 })
448 .unwrap_or_else(|| {
449 let mut context = ConflictAnalysisContext {
450 solver_state: &mut self.solver_state,
451 brancher,
452 proof_log: &mut self.internal_parameters.proof_log,
453 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
454 restart_strategy: &mut self.restart_strategy,
455 state: &mut self.state,
456 nogood_propagator_handle: self.nogood_propagator_handle,
457
458 rng: &mut self.internal_parameters.random_generator,
459 };
460 let mut predicates = context.get_conflict_nogood();
461 let mut core: HashSet<Predicate> = HashSet::default();
462
463 while let Some(predicate) = predicates.pop() {
464 if context.state.assignments.is_decision_predicate(&predicate) {
465 let _ = core.insert(predicate);
466 continue;
467 }
468
469 let _ = ConflictAnalysisContext::get_propagation_reason_inner(
470 predicate,
471 CurrentNogood::empty(),
472 context.proof_log,
473 context.unit_nogood_inference_codes,
474 &mut predicates,
475 context.state,
476 );
477 }
478
479 CoreExtractionResult::Core(core.into_iter().collect())
480 })
481 }
482
483 pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
484 self.state.get_literal_value(literal)
485 }
486
487 pub fn get_lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
489 self.state.lower_bound(variable.clone())
490 }
491
492 pub fn get_upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
494 self.state.upper_bound(variable.clone())
495 }
496
497 pub fn integer_variable_contains(&self, variable: &impl IntegerVariable, value: i32) -> bool {
499 self.state.contains(variable.clone(), value)
500 }
501
502 pub fn get_assigned_integer_value(&self, variable: &impl IntegerVariable) -> Option<i32> {
504 self.state.fixed_value(variable.clone())
505 }
506
507 pub fn restore_state_at_root(&mut self, brancher: &mut impl Brancher) {
508 if self.state.get_checkpoint() != 0 {
509 ConstraintSatisfactionSolver::backtrack(
510 &mut self.state,
511 0,
512 brancher,
513 &mut self.internal_parameters.random_generator,
514 );
515 self.solver_state.declare_ready();
516 } else if self.solver_state.internal_state == CSPSolverStateInternal::ContainsSolution {
517 self.solver_state.declare_ready();
518 }
519 }
520}
521
522impl ConstraintSatisfactionSolver {
524 fn initialise(&mut self, assumptions: &[Predicate]) {
525 pumpkin_assert_simple!(
526 !self.solver_state.is_infeasible_under_assumptions(),
527 "Solver is not expected to be in the infeasible under assumptions state when initialising.
528 Missed extracting the core?"
529 );
530 self.solver_state.declare_solving();
531 assumptions.clone_into(&mut self.assumptions);
532 }
533
534 fn solve_internal(
535 &mut self,
536 termination: &mut impl TerminationCondition,
537 brancher: &mut impl Brancher,
538 resolver: &mut impl ConflictResolver,
539 ) -> CSPSolverExecutionFlag {
540 loop {
541 if termination.should_stop() {
542 self.solver_state.declare_timeout();
543 return CSPSolverExecutionFlag::Timeout;
544 }
545
546 self.propagate();
547
548 if self.solver_state.no_conflict() {
549 if self.get_checkpoint() > self.assumptions.len()
555 && self.restart_strategy.should_restart()
556 {
557 self.restart_during_search(brancher);
558 }
559
560 let branching_result = self.make_next_decision(brancher);
561
562 self.solver_statistics.engine_statistics.peak_depth = max(
563 self.solver_statistics.engine_statistics.peak_depth,
564 self.state.get_checkpoint() as u64,
565 );
566
567 match branching_result {
568 Err(CSPSolverExecutionFlag::Infeasible) => {
569 pumpkin_assert_simple!(self.solver_state.is_infeasible_under_assumptions());
574
575 self.complete_proof();
576 return CSPSolverExecutionFlag::Infeasible;
577 }
578
579 Err(flag) => return flag,
580 Ok(()) => {}
581 }
582 } else {
583 if self.get_checkpoint() == 0 {
584 self.complete_proof();
585 self.solver_state.declare_infeasible();
586
587 return CSPSolverExecutionFlag::Infeasible;
588 }
589
590 self.resolve_conflict(brancher, resolver);
591
592 brancher.on_conflict();
593 self.decay_nogood_activities();
594 }
595 }
596 }
597
598 fn decay_nogood_activities(&mut self) {
599 match self.state.get_propagator_mut(self.nogood_propagator_handle) {
600 Some(nogood_propagator) => {
601 nogood_propagator.decay_nogood_activities();
602 }
603 None => panic!("Provided propagator should be the nogood propagator"),
604 }
605 }
606
607 fn make_next_decision(
608 &mut self,
609 brancher: &mut impl Brancher,
610 ) -> Result<(), CSPSolverExecutionFlag> {
611 if let Some(assumption_literal) = self.peek_next_assumption_predicate() {
615 self.new_checkpoint();
616
617 let _ = self.state.post(assumption_literal).map_err(|_| {
618 self.solver_state
619 .declare_infeasible_under_assumptions(assumption_literal);
620 CSPSolverExecutionFlag::Infeasible
621 })?;
622
623 return Ok(());
624 }
625
626 let context = &mut SelectionContext::new(
628 &self.state.assignments,
629 &mut self.internal_parameters.random_generator,
630 );
631
632 let Some(decision_predicate) = brancher.next_decision(context) else {
634 self.solver_state.declare_solution_found();
638 return Err(CSPSolverExecutionFlag::Feasible);
639 };
640
641 self.new_checkpoint();
642
643 pumpkin_assert_ne_moderate!(
647 self.state.truth_value(decision_predicate),
648 Some(true),
649 "Decision should not already be assigned; double check the brancher"
650 );
651
652 self.solver_statistics.engine_statistics.num_decisions += 1;
653 let update_occurred = self
654 .state
655 .post(decision_predicate)
656 .expect("Decisions are expected not to fail.");
657 pumpkin_assert_simple!(update_occurred);
658
659 Ok(())
660 }
661
662 pub(crate) fn new_checkpoint(&mut self) {
663 self.state.new_checkpoint();
664 }
665
666 fn resolve_conflict(
677 &mut self,
678 brancher: &mut impl Brancher,
679 resolver: &mut impl ConflictResolver,
680 ) {
681 pumpkin_assert_moderate!(self.solver_state.is_conflicting());
682
683 let mut conflict_analysis_context = ConflictAnalysisContext {
684 solver_state: &mut self.solver_state,
685 brancher,
686 proof_log: &mut self.internal_parameters.proof_log,
687 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
688 restart_strategy: &mut self.restart_strategy,
689 state: &mut self.state,
690 nogood_propagator_handle: self.nogood_propagator_handle,
691 rng: &mut self.internal_parameters.random_generator,
692 };
693
694 resolver.resolve_conflict(&mut conflict_analysis_context);
695
696 self.solver_state.declare_solving();
697 }
698
699 fn restart_during_search(&mut self, brancher: &mut impl Brancher) {
709 pumpkin_assert_simple!(
710 self.get_checkpoint() > self.assumptions.len(),
711 "Sanity check: restarts should not trigger whilst assigning assumptions"
712 );
713
714 if self.get_checkpoint() <= self.assumptions.len() {
716 return;
717 }
718
719 if brancher.is_restart_pointless() {
720 return;
723 }
724
725 self.solver_statistics.engine_statistics.num_restarts += 1;
726
727 ConstraintSatisfactionSolver::backtrack(
728 &mut self.state,
729 0,
730 brancher,
731 &mut self.internal_parameters.random_generator,
732 );
733
734 self.restart_strategy.notify_restart();
735 }
736
737 #[allow(
738 clippy::too_many_arguments,
739 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"
740 )]
741 pub(crate) fn backtrack<BrancherType: Brancher + ?Sized>(
742 state: &mut State,
743 backtrack_level: usize,
744 brancher: &mut BrancherType,
745 rng: &mut dyn Random,
746 ) {
747 pumpkin_assert_simple!(backtrack_level < state.get_checkpoint());
748
749 brancher.on_backtrack();
750
751 state
752 .restore_to(backtrack_level)
753 .into_iter()
754 .for_each(|(domain_id, previous_value)| {
755 brancher.on_unassign_integer(domain_id, previous_value)
756 });
757
758 brancher.synchronise(&mut SelectionContext::new(&state.assignments, rng));
759 }
760
761 pub(crate) fn propagate(&mut self) {
763 let num_trail_entries_prev = self.state.trail_len();
764
765 let result = self.state.propagate_to_fixed_point();
766
767 if self.state.get_checkpoint() == 0 {
768 self.handle_root_propagation(num_trail_entries_prev);
769 }
770
771 if let Err(conflict) = result {
772 self.solver_state.declare_conflict(conflict.into());
773 }
774 }
775
776 fn handle_root_propagation(&mut self, start_trail_index: usize) {
783 pumpkin_assert_eq_simple!(self.get_checkpoint(), 0);
784
785 for trail_idx in start_trail_index..self.state.trail_len() {
786 let entry = self.state.trail_entry(trail_idx);
787
788 let mut reason = vec![];
791 let inference_code = self
792 .state
793 .get_propagation_reason_trail_entry(trail_idx, &mut reason);
794
795 if !self.internal_parameters.proof_log.is_logging_inferences() {
796 let _ = self
800 .unit_nogood_inference_codes
801 .insert(entry.predicate, inference_code);
802 continue;
803 }
804
805 let propagated = entry.predicate;
806
807 let inference_premises = reason.iter().copied().chain(std::iter::once(!propagated));
809 let _ = self.internal_parameters.proof_log.log_inference(
810 &mut self.state.constraint_tags,
811 inference_code,
812 inference_premises,
813 None,
814 &self.state.variable_names,
815 &self.state.assignments,
816 );
817
818 let mut to_explain: VecDeque<Predicate> = reason.iter().copied().collect();
829
830 while let Some(premise) = to_explain.pop_front() {
831 pumpkin_assert_simple!(
832 self.state
833 .truth_value(premise)
834 .expect("Expected predicate to hold")
835 );
836
837 let mut context = RootExplanationContext {
838 proof_log: &mut self.internal_parameters.proof_log,
839 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
840 state: &mut self.state,
841 };
842
843 explain_root_assignment(&mut context, premise);
844 }
845
846 let constraint_tag = self.internal_parameters.proof_log.log_deduction(
848 [!propagated],
849 &self.state.variable_names,
850 &mut self.state.constraint_tags,
851 &self.state.assignments,
852 );
853
854 if let Ok(constraint_tag) = constraint_tag {
855 let inference_code = InferenceCode::new(constraint_tag, NogoodLabel);
856
857 let _ = self
858 .unit_nogood_inference_codes
859 .insert(propagated, inference_code);
860 }
861 }
862 }
863
864 fn peek_next_assumption_predicate(&self) -> Option<Predicate> {
865 let next_assumption_index = self.get_checkpoint();
868 self.assumptions.get(next_assumption_index).copied()
869 }
870}
871
872impl ConstraintSatisfactionSolver {
874 pub(crate) fn add_propagator<Constructor>(
876 &mut self,
877 constructor: Constructor,
878 ) -> PropagatorHandle<Constructor::PropagatorImpl>
879 where
880 Constructor: PropagatorConstructor,
881 Constructor::PropagatorImpl: 'static,
882 {
883 self.state.add_propagator(constructor)
884 }
885
886 pub fn post_predicate(&mut self, predicate: Predicate) -> Result<(), ConstraintOperationError> {
887 assert!(
888 self.get_checkpoint() == 0,
889 "Can only post predicates at the root level."
890 );
891
892 if self.solver_state.is_infeasible() {
893 Err(ConstraintOperationError::InfeasibleState)
894 } else {
895 match self.state.post(predicate) {
896 Ok(_) => Ok(()),
897 Err(_) => Err(ConstraintOperationError::InfeasibleNogood),
898 }
899 }
900 }
901
902 fn add_nogood(&mut self, nogood: Vec<Predicate>, constraint_tag: ConstraintTag) {
903 pumpkin_assert_eq_simple!(self.get_checkpoint(), 0);
904
905 let inference_code = self.state.add_inference_checker(
906 constraint_tag,
907 NogoodLabel,
908 NogoodChecker {
909 nogood: nogood.clone().into(),
910 },
911 );
912
913 let (nogood_propagator, mut context) = self
914 .state
915 .get_propagator_mut_with_context(self.nogood_propagator_handle);
916
917 let nogood_propagator =
918 nogood_propagator.expect("Nogood propagator handle should refer to nogood propagator");
919
920 nogood_propagator.add_nogood(nogood, inference_code, &mut context);
921
922 #[allow(deprecated, reason = "Will be refactored")]
923 self.state.enqueue_propagator(self.nogood_propagator_handle);
924 }
925
926 pub fn add_clause(
932 &mut self,
933 predicates: impl IntoIterator<Item = Predicate>,
934 constraint_tag: ConstraintTag,
935 ) {
936 pumpkin_assert_simple!(
937 self.get_checkpoint() == 0,
938 "Clauses can only be added in the root"
939 );
940
941 let mut are_all_falsified_at_root = true;
946 let predicates = predicates
947 .into_iter()
948 .map(|predicate| {
949 are_all_falsified_at_root &= self.state.truth_value(predicate) == Some(false);
950 !predicate
951 })
952 .collect::<Vec<_>>();
953
954 if predicates.is_empty() {
955 self.solver_state.declare_infeasible();
958 return;
959 }
960
961 if are_all_falsified_at_root {
962 let _ = self
965 .internal_parameters
966 .proof_log
967 .log_inference(
968 &mut self.state.constraint_tags,
969 InferenceCode::new(constraint_tag, NogoodLabel),
970 predicates.iter().copied(),
971 None,
972 &self.state.variable_names,
973 &self.state.assignments,
974 )
975 .expect("failed to write to proof");
976
977 finalize_proof(FinalizingContext {
978 conflict: predicates.into(),
979 proof_log: &mut self.internal_parameters.proof_log,
980 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
981 state: &mut self.state,
982 });
983 self.solver_state.declare_infeasible();
984 return;
985 }
986 self.add_nogood(predicates, constraint_tag)
987 }
988
989 pub(crate) fn get_checkpoint(&self) -> usize {
990 self.state.get_checkpoint()
991 }
992}
993
994#[derive(Default, Debug, PartialEq, Eq)]
995enum CSPSolverStateInternal {
996 #[default]
997 Ready,
998 Solving,
999 ContainsSolution,
1000 Conflict {
1001 conflict_info: StoredConflictInfo,
1002 },
1003 Infeasible,
1004 InfeasibleUnderAssumptions {
1005 violated_assumption: Predicate,
1006 },
1007 Timeout,
1008}
1009
1010#[derive(Default, Debug)]
1011pub struct CSPSolverState {
1012 internal_state: CSPSolverStateInternal,
1013}
1014
1015impl CSPSolverState {
1016 pub fn is_ready(&self) -> bool {
1017 matches!(self.internal_state, CSPSolverStateInternal::Ready)
1018 }
1019
1020 pub fn no_conflict(&self) -> bool {
1021 !self.is_conflicting()
1022 }
1023
1024 pub fn is_conflicting(&self) -> bool {
1025 matches!(
1026 self.internal_state,
1027 CSPSolverStateInternal::Conflict { conflict_info: _ }
1028 )
1029 }
1030
1031 pub fn is_infeasible(&self) -> bool {
1032 matches!(self.internal_state, CSPSolverStateInternal::Infeasible)
1033 }
1034
1035 pub fn is_inconsistent(&self) -> bool {
1038 self.is_conflicting() || self.is_infeasible() || self.is_infeasible_under_assumptions()
1039 }
1040
1041 pub fn is_infeasible_under_assumptions(&self) -> bool {
1042 matches!(
1043 self.internal_state,
1044 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1045 violated_assumption: _
1046 }
1047 )
1048 }
1049
1050 pub fn get_violated_assumption(&self) -> Predicate {
1051 if let CSPSolverStateInternal::InfeasibleUnderAssumptions {
1052 violated_assumption,
1053 } = self.internal_state
1054 {
1055 violated_assumption
1056 } else {
1057 panic!(
1058 "Cannot extract violated assumption without getting the solver into the infeasible
1059 under assumptions state."
1060 );
1061 }
1062 }
1063
1064 pub(crate) fn get_conflict_info(&self) -> StoredConflictInfo {
1065 match &self.internal_state {
1066 CSPSolverStateInternal::Conflict { conflict_info } => conflict_info.clone(),
1067 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1068 violated_assumption,
1069 } => StoredConflictInfo::InconsistentAssumptions(*violated_assumption),
1070 _ => {
1071 panic!("Cannot extract conflict clause if solver is not in a conflict.");
1072 }
1073 }
1074 }
1075
1076 pub fn timeout(&self) -> bool {
1077 matches!(self.internal_state, CSPSolverStateInternal::Timeout)
1078 }
1079
1080 pub fn has_solution(&self) -> bool {
1081 matches!(
1082 self.internal_state,
1083 CSPSolverStateInternal::ContainsSolution
1084 )
1085 }
1086
1087 pub(crate) fn declare_ready(&mut self) {
1088 self.internal_state = CSPSolverStateInternal::Ready;
1089 }
1090
1091 pub fn declare_solving(&mut self) {
1092 pumpkin_assert_simple!((self.is_ready() || self.is_conflicting()) && !self.is_infeasible());
1093 self.internal_state = CSPSolverStateInternal::Solving;
1094 }
1095
1096 pub fn declare_infeasible(&mut self) {
1097 self.internal_state = CSPSolverStateInternal::Infeasible;
1098 }
1099
1100 pub(crate) fn declare_conflict(&mut self, conflict_info: StoredConflictInfo) {
1101 self.internal_state = CSPSolverStateInternal::Conflict { conflict_info };
1102 }
1103
1104 pub fn declare_solution_found(&mut self) {
1105 pumpkin_assert_simple!(!self.is_infeasible());
1106 self.internal_state = CSPSolverStateInternal::ContainsSolution;
1107 }
1108
1109 pub fn declare_timeout(&mut self) {
1110 pumpkin_assert_simple!(!self.is_infeasible());
1111 self.internal_state = CSPSolverStateInternal::Timeout;
1112 }
1113
1114 pub fn declare_infeasible_under_assumptions(&mut self, violated_assumption: Predicate) {
1115 pumpkin_assert_simple!(!self.is_infeasible());
1116 self.internal_state = CSPSolverStateInternal::InfeasibleUnderAssumptions {
1117 violated_assumption,
1118 }
1119 }
1120}
1121
1122declare_inference_label!(pub(crate) NogoodLabel, "nogood");
1123
1124#[cfg(test)]
1125mod tests {
1126
1127 #[derive(Debug, Clone, Copy)]
1128 struct NoLearningResolver;
1129
1130 impl ConflictResolver for NoLearningResolver {
1131 fn resolve_conflict(&mut self, context: &mut ConflictAnalysisContext) {
1132 let last_decision = context
1133 .find_last_decision()
1134 .expect("the solver is not at decision level 0, so there exists a last decision");
1135
1136 let current_checkpoint = context.get_checkpoint();
1137 context.restore_to(current_checkpoint - 1);
1138
1139 let update_occurred = context
1140 .post(!last_decision)
1141 .expect("Expected enqueued predicate to not lead to conflict directly");
1142
1143 pumpkin_assert_simple!(
1144 update_occurred,
1145 "The propagated predicate should not already be true."
1146 );
1147 }
1148 }
1149 use super::ConstraintSatisfactionSolver;
1150 use super::CoreExtractionResult;
1151 use crate::DefaultBrancher;
1152 use crate::basic_types::CSPSolverExecutionFlag;
1153 use crate::conflict_resolving::ConflictAnalysisContext;
1154 use crate::conflict_resolving::ConflictResolver;
1155 use crate::predicate;
1156 use crate::predicates::Predicate;
1157 use crate::propagation::ReadDomains;
1158 use crate::pumpkin_assert_simple;
1159 use crate::termination::Indefinite;
1160
1161 fn is_same_core(core1: &[Predicate], core2: &[Predicate]) -> bool {
1162 core1.len() == core2.len() && core2.iter().all(|lit| core1.contains(lit))
1163 }
1164
1165 fn is_result_the_same(res1: &CoreExtractionResult, res2: &CoreExtractionResult) -> bool {
1166 match (res1, res2) {
1167 (
1168 CoreExtractionResult::ConflictingAssumption(assumption1),
1169 CoreExtractionResult::ConflictingAssumption(assumption2),
1170 ) => assumption1 == assumption2,
1171 (CoreExtractionResult::Core(core1), CoreExtractionResult::Core(core2)) => {
1172 is_same_core(core1, core2)
1173 }
1174 _ => false,
1175 }
1176 }
1177
1178 fn run_test(
1179 mut solver: ConstraintSatisfactionSolver,
1180 assumptions: Vec<Predicate>,
1181 expected_flag: CSPSolverExecutionFlag,
1182 expected_result: CoreExtractionResult,
1183 ) {
1184 let mut brancher = DefaultBrancher::default_over_all_variables(&solver.state.assignments);
1185 let mut resolver = NoLearningResolver;
1186
1187 let flag = solver.solve_under_assumptions(
1188 &assumptions,
1189 &mut Indefinite,
1190 &mut brancher,
1191 &mut resolver,
1192 );
1193 assert_eq!(flag, expected_flag, "The flags do not match.");
1194
1195 if matches!(flag, CSPSolverExecutionFlag::Infeasible) {
1196 assert!(
1197 is_result_the_same(
1198 &solver.extract_clausal_core(&mut brancher),
1199 &expected_result
1200 ),
1201 "The result is not the same"
1202 );
1203 }
1204 }
1205
1206 fn create_instance1() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1207 let mut solver = ConstraintSatisfactionSolver::default();
1208 let c1 = solver.new_constraint_tag();
1209 let c2 = solver.new_constraint_tag();
1210 let c3 = solver.new_constraint_tag();
1211 let lit1 = solver.create_new_literal(None).get_true_predicate();
1212 let lit2 = solver.create_new_literal(None).get_true_predicate();
1213
1214 solver.add_clause([lit1, lit2], c1);
1215 solver.add_clause([lit1, !lit2], c2);
1216 solver.add_clause([!lit1, lit2], c3);
1217 (solver, vec![lit1, lit2])
1218 }
1219
1220 #[test]
1221 fn core_extraction_unit_core() {
1222 let mut solver = ConstraintSatisfactionSolver::default();
1223 let constraint_tag = solver.new_constraint_tag();
1224 let lit1 = solver.create_new_literal(None).get_true_predicate();
1225 solver.add_clause(vec![lit1], constraint_tag);
1226
1227 run_test(
1228 solver,
1229 vec![!lit1],
1230 CSPSolverExecutionFlag::Infeasible,
1231 CoreExtractionResult::Core(vec![!lit1]),
1232 )
1233 }
1234
1235 #[test]
1236 fn simple_core_extraction_1_1() {
1237 let (solver, lits) = create_instance1();
1238 run_test(
1239 solver,
1240 vec![!lits[0], !lits[1]],
1241 CSPSolverExecutionFlag::Infeasible,
1242 CoreExtractionResult::Core(vec![!lits[0]]),
1243 )
1244 }
1245
1246 #[test]
1247 fn simple_core_extraction_1_2() {
1248 let (solver, lits) = create_instance1();
1249 run_test(
1250 solver,
1251 vec![!lits[1], !lits[0]],
1252 CSPSolverExecutionFlag::Infeasible,
1253 CoreExtractionResult::Core(vec![!lits[1]]),
1254 );
1255 }
1256
1257 #[test]
1258 fn simple_core_extraction_1_infeasible() {
1259 let (mut solver, lits) = create_instance1();
1260 let constraint_tag = solver.new_constraint_tag();
1261 solver.add_clause([!lits[0], !lits[1]], constraint_tag);
1262 run_test(
1263 solver,
1264 vec![!lits[1], !lits[0]],
1265 CSPSolverExecutionFlag::Infeasible,
1266 CoreExtractionResult::Core(vec![]),
1267 );
1268 }
1269
1270 #[test]
1271 fn simple_core_extraction_1_core_conflicting() {
1272 let (solver, lits) = create_instance1();
1273 run_test(
1274 solver,
1275 vec![!lits[1], lits[1]],
1276 CSPSolverExecutionFlag::Infeasible,
1277 CoreExtractionResult::ConflictingAssumption(!lits[1]),
1278 );
1279 }
1280 fn create_instance2() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1281 let mut solver = ConstraintSatisfactionSolver::default();
1282 let c1 = solver.new_constraint_tag();
1283 let c2 = solver.new_constraint_tag();
1284 let lit1 = solver.create_new_literal(None).get_true_predicate();
1285 let lit2 = solver.create_new_literal(None).get_true_predicate();
1286 let lit3 = solver.create_new_literal(None).get_true_predicate();
1287
1288 solver.add_clause([lit1, lit2, lit3], c1);
1289 solver.add_clause([lit1, !lit2, lit3], c2);
1290 (solver, vec![lit1, lit2, lit3])
1291 }
1292
1293 #[test]
1294 fn simple_core_extraction_2_1() {
1295 let (solver, lits) = create_instance2();
1296 run_test(
1297 solver,
1298 vec![!lits[0], lits[1], !lits[2]],
1299 CSPSolverExecutionFlag::Infeasible,
1300 CoreExtractionResult::Core(vec![!lits[0], lits[1], !lits[2]]),
1301 );
1302 }
1303
1304 #[test]
1305 fn simple_core_extraction_2_long_assumptions_with_inconsistency_at_the_end() {
1306 let (solver, lits) = create_instance2();
1307 run_test(
1308 solver,
1309 vec![!lits[0], lits[1], !lits[2], lits[0]],
1310 CSPSolverExecutionFlag::Infeasible,
1311 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1312 );
1313 }
1314
1315 #[test]
1316 fn simple_core_extraction_2_inconsistent_long_assumptions() {
1317 let (solver, lits) = create_instance2();
1318 run_test(
1319 solver,
1320 vec![!lits[0], !lits[0], !lits[1], !lits[1], lits[0]],
1321 CSPSolverExecutionFlag::Infeasible,
1322 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1323 );
1324 }
1325 fn create_instance3() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1326 let mut solver = ConstraintSatisfactionSolver::default();
1327 let constraint_tag = solver.new_constraint_tag();
1328
1329 let lit1 = solver.create_new_literal(None).get_true_predicate();
1330 let lit2 = solver.create_new_literal(None).get_true_predicate();
1331 let lit3 = solver.create_new_literal(None).get_true_predicate();
1332
1333 solver.add_clause([lit1, lit2, lit3], constraint_tag);
1334 (solver, vec![lit1, lit2, lit3])
1335 }
1336
1337 #[test]
1338 fn simple_core_extraction_3_1() {
1339 let (solver, lits) = create_instance3();
1340 run_test(
1341 solver,
1342 vec![!lits[0], !lits[1], !lits[2]],
1343 CSPSolverExecutionFlag::Infeasible,
1344 CoreExtractionResult::Core(vec![!lits[0], !lits[1], !lits[2]]),
1345 );
1346 }
1347
1348 #[test]
1349 fn simple_core_extraction_3_2() {
1350 let (solver, lits) = create_instance3();
1351 run_test(
1352 solver,
1353 vec![!lits[0], !lits[1]],
1354 CSPSolverExecutionFlag::Feasible,
1355 CoreExtractionResult::Core(vec![]), );
1357 }
1358
1359 #[test]
1389 fn new_domain_with_negative_lower_bound() {
1390 let lb = -2;
1391 let ub = 2;
1392
1393 let mut solver = ConstraintSatisfactionSolver::default();
1394 let domain_id = solver.create_new_integer_variable(lb, ub, None);
1395
1396 assert_eq!(lb, solver.state.assignments.get_lower_bound(domain_id));
1397
1398 assert_eq!(ub, solver.state.assignments.get_upper_bound(domain_id));
1399
1400 assert!(
1401 !solver
1402 .state
1403 .assignments
1404 .is_predicate_satisfied(predicate![domain_id == lb])
1405 );
1406
1407 for value in (lb + 1)..ub {
1408 let predicate = predicate![domain_id >= value];
1409
1410 assert!(!solver.state.assignments.is_predicate_satisfied(predicate));
1411
1412 assert!(
1413 !solver
1414 .state
1415 .assignments
1416 .is_predicate_satisfied(predicate![domain_id == value])
1417 );
1418 }
1419
1420 assert!(
1421 !solver
1422 .state
1423 .assignments
1424 .is_predicate_satisfied(predicate![domain_id == ub])
1425 );
1426 }
1427
1428 }