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::pumpkin_assert_eq_simple;
54use crate::pumpkin_assert_moderate;
55use crate::pumpkin_assert_ne_moderate;
56use crate::pumpkin_assert_simple;
57use crate::state::CurrentNogood;
58use crate::statistics::StatisticLogger;
59use crate::statistics::statistic_logging::should_log_statistics;
60use crate::variables::DomainId;
61
62#[derive(Debug)]
92pub struct ConstraintSatisfactionSolver {
93 pub(crate) solver_state: CSPSolverState,
96 pub(crate) state: State,
97 pub(crate) nogood_propagator_handle: PropagatorHandle<NogoodPropagator>,
98
99 pub(crate) restart_strategy: RestartStrategy,
103 assumptions: Vec<Predicate>,
105 solver_statistics: SolverStatistics,
107 pub(crate) internal_parameters: SatisfactionSolverOptions,
109 pub(crate) unit_nogood_inference_codes: HashMap<Predicate, InferenceCode>,
111}
112
113impl Default for ConstraintSatisfactionSolver {
114 fn default() -> Self {
115 ConstraintSatisfactionSolver::new(SatisfactionSolverOptions::default())
116 }
117}
118
119#[derive(Debug, Clone)]
127pub enum CoreExtractionResult {
128 ConflictingAssumption(Predicate),
131 Core(Vec<Predicate>),
134}
135
136#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
143#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
144pub enum ConflictResolverType {
145 NoLearning,
146 #[default]
147 UIP,
148}
149
150#[derive(Debug)]
152pub struct SatisfactionSolverOptions {
153 pub restart_options: RestartOptions,
155 pub should_minimise_nogoods: bool,
157 pub random_generator: SmallRng,
159 pub proof_log: ProofLog,
161 pub learning_options: LearningOptions,
163 pub memory_preallocated: usize,
165}
166
167impl Default for SatisfactionSolverOptions {
168 fn default() -> Self {
169 SatisfactionSolverOptions {
170 restart_options: RestartOptions::default(),
171 should_minimise_nogoods: true,
172 random_generator: SmallRng::seed_from_u64(42),
173 proof_log: ProofLog::default(),
174 learning_options: LearningOptions::default(),
175 memory_preallocated: 50,
176 }
177 }
178}
179
180impl ConstraintSatisfactionSolver {
181 pub(crate) fn assignments(&self) -> &Assignments {
182 &self.state.assignments
183 }
184
185 pub fn get_solution_reference(&self) -> SolutionReference<'_> {
187 self.state.get_solution_reference()
188 }
189
190 pub fn conclude_proof_unsat(&mut self) -> std::io::Result<()> {
194 let proof = std::mem::take(&mut self.internal_parameters.proof_log);
195 proof.unsat(self.state.variable_names())
196 }
197
198 pub fn conclude_proof_optimal(&mut self, bound: Predicate) -> std::io::Result<()> {
202 let proof = std::mem::take(&mut self.internal_parameters.proof_log);
203 proof.optimal(bound, self.state.variable_names())
204 }
205
206 fn complete_proof(&mut self) {
207 struct DummyBrancher;
208
209 impl Brancher for DummyBrancher {
210 fn next_decision(&mut self, _: &mut SelectionContext) -> Option<Predicate> {
211 unreachable!()
212 }
213
214 fn subscribe_to_events(&self) -> Vec<crate::branching::BrancherEvent> {
215 unreachable!()
216 }
217 }
218
219 let mut conflict_analysis_context = ConflictAnalysisContext {
220 solver_state: &mut self.solver_state,
221 brancher: &mut DummyBrancher,
222 proof_log: &mut self.internal_parameters.proof_log,
223 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
224 restart_strategy: &mut self.restart_strategy,
225
226 state: &mut self.state,
227 nogood_propagator_handle: self.nogood_propagator_handle,
228
229 rng: &mut self.internal_parameters.random_generator,
230 };
231
232 let conflict = conflict_analysis_context.get_conflict_nogood();
233
234 let context = FinalizingContext {
235 conflict: conflict.into(),
236 proof_log: &mut self.internal_parameters.proof_log,
237 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
238 state: &mut self.state,
239 };
240
241 finalize_proof(context);
242 }
243
244 pub(crate) fn is_logging_proof(&self) -> bool {
245 self.internal_parameters.proof_log.is_logging_proof()
246 }
247}
248
249impl ConstraintSatisfactionSolver {
251 pub fn new(solver_options: SatisfactionSolverOptions) -> Self {
252 let mut state = State::default();
253 let handle = state.add_propagator(NogoodPropagatorConstructor::new(
254 (solver_options.memory_preallocated * 1_000_000) / size_of::<PredicateId>(),
255 solver_options.learning_options,
256 ));
257
258 ConstraintSatisfactionSolver {
259 solver_state: CSPSolverState::default(),
260 assumptions: Vec::default(),
261 restart_strategy: RestartStrategy::new(solver_options.restart_options),
262 nogood_propagator_handle: handle,
263 solver_statistics: SolverStatistics::default(),
264 unit_nogood_inference_codes: Default::default(),
265 internal_parameters: solver_options,
266 state,
267 }
268 }
269
270 pub fn solve(
271 &mut self,
272 termination: &mut impl TerminationCondition,
273 brancher: &mut impl Brancher,
274 resolver: &mut impl ConflictResolver,
275 ) -> CSPSolverExecutionFlag {
276 let dummy_assumptions: Vec<Predicate> = vec![];
277 self.solve_under_assumptions(&dummy_assumptions, termination, brancher, resolver)
278 }
279
280 pub fn solve_under_assumptions(
281 &mut self,
282 assumptions: &[Predicate],
283 termination: &mut impl TerminationCondition,
284 brancher: &mut impl Brancher,
285 resolver: &mut impl ConflictResolver,
286 ) -> CSPSolverExecutionFlag {
287 if self.solver_state.is_inconsistent() {
288 return CSPSolverExecutionFlag::Infeasible;
289 }
290
291 let start_time = Instant::now();
292
293 self.initialise(assumptions);
294 let result = self.solve_internal(termination, brancher, resolver);
295
296 self.solver_statistics
297 .engine_statistics
298 .time_spent_in_solver += start_time.elapsed();
299
300 result
301 }
302
303 pub fn get_state(&self) -> &CSPSolverState {
304 &self.solver_state
305 }
306
307 pub fn get_random_generator(&mut self) -> &mut impl Random {
308 &mut self.internal_parameters.random_generator
309 }
310
311 pub fn log_statistics(&self, verbose: bool) {
312 if !should_log_statistics() {
315 return;
316 }
317
318 self.solver_statistics
319 .log(StatisticLogger::default(), verbose);
320 self.state.log_statistics(verbose);
321 }
322
323 pub fn new_constraint_tag(&mut self) -> ConstraintTag {
325 self.state.new_constraint_tag()
326 }
327
328 pub fn create_new_literal(&mut self, name: Option<Arc<str>>) -> Literal {
329 self.state.new_literal(name)
330 }
331
332 pub fn create_new_literal_for_predicate(
333 &mut self,
334 predicate: Predicate,
335 name: Option<Arc<str>>,
336 constraint_tag: ConstraintTag,
337 ) -> Literal {
338 let literal = self.state.new_literal(name);
339
340 self.internal_parameters
341 .proof_log
342 .reify_predicate(literal, predicate);
343
344 let _ = self.add_clause(
346 vec![!literal.get_true_predicate(), predicate],
347 constraint_tag,
348 );
349
350 let _ = self.add_clause(
352 vec![!literal.get_false_predicate(), !predicate],
353 constraint_tag,
354 );
355
356 literal
357 }
358
359 pub fn create_new_integer_variable(
361 &mut self,
362 lower_bound: i32,
363 upper_bound: i32,
364 name: Option<Arc<str>>,
365 ) -> DomainId {
366 assert!(
367 !self.solver_state.is_inconsistent(),
368 "Variables cannot be created in an inconsistent state"
369 );
370
371 self.state
372 .new_interval_variable(lower_bound, upper_bound, name)
373 }
374
375 pub fn create_new_integer_variable_sparse(
377 &mut self,
378 values: Vec<i32>,
379 name: Option<String>,
380 ) -> DomainId {
381 self.state.new_sparse_variable(values, name)
382 }
383
384 pub fn extract_clausal_core(&mut self, brancher: &mut impl Brancher) -> CoreExtractionResult {
401 if self.solver_state.is_infeasible() {
402 return CoreExtractionResult::Core(vec![]);
403 }
404
405 self.assumptions
406 .iter()
407 .enumerate()
408 .find(|(index, assumption)| {
409 self.assumptions
410 .iter()
411 .skip(index + 1)
412 .any(|other_assumption| {
413 assumption.is_mutually_exclusive_with(*other_assumption)
414 })
415 })
416 .map(|(_, conflicting_assumption)| {
417 CoreExtractionResult::ConflictingAssumption(*conflicting_assumption)
418 })
419 .unwrap_or_else(|| {
420 let mut context = ConflictAnalysisContext {
421 solver_state: &mut self.solver_state,
422 brancher,
423 proof_log: &mut self.internal_parameters.proof_log,
424 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
425 restart_strategy: &mut self.restart_strategy,
426 state: &mut self.state,
427 nogood_propagator_handle: self.nogood_propagator_handle,
428
429 rng: &mut self.internal_parameters.random_generator,
430 };
431 let mut predicates = context.get_conflict_nogood();
432 let mut core: HashSet<Predicate> = HashSet::default();
433
434 while let Some(predicate) = predicates.pop() {
435 if context.state.assignments.is_decision_predicate(&predicate) {
436 let _ = core.insert(predicate);
437 continue;
438 }
439
440 ConflictAnalysisContext::get_propagation_reason_inner(
441 predicate,
442 CurrentNogood::empty(),
443 context.proof_log,
444 context.unit_nogood_inference_codes,
445 &mut predicates,
446 context.state,
447 );
448 }
449
450 CoreExtractionResult::Core(core.into_iter().collect())
451 })
452 }
453
454 pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
455 self.state.get_literal_value(literal)
456 }
457
458 pub fn get_lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
460 self.state.lower_bound(variable.clone())
461 }
462
463 pub fn get_upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
465 self.state.upper_bound(variable.clone())
466 }
467
468 pub fn integer_variable_contains(&self, variable: &impl IntegerVariable, value: i32) -> bool {
470 self.state.contains(variable.clone(), value)
471 }
472
473 pub fn get_assigned_integer_value(&self, variable: &impl IntegerVariable) -> Option<i32> {
475 self.state.fixed_value(variable.clone())
476 }
477
478 pub fn restore_state_at_root(&mut self, brancher: &mut impl Brancher) {
479 if self.state.get_checkpoint() != 0 {
480 ConstraintSatisfactionSolver::backtrack(
481 &mut self.state,
482 0,
483 brancher,
484 &mut self.internal_parameters.random_generator,
485 );
486 self.solver_state.declare_ready();
487 } else if self.solver_state.internal_state == CSPSolverStateInternal::ContainsSolution {
488 self.solver_state.declare_ready();
489 }
490 }
491}
492
493impl ConstraintSatisfactionSolver {
495 fn initialise(&mut self, assumptions: &[Predicate]) {
496 pumpkin_assert_simple!(
497 !self.solver_state.is_infeasible_under_assumptions(),
498 "Solver is not expected to be in the infeasible under assumptions state when initialising.
499 Missed extracting the core?"
500 );
501 self.solver_state.declare_solving();
502 assumptions.clone_into(&mut self.assumptions);
503 }
504
505 fn solve_internal(
506 &mut self,
507 termination: &mut impl TerminationCondition,
508 brancher: &mut impl Brancher,
509 resolver: &mut impl ConflictResolver,
510 ) -> CSPSolverExecutionFlag {
511 loop {
512 if termination.should_stop() {
513 self.solver_state.declare_timeout();
514 return CSPSolverExecutionFlag::Timeout;
515 }
516
517 self.propagate();
518
519 if self.solver_state.no_conflict() {
520 if self.get_checkpoint() > self.assumptions.len()
526 && self.restart_strategy.should_restart()
527 {
528 self.restart_during_search(brancher);
529 }
530
531 let branching_result = self.make_next_decision(brancher);
532
533 self.solver_statistics.engine_statistics.peak_depth = max(
534 self.solver_statistics.engine_statistics.peak_depth,
535 self.state.get_checkpoint() as u64,
536 );
537
538 match branching_result {
539 Err(CSPSolverExecutionFlag::Infeasible) => {
540 pumpkin_assert_simple!(self.solver_state.is_infeasible_under_assumptions());
545
546 self.complete_proof();
547 return CSPSolverExecutionFlag::Infeasible;
548 }
549
550 Err(flag) => return flag,
551 Ok(()) => {}
552 }
553 } else {
554 if self.get_checkpoint() == 0 {
555 self.complete_proof();
556 self.solver_state.declare_infeasible();
557
558 return CSPSolverExecutionFlag::Infeasible;
559 }
560
561 self.resolve_conflict(brancher, resolver);
562
563 brancher.on_conflict();
564 self.decay_nogood_activities();
565 }
566 }
567 }
568
569 fn decay_nogood_activities(&mut self) {
570 match self.state.get_propagator_mut(self.nogood_propagator_handle) {
571 Some(nogood_propagator) => {
572 nogood_propagator.decay_nogood_activities();
573 }
574 None => panic!("Provided propagator should be the nogood propagator"),
575 }
576 }
577
578 fn make_next_decision(
579 &mut self,
580 brancher: &mut impl Brancher,
581 ) -> Result<(), CSPSolverExecutionFlag> {
582 if let Some(assumption_literal) = self.peek_next_assumption_predicate() {
586 self.new_checkpoint();
587
588 let _ = self.state.post(assumption_literal).map_err(|_| {
589 self.solver_state
590 .declare_infeasible_under_assumptions(assumption_literal);
591 CSPSolverExecutionFlag::Infeasible
592 })?;
593
594 return Ok(());
595 }
596
597 let context = &mut SelectionContext::new(
599 &self.state.assignments,
600 &mut self.internal_parameters.random_generator,
601 );
602
603 let Some(decision_predicate) = brancher.next_decision(context) else {
605 self.solver_state.declare_solution_found();
609 return Err(CSPSolverExecutionFlag::Feasible);
610 };
611
612 self.new_checkpoint();
613
614 pumpkin_assert_ne_moderate!(
618 self.state.truth_value(decision_predicate),
619 Some(true),
620 "Decision should not already be assigned; double check the brancher"
621 );
622
623 self.solver_statistics.engine_statistics.num_decisions += 1;
624 let update_occurred = self
625 .state
626 .post(decision_predicate)
627 .expect("Decisions are expected not to fail.");
628 pumpkin_assert_simple!(update_occurred);
629
630 Ok(())
631 }
632
633 pub(crate) fn new_checkpoint(&mut self) {
634 self.state.new_checkpoint();
635 }
636
637 fn resolve_conflict(
648 &mut self,
649 brancher: &mut impl Brancher,
650 resolver: &mut impl ConflictResolver,
651 ) {
652 pumpkin_assert_moderate!(self.solver_state.is_conflicting());
653
654 let mut conflict_analysis_context = ConflictAnalysisContext {
655 solver_state: &mut self.solver_state,
656 brancher,
657 proof_log: &mut self.internal_parameters.proof_log,
658 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
659 restart_strategy: &mut self.restart_strategy,
660 state: &mut self.state,
661 nogood_propagator_handle: self.nogood_propagator_handle,
662 rng: &mut self.internal_parameters.random_generator,
663 };
664
665 resolver.resolve_conflict(&mut conflict_analysis_context);
666
667 self.solver_state.declare_solving();
668 }
669
670 fn restart_during_search(&mut self, brancher: &mut impl Brancher) {
680 pumpkin_assert_simple!(
681 self.get_checkpoint() > self.assumptions.len(),
682 "Sanity check: restarts should not trigger whilst assigning assumptions"
683 );
684
685 if self.get_checkpoint() <= self.assumptions.len() {
687 return;
688 }
689
690 if brancher.is_restart_pointless() {
691 return;
694 }
695
696 self.solver_statistics.engine_statistics.num_restarts += 1;
697
698 ConstraintSatisfactionSolver::backtrack(
699 &mut self.state,
700 0,
701 brancher,
702 &mut self.internal_parameters.random_generator,
703 );
704
705 self.restart_strategy.notify_restart();
706 }
707
708 #[allow(
709 clippy::too_many_arguments,
710 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"
711 )]
712 pub(crate) fn backtrack<BrancherType: Brancher + ?Sized>(
713 state: &mut State,
714 backtrack_level: usize,
715 brancher: &mut BrancherType,
716 rng: &mut dyn Random,
717 ) {
718 pumpkin_assert_simple!(backtrack_level < state.get_checkpoint());
719
720 brancher.on_backtrack();
721
722 state
723 .restore_to(backtrack_level)
724 .into_iter()
725 .for_each(|(domain_id, previous_value)| {
726 brancher.on_unassign_integer(domain_id, previous_value)
727 });
728
729 brancher.synchronise(&mut SelectionContext::new(&state.assignments, rng));
730 }
731
732 pub(crate) fn propagate(&mut self) {
734 let num_trail_entries_prev = self.state.trail_len();
735
736 let result = self.state.propagate_to_fixed_point();
737
738 if self.state.get_checkpoint() == 0 {
739 self.handle_root_propagation(num_trail_entries_prev);
740 }
741
742 if let Err(conflict) = result {
743 self.solver_state.declare_conflict(conflict.into());
744 }
745 }
746
747 fn handle_root_propagation(&mut self, start_trail_index: usize) {
754 pumpkin_assert_eq_simple!(self.get_checkpoint(), 0);
755
756 for trail_idx in start_trail_index..self.state.trail_len() {
757 let entry = self.state.trail_entry(trail_idx);
758 let (_, inference_code) = entry
759 .reason
760 .expect("Added by a propagator and must therefore have a reason");
761
762 if !self.internal_parameters.proof_log.is_logging_inferences() {
763 let _ = self
767 .unit_nogood_inference_codes
768 .insert(entry.predicate, inference_code);
769 continue;
770 }
771
772 let mut reason = vec![];
774 self.state
775 .get_propagation_reason_trail_entry(trail_idx, &mut reason);
776
777 let propagated = entry.predicate;
778
779 let inference_premises = reason.iter().copied().chain(std::iter::once(!propagated));
781 let _ = self.internal_parameters.proof_log.log_inference(
782 &mut self.state.constraint_tags,
783 inference_code,
784 inference_premises,
785 None,
786 &self.state.variable_names,
787 &self.state.assignments,
788 );
789
790 let mut to_explain: VecDeque<Predicate> = reason.iter().copied().collect();
801
802 while let Some(premise) = to_explain.pop_front() {
803 pumpkin_assert_simple!(
804 self.state
805 .truth_value(premise)
806 .expect("Expected predicate to hold")
807 );
808
809 let mut context = RootExplanationContext {
810 proof_log: &mut self.internal_parameters.proof_log,
811 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
812 state: &mut self.state,
813 };
814
815 explain_root_assignment(&mut context, premise);
816 }
817
818 let constraint_tag = self.internal_parameters.proof_log.log_deduction(
820 [!propagated],
821 &self.state.variable_names,
822 &mut self.state.constraint_tags,
823 &self.state.assignments,
824 );
825
826 if let Ok(constraint_tag) = constraint_tag {
827 let inference_code = InferenceCode::new(constraint_tag, NogoodLabel);
828
829 let _ = self
830 .unit_nogood_inference_codes
831 .insert(propagated, inference_code);
832 }
833 }
834 }
835
836 fn peek_next_assumption_predicate(&self) -> Option<Predicate> {
837 let next_assumption_index = self.get_checkpoint();
840 self.assumptions.get(next_assumption_index).copied()
841 }
842}
843
844impl ConstraintSatisfactionSolver {
846 pub(crate) fn add_propagator<Constructor>(
848 &mut self,
849 constructor: Constructor,
850 ) -> Result<PropagatorHandle<Constructor::PropagatorImpl>, ConstraintOperationError>
851 where
852 Constructor: PropagatorConstructor,
853 Constructor::PropagatorImpl: 'static,
854 {
855 if self.solver_state.is_inconsistent() {
856 return Err(ConstraintOperationError::InfeasiblePropagator);
857 }
858
859 let handle = self.state.add_propagator(constructor);
860 let result = self.state.propagate_to_fixed_point();
861
862 if let Err(conflict) = result {
863 self.solver_state.declare_conflict(conflict.into());
864 }
865
866 if self.solver_state.no_conflict() {
867 Ok(handle)
868 } else {
869 self.complete_proof();
870 let _ = self.conclude_proof_unsat();
871 Err(ConstraintOperationError::InfeasiblePropagator)
872 }
873 }
874
875 pub fn post_predicate(&mut self, predicate: Predicate) -> Result<(), ConstraintOperationError> {
876 assert!(
877 self.get_checkpoint() == 0,
878 "Can only post predicates at the root level."
879 );
880
881 if self.solver_state.is_infeasible() {
882 Err(ConstraintOperationError::InfeasibleState)
883 } else {
884 match self.state.post(predicate) {
885 Ok(_) => Ok(()),
886 Err(_) => Err(ConstraintOperationError::InfeasibleNogood),
887 }
888 }
889 }
890
891 fn add_nogood(
892 &mut self,
893 nogood: Vec<Predicate>,
894 inference_code: InferenceCode,
895 ) -> Result<(), ConstraintOperationError> {
896 pumpkin_assert_eq_simple!(self.get_checkpoint(), 0);
897 let num_trail_entries = self.state.trail_len();
898
899 self.state.add_inference_checker(
900 inference_code.clone(),
901 Box::new(NogoodChecker {
902 nogood: nogood.clone().into(),
903 }),
904 );
905
906 let (nogood_propagator, mut context) = self
907 .state
908 .get_propagator_mut_with_context(self.nogood_propagator_handle);
909
910 let nogood_propagator =
911 nogood_propagator.expect("Nogood propagator handle should refer to nogood propagator");
912
913 let addition_status = nogood_propagator.add_nogood(nogood, inference_code, &mut context);
914
915 if addition_status.is_err() || self.solver_state.is_conflicting() {
916 if let Err(conflict) = addition_status {
917 self.solver_state.declare_conflict(conflict.into());
918 }
919
920 self.handle_root_propagation(num_trail_entries);
921 self.complete_proof();
922 return Err(ConstraintOperationError::InfeasibleNogood);
923 }
924
925 self.handle_root_propagation(num_trail_entries);
926
927 #[allow(deprecated, reason = "Will be refactored")]
928 self.state.enqueue_propagator(self.nogood_propagator_handle);
929 let result = self.state.propagate_to_fixed_point();
930 if let Err(conflict) = result {
931 self.solver_state.declare_conflict(conflict.into());
932 }
933
934 self.handle_root_propagation(num_trail_entries);
935
936 if self.solver_state.is_infeasible() {
937 self.complete_proof();
938 Err(ConstraintOperationError::InfeasibleState)
939 } else {
940 Ok(())
941 }
942 }
943
944 pub fn add_clause(
950 &mut self,
951 predicates: impl IntoIterator<Item = Predicate>,
952 constraint_tag: ConstraintTag,
953 ) -> Result<(), ConstraintOperationError> {
954 pumpkin_assert_simple!(
955 self.get_checkpoint() == 0,
956 "Clauses can only be added in the root"
957 );
958
959 if self.solver_state.is_inconsistent() {
960 return Err(ConstraintOperationError::InfeasiblePropagator);
961 }
962
963 let mut are_all_falsified_at_root = true;
968 let predicates = predicates
969 .into_iter()
970 .map(|predicate| {
971 are_all_falsified_at_root &= self.state.truth_value(predicate) == Some(false);
972 !predicate
973 })
974 .collect::<Vec<_>>();
975
976 if predicates.is_empty() {
977 self.solver_state
980 .declare_conflict(StoredConflictInfo::RootLevelConflict(
981 ConstraintOperationError::InfeasibleClause,
982 ));
983 return Err(ConstraintOperationError::InfeasibleClause);
984 }
985
986 if are_all_falsified_at_root {
987 finalize_proof(FinalizingContext {
988 conflict: predicates.into(),
989 proof_log: &mut self.internal_parameters.proof_log,
990 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
991 state: &mut self.state,
992 });
993 self.solver_state
994 .declare_conflict(StoredConflictInfo::RootLevelConflict(
995 ConstraintOperationError::InfeasibleClause,
996 ));
997 return Err(ConstraintOperationError::InfeasibleClause);
998 }
999
1000 let inference_code = InferenceCode::new(constraint_tag, NogoodLabel);
1001 if let Err(constraint_operation_error) = self.add_nogood(predicates, inference_code) {
1002 let _ = self.conclude_proof_unsat();
1003
1004 self.solver_state
1005 .declare_conflict(StoredConflictInfo::RootLevelConflict(
1006 constraint_operation_error,
1007 ));
1008 return Err(constraint_operation_error);
1009 }
1010 Ok(())
1011 }
1012
1013 pub(crate) fn get_checkpoint(&self) -> usize {
1014 self.state.get_checkpoint()
1015 }
1016}
1017
1018#[derive(Default, Debug, PartialEq, Eq)]
1019enum CSPSolverStateInternal {
1020 #[default]
1021 Ready,
1022 Solving,
1023 ContainsSolution,
1024 Conflict {
1025 conflict_info: StoredConflictInfo,
1026 },
1027 Infeasible,
1028 InfeasibleUnderAssumptions {
1029 violated_assumption: Predicate,
1030 },
1031 Timeout,
1032}
1033
1034#[derive(Default, Debug)]
1035pub struct CSPSolverState {
1036 internal_state: CSPSolverStateInternal,
1037}
1038
1039impl CSPSolverState {
1040 pub fn is_ready(&self) -> bool {
1041 matches!(self.internal_state, CSPSolverStateInternal::Ready)
1042 }
1043
1044 pub fn no_conflict(&self) -> bool {
1045 !self.is_conflicting()
1046 }
1047
1048 pub fn is_conflicting(&self) -> bool {
1049 matches!(
1050 self.internal_state,
1051 CSPSolverStateInternal::Conflict { conflict_info: _ }
1052 )
1053 }
1054
1055 pub fn is_infeasible(&self) -> bool {
1056 matches!(self.internal_state, CSPSolverStateInternal::Infeasible)
1057 }
1058
1059 pub fn is_inconsistent(&self) -> bool {
1062 self.is_conflicting() || self.is_infeasible() || self.is_infeasible_under_assumptions()
1063 }
1064
1065 pub fn is_infeasible_under_assumptions(&self) -> bool {
1066 matches!(
1067 self.internal_state,
1068 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1069 violated_assumption: _
1070 }
1071 )
1072 }
1073
1074 pub fn get_violated_assumption(&self) -> Predicate {
1075 if let CSPSolverStateInternal::InfeasibleUnderAssumptions {
1076 violated_assumption,
1077 } = self.internal_state
1078 {
1079 violated_assumption
1080 } else {
1081 panic!(
1082 "Cannot extract violated assumption without getting the solver into the infeasible
1083 under assumptions state."
1084 );
1085 }
1086 }
1087
1088 pub(crate) fn get_conflict_info(&self) -> StoredConflictInfo {
1089 match &self.internal_state {
1090 CSPSolverStateInternal::Conflict { conflict_info } => conflict_info.clone(),
1091 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1092 violated_assumption,
1093 } => StoredConflictInfo::InconsistentAssumptions(*violated_assumption),
1094 _ => {
1095 panic!("Cannot extract conflict clause if solver is not in a conflict.");
1096 }
1097 }
1098 }
1099
1100 pub fn timeout(&self) -> bool {
1101 matches!(self.internal_state, CSPSolverStateInternal::Timeout)
1102 }
1103
1104 pub fn has_solution(&self) -> bool {
1105 matches!(
1106 self.internal_state,
1107 CSPSolverStateInternal::ContainsSolution
1108 )
1109 }
1110
1111 pub(crate) fn declare_ready(&mut self) {
1112 self.internal_state = CSPSolverStateInternal::Ready;
1113 }
1114
1115 pub fn declare_solving(&mut self) {
1116 pumpkin_assert_simple!((self.is_ready() || self.is_conflicting()) && !self.is_infeasible());
1117 self.internal_state = CSPSolverStateInternal::Solving;
1118 }
1119
1120 pub fn declare_infeasible(&mut self) {
1121 self.internal_state = CSPSolverStateInternal::Infeasible;
1122 }
1123
1124 pub(crate) fn declare_conflict(&mut self, conflict_info: StoredConflictInfo) {
1125 self.internal_state = CSPSolverStateInternal::Conflict { conflict_info };
1126 }
1127
1128 pub fn declare_solution_found(&mut self) {
1129 pumpkin_assert_simple!(!self.is_infeasible());
1130 self.internal_state = CSPSolverStateInternal::ContainsSolution;
1131 }
1132
1133 pub fn declare_timeout(&mut self) {
1134 pumpkin_assert_simple!(!self.is_infeasible());
1135 self.internal_state = CSPSolverStateInternal::Timeout;
1136 }
1137
1138 pub fn declare_infeasible_under_assumptions(&mut self, violated_assumption: Predicate) {
1139 pumpkin_assert_simple!(!self.is_infeasible());
1140 self.internal_state = CSPSolverStateInternal::InfeasibleUnderAssumptions {
1141 violated_assumption,
1142 }
1143 }
1144}
1145
1146declare_inference_label!(pub(crate) NogoodLabel, "nogood");
1147
1148#[cfg(test)]
1149mod tests {
1150
1151 #[derive(Debug, Clone, Copy)]
1152 struct NoLearningResolver;
1153
1154 impl ConflictResolver for NoLearningResolver {
1155 fn resolve_conflict(&mut self, context: &mut ConflictAnalysisContext) {
1156 let last_decision = context
1157 .find_last_decision()
1158 .expect("the solver is not at decision level 0, so there exists a last decision");
1159
1160 let current_checkpoint = context.get_checkpoint();
1161 context.restore_to(current_checkpoint - 1);
1162
1163 let update_occurred = context
1164 .post(!last_decision)
1165 .expect("Expected enqueued predicate to not lead to conflict directly");
1166
1167 pumpkin_assert_simple!(
1168 update_occurred,
1169 "The propagated predicate should not already be true."
1170 );
1171 }
1172 }
1173 use super::ConstraintSatisfactionSolver;
1174 use super::CoreExtractionResult;
1175 use crate::DefaultBrancher;
1176 use crate::basic_types::CSPSolverExecutionFlag;
1177 use crate::conflict_resolving::ConflictAnalysisContext;
1178 use crate::conflict_resolving::ConflictResolver;
1179 use crate::predicate;
1180 use crate::predicates::Predicate;
1181 use crate::propagation::ReadDomains;
1182 use crate::pumpkin_assert_simple;
1183 use crate::termination::Indefinite;
1184
1185 fn is_same_core(core1: &[Predicate], core2: &[Predicate]) -> bool {
1186 core1.len() == core2.len() && core2.iter().all(|lit| core1.contains(lit))
1187 }
1188
1189 fn is_result_the_same(res1: &CoreExtractionResult, res2: &CoreExtractionResult) -> bool {
1190 match (res1, res2) {
1191 (
1192 CoreExtractionResult::ConflictingAssumption(assumption1),
1193 CoreExtractionResult::ConflictingAssumption(assumption2),
1194 ) => assumption1 == assumption2,
1195 (CoreExtractionResult::Core(core1), CoreExtractionResult::Core(core2)) => {
1196 is_same_core(core1, core2)
1197 }
1198 _ => false,
1199 }
1200 }
1201
1202 fn run_test(
1203 mut solver: ConstraintSatisfactionSolver,
1204 assumptions: Vec<Predicate>,
1205 expected_flag: CSPSolverExecutionFlag,
1206 expected_result: CoreExtractionResult,
1207 ) {
1208 let mut brancher = DefaultBrancher::default_over_all_variables(&solver.state.assignments);
1209 let mut resolver = NoLearningResolver;
1210
1211 let flag = solver.solve_under_assumptions(
1212 &assumptions,
1213 &mut Indefinite,
1214 &mut brancher,
1215 &mut resolver,
1216 );
1217 assert_eq!(flag, expected_flag, "The flags do not match.");
1218
1219 if matches!(flag, CSPSolverExecutionFlag::Infeasible) {
1220 assert!(
1221 is_result_the_same(
1222 &solver.extract_clausal_core(&mut brancher),
1223 &expected_result
1224 ),
1225 "The result is not the same"
1226 );
1227 }
1228 }
1229
1230 fn create_instance1() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1231 let mut solver = ConstraintSatisfactionSolver::default();
1232 let c1 = solver.new_constraint_tag();
1233 let c2 = solver.new_constraint_tag();
1234 let c3 = solver.new_constraint_tag();
1235 let lit1 = solver.create_new_literal(None).get_true_predicate();
1236 let lit2 = solver.create_new_literal(None).get_true_predicate();
1237
1238 let _ = solver.add_clause([lit1, lit2], c1);
1239 let _ = solver.add_clause([lit1, !lit2], c2);
1240 let _ = solver.add_clause([!lit1, lit2], c3);
1241 (solver, vec![lit1, lit2])
1242 }
1243
1244 #[test]
1245 fn core_extraction_unit_core() {
1246 let mut solver = ConstraintSatisfactionSolver::default();
1247 let constraint_tag = solver.new_constraint_tag();
1248 let lit1 = solver.create_new_literal(None).get_true_predicate();
1249 let _ = solver.add_clause(vec![lit1], constraint_tag);
1250
1251 run_test(
1252 solver,
1253 vec![!lit1],
1254 CSPSolverExecutionFlag::Infeasible,
1255 CoreExtractionResult::Core(vec![!lit1]),
1256 )
1257 }
1258
1259 #[test]
1260 fn simple_core_extraction_1_1() {
1261 let (solver, lits) = create_instance1();
1262 run_test(
1263 solver,
1264 vec![!lits[0], !lits[1]],
1265 CSPSolverExecutionFlag::Infeasible,
1266 CoreExtractionResult::Core(vec![!lits[0]]),
1267 )
1268 }
1269
1270 #[test]
1271 fn simple_core_extraction_1_2() {
1272 let (solver, lits) = create_instance1();
1273 run_test(
1274 solver,
1275 vec![!lits[1], !lits[0]],
1276 CSPSolverExecutionFlag::Infeasible,
1277 CoreExtractionResult::Core(vec![!lits[1]]),
1278 );
1279 }
1280
1281 #[test]
1282 fn simple_core_extraction_1_infeasible() {
1283 let (mut solver, lits) = create_instance1();
1284 let constraint_tag = solver.new_constraint_tag();
1285 let _ = solver.add_clause([!lits[0], !lits[1]], constraint_tag);
1286 run_test(
1287 solver,
1288 vec![!lits[1], !lits[0]],
1289 CSPSolverExecutionFlag::Infeasible,
1290 CoreExtractionResult::Core(vec![]),
1291 );
1292 }
1293
1294 #[test]
1295 fn simple_core_extraction_1_core_conflicting() {
1296 let (solver, lits) = create_instance1();
1297 run_test(
1298 solver,
1299 vec![!lits[1], lits[1]],
1300 CSPSolverExecutionFlag::Infeasible,
1301 CoreExtractionResult::ConflictingAssumption(!lits[1]),
1302 );
1303 }
1304 fn create_instance2() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1305 let mut solver = ConstraintSatisfactionSolver::default();
1306 let c1 = solver.new_constraint_tag();
1307 let c2 = solver.new_constraint_tag();
1308 let lit1 = solver.create_new_literal(None).get_true_predicate();
1309 let lit2 = solver.create_new_literal(None).get_true_predicate();
1310 let lit3 = solver.create_new_literal(None).get_true_predicate();
1311
1312 let _ = solver.add_clause([lit1, lit2, lit3], c1);
1313 let _ = solver.add_clause([lit1, !lit2, lit3], c2);
1314 (solver, vec![lit1, lit2, lit3])
1315 }
1316
1317 #[test]
1318 fn simple_core_extraction_2_1() {
1319 let (solver, lits) = create_instance2();
1320 run_test(
1321 solver,
1322 vec![!lits[0], lits[1], !lits[2]],
1323 CSPSolverExecutionFlag::Infeasible,
1324 CoreExtractionResult::Core(vec![!lits[0], lits[1], !lits[2]]),
1325 );
1326 }
1327
1328 #[test]
1329 fn simple_core_extraction_2_long_assumptions_with_inconsistency_at_the_end() {
1330 let (solver, lits) = create_instance2();
1331 run_test(
1332 solver,
1333 vec![!lits[0], lits[1], !lits[2], lits[0]],
1334 CSPSolverExecutionFlag::Infeasible,
1335 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1336 );
1337 }
1338
1339 #[test]
1340 fn simple_core_extraction_2_inconsistent_long_assumptions() {
1341 let (solver, lits) = create_instance2();
1342 run_test(
1343 solver,
1344 vec![!lits[0], !lits[0], !lits[1], !lits[1], lits[0]],
1345 CSPSolverExecutionFlag::Infeasible,
1346 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1347 );
1348 }
1349 fn create_instance3() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1350 let mut solver = ConstraintSatisfactionSolver::default();
1351 let constraint_tag = solver.new_constraint_tag();
1352
1353 let lit1 = solver.create_new_literal(None).get_true_predicate();
1354 let lit2 = solver.create_new_literal(None).get_true_predicate();
1355 let lit3 = solver.create_new_literal(None).get_true_predicate();
1356
1357 let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1358 (solver, vec![lit1, lit2, lit3])
1359 }
1360
1361 #[test]
1362 fn simple_core_extraction_3_1() {
1363 let (solver, lits) = create_instance3();
1364 run_test(
1365 solver,
1366 vec![!lits[0], !lits[1], !lits[2]],
1367 CSPSolverExecutionFlag::Infeasible,
1368 CoreExtractionResult::Core(vec![!lits[0], !lits[1], !lits[2]]),
1369 );
1370 }
1371
1372 #[test]
1373 fn simple_core_extraction_3_2() {
1374 let (solver, lits) = create_instance3();
1375 run_test(
1376 solver,
1377 vec![!lits[0], !lits[1]],
1378 CSPSolverExecutionFlag::Feasible,
1379 CoreExtractionResult::Core(vec![]), );
1381 }
1382
1383 #[test]
1413 fn new_domain_with_negative_lower_bound() {
1414 let lb = -2;
1415 let ub = 2;
1416
1417 let mut solver = ConstraintSatisfactionSolver::default();
1418 let domain_id = solver.create_new_integer_variable(lb, ub, None);
1419
1420 assert_eq!(lb, solver.state.assignments.get_lower_bound(domain_id));
1421
1422 assert_eq!(ub, solver.state.assignments.get_upper_bound(domain_id));
1423
1424 assert!(
1425 !solver
1426 .state
1427 .assignments
1428 .is_predicate_satisfied(predicate![domain_id == lb])
1429 );
1430
1431 for value in (lb + 1)..ub {
1432 let predicate = predicate![domain_id >= value];
1433
1434 assert!(!solver.state.assignments.is_predicate_satisfied(predicate));
1435
1436 assert!(
1437 !solver
1438 .state
1439 .assignments
1440 .is_predicate_satisfied(predicate![domain_id == value])
1441 );
1442 }
1443
1444 assert!(
1445 !solver
1446 .state
1447 .assignments
1448 .is_predicate_satisfied(predicate![domain_id == ub])
1449 );
1450 }
1451
1452 }