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 #[derive(Debug)]
208 struct DummyBrancher;
209
210 impl Brancher for DummyBrancher {
211 fn next_decision(&mut self, _: &mut SelectionContext) -> Option<Predicate> {
212 unreachable!()
213 }
214
215 fn subscribe_to_events(&self) -> Vec<crate::branching::BrancherEvent> {
216 unreachable!()
217 }
218 }
219
220 let mut conflict_analysis_context = ConflictAnalysisContext {
221 solver_state: &mut self.solver_state,
222 brancher: &mut DummyBrancher,
223 proof_log: &mut self.internal_parameters.proof_log,
224 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
225 restart_strategy: &mut self.restart_strategy,
226
227 state: &mut self.state,
228 nogood_propagator_handle: self.nogood_propagator_handle,
229
230 rng: &mut self.internal_parameters.random_generator,
231 };
232
233 let conflict = conflict_analysis_context.get_conflict_nogood();
234
235 let context = FinalizingContext {
236 conflict: conflict.into(),
237 proof_log: &mut self.internal_parameters.proof_log,
238 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
239 state: &mut self.state,
240 };
241
242 finalize_proof(context);
243 }
244
245 pub(crate) fn is_logging_proof(&self) -> bool {
246 self.internal_parameters.proof_log.is_logging_proof()
247 }
248}
249
250impl ConstraintSatisfactionSolver {
252 pub fn new(solver_options: SatisfactionSolverOptions) -> Self {
253 let mut state = State::default();
254 let handle = state.add_propagator(NogoodPropagatorConstructor::new(
255 (solver_options.memory_preallocated * 1_000_000) / size_of::<PredicateId>(),
256 solver_options.learning_options,
257 ));
258
259 ConstraintSatisfactionSolver {
260 solver_state: CSPSolverState::default(),
261 assumptions: Vec::default(),
262 restart_strategy: RestartStrategy::new(solver_options.restart_options),
263 nogood_propagator_handle: handle,
264 solver_statistics: SolverStatistics::default(),
265 unit_nogood_inference_codes: Default::default(),
266 internal_parameters: solver_options,
267 state,
268 }
269 }
270
271 pub fn solve(
272 &mut self,
273 termination: &mut impl TerminationCondition,
274 brancher: &mut impl Brancher,
275 resolver: &mut impl ConflictResolver,
276 ) -> CSPSolverExecutionFlag {
277 let dummy_assumptions: Vec<Predicate> = vec![];
278 self.solve_under_assumptions(&dummy_assumptions, termination, brancher, resolver)
279 }
280
281 pub fn solve_under_assumptions(
282 &mut self,
283 assumptions: &[Predicate],
284 termination: &mut impl TerminationCondition,
285 brancher: &mut impl Brancher,
286 resolver: &mut impl ConflictResolver,
287 ) -> CSPSolverExecutionFlag {
288 if self.solver_state.is_inconsistent() {
289 return CSPSolverExecutionFlag::Infeasible;
290 }
291
292 let start_time = Instant::now();
293
294 self.initialise(assumptions);
295 let result = self.solve_internal(termination, brancher, resolver);
296
297 self.solver_statistics
298 .engine_statistics
299 .time_spent_in_solver += start_time.elapsed();
300
301 result
302 }
303
304 pub fn get_state(&self) -> &CSPSolverState {
305 &self.solver_state
306 }
307
308 pub fn get_random_generator(&mut self) -> &mut impl Random {
309 &mut self.internal_parameters.random_generator
310 }
311
312 pub fn log_statistics(&self, verbose: bool) {
313 if !should_log_statistics() {
316 return;
317 }
318
319 self.solver_statistics
320 .log(StatisticLogger::default(), verbose);
321 self.state.log_statistics(verbose);
322 }
323
324 pub fn new_constraint_tag(&mut self) -> ConstraintTag {
326 self.state.new_constraint_tag()
327 }
328
329 pub fn create_new_literal(&mut self, name: Option<Arc<str>>) -> Literal {
330 self.state.new_literal(name)
331 }
332
333 pub fn create_new_literal_for_predicate(
334 &mut self,
335 predicate: Predicate,
336 name: Option<Arc<str>>,
337 constraint_tag: ConstraintTag,
338 ) -> Literal {
339 let literal = self.state.new_literal(name);
340
341 self.internal_parameters
342 .proof_log
343 .reify_predicate(literal, predicate);
344
345 let _ = self.add_clause(
347 vec![!literal.get_true_predicate(), predicate],
348 constraint_tag,
349 );
350
351 let _ = self.add_clause(
353 vec![!literal.get_false_predicate(), !predicate],
354 constraint_tag,
355 );
356
357 literal
358 }
359
360 pub fn create_new_integer_variable(
362 &mut self,
363 lower_bound: i32,
364 upper_bound: i32,
365 name: Option<Arc<str>>,
366 ) -> DomainId {
367 assert!(
368 !self.solver_state.is_inconsistent(),
369 "Variables cannot be created in an inconsistent state"
370 );
371
372 self.state
373 .new_interval_variable(lower_bound, upper_bound, name)
374 }
375
376 pub fn create_new_integer_variable_sparse(
378 &mut self,
379 values: Vec<i32>,
380 name: Option<String>,
381 ) -> DomainId {
382 self.state.new_sparse_variable(values, name)
383 }
384
385 pub fn extract_clausal_core(&mut self, brancher: &mut impl Brancher) -> CoreExtractionResult {
402 if self.solver_state.is_infeasible() {
403 return CoreExtractionResult::Core(vec![]);
404 }
405
406 self.assumptions
407 .iter()
408 .enumerate()
409 .find(|(index, assumption)| {
410 self.assumptions
411 .iter()
412 .skip(index + 1)
413 .any(|other_assumption| {
414 assumption.is_mutually_exclusive_with(*other_assumption)
415 })
416 })
417 .map(|(_, conflicting_assumption)| {
418 CoreExtractionResult::ConflictingAssumption(*conflicting_assumption)
419 })
420 .unwrap_or_else(|| {
421 let mut context = ConflictAnalysisContext {
422 solver_state: &mut self.solver_state,
423 brancher,
424 proof_log: &mut self.internal_parameters.proof_log,
425 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
426 restart_strategy: &mut self.restart_strategy,
427 state: &mut self.state,
428 nogood_propagator_handle: self.nogood_propagator_handle,
429
430 rng: &mut self.internal_parameters.random_generator,
431 };
432 let mut predicates = context.get_conflict_nogood();
433 let mut core: HashSet<Predicate> = HashSet::default();
434
435 while let Some(predicate) = predicates.pop() {
436 if context.state.assignments.is_decision_predicate(&predicate) {
437 let _ = core.insert(predicate);
438 continue;
439 }
440
441 let _ = ConflictAnalysisContext::get_propagation_reason_inner(
442 predicate,
443 CurrentNogood::empty(),
444 context.proof_log,
445 context.unit_nogood_inference_codes,
446 &mut predicates,
447 context.state,
448 );
449 }
450
451 CoreExtractionResult::Core(core.into_iter().collect())
452 })
453 }
454
455 pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
456 self.state.get_literal_value(literal)
457 }
458
459 pub fn get_lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
461 self.state.lower_bound(variable.clone())
462 }
463
464 pub fn get_upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
466 self.state.upper_bound(variable.clone())
467 }
468
469 pub fn integer_variable_contains(&self, variable: &impl IntegerVariable, value: i32) -> bool {
471 self.state.contains(variable.clone(), value)
472 }
473
474 pub fn get_assigned_integer_value(&self, variable: &impl IntegerVariable) -> Option<i32> {
476 self.state.fixed_value(variable.clone())
477 }
478
479 pub fn restore_state_at_root(&mut self, brancher: &mut impl Brancher) {
480 if self.state.get_checkpoint() != 0 {
481 ConstraintSatisfactionSolver::backtrack(
482 &mut self.state,
483 0,
484 brancher,
485 &mut self.internal_parameters.random_generator,
486 );
487 self.solver_state.declare_ready();
488 } else if self.solver_state.internal_state == CSPSolverStateInternal::ContainsSolution {
489 self.solver_state.declare_ready();
490 }
491 }
492}
493
494impl ConstraintSatisfactionSolver {
496 fn initialise(&mut self, assumptions: &[Predicate]) {
497 pumpkin_assert_simple!(
498 !self.solver_state.is_infeasible_under_assumptions(),
499 "Solver is not expected to be in the infeasible under assumptions state when initialising.
500 Missed extracting the core?"
501 );
502 self.solver_state.declare_solving();
503 assumptions.clone_into(&mut self.assumptions);
504 }
505
506 fn solve_internal(
507 &mut self,
508 termination: &mut impl TerminationCondition,
509 brancher: &mut impl Brancher,
510 resolver: &mut impl ConflictResolver,
511 ) -> CSPSolverExecutionFlag {
512 loop {
513 if termination.should_stop() {
514 self.solver_state.declare_timeout();
515 return CSPSolverExecutionFlag::Timeout;
516 }
517
518 self.propagate();
519
520 if self.solver_state.no_conflict() {
521 if self.get_checkpoint() > self.assumptions.len()
527 && self.restart_strategy.should_restart()
528 {
529 self.restart_during_search(brancher);
530 }
531
532 let branching_result = self.make_next_decision(brancher);
533
534 self.solver_statistics.engine_statistics.peak_depth = max(
535 self.solver_statistics.engine_statistics.peak_depth,
536 self.state.get_checkpoint() as u64,
537 );
538
539 match branching_result {
540 Err(CSPSolverExecutionFlag::Infeasible) => {
541 pumpkin_assert_simple!(self.solver_state.is_infeasible_under_assumptions());
546
547 self.complete_proof();
548 return CSPSolverExecutionFlag::Infeasible;
549 }
550
551 Err(flag) => return flag,
552 Ok(()) => {}
553 }
554 } else {
555 if self.get_checkpoint() == 0 {
556 self.complete_proof();
557 self.solver_state.declare_infeasible();
558
559 return CSPSolverExecutionFlag::Infeasible;
560 }
561
562 self.resolve_conflict(brancher, resolver);
563
564 brancher.on_conflict();
565 self.decay_nogood_activities();
566 }
567 }
568 }
569
570 fn decay_nogood_activities(&mut self) {
571 match self.state.get_propagator_mut(self.nogood_propagator_handle) {
572 Some(nogood_propagator) => {
573 nogood_propagator.decay_nogood_activities();
574 }
575 None => panic!("Provided propagator should be the nogood propagator"),
576 }
577 }
578
579 fn make_next_decision(
580 &mut self,
581 brancher: &mut impl Brancher,
582 ) -> Result<(), CSPSolverExecutionFlag> {
583 if let Some(assumption_literal) = self.peek_next_assumption_predicate() {
587 self.new_checkpoint();
588
589 let _ = self.state.post(assumption_literal).map_err(|_| {
590 self.solver_state
591 .declare_infeasible_under_assumptions(assumption_literal);
592 CSPSolverExecutionFlag::Infeasible
593 })?;
594
595 return Ok(());
596 }
597
598 let context = &mut SelectionContext::new(
600 &self.state.assignments,
601 &mut self.internal_parameters.random_generator,
602 );
603
604 let Some(decision_predicate) = brancher.next_decision(context) else {
606 self.solver_state.declare_solution_found();
610 return Err(CSPSolverExecutionFlag::Feasible);
611 };
612
613 self.new_checkpoint();
614
615 pumpkin_assert_ne_moderate!(
619 self.state.truth_value(decision_predicate),
620 Some(true),
621 "Decision should not already be assigned; double check the brancher"
622 );
623
624 self.solver_statistics.engine_statistics.num_decisions += 1;
625 let update_occurred = self
626 .state
627 .post(decision_predicate)
628 .expect("Decisions are expected not to fail.");
629 pumpkin_assert_simple!(update_occurred);
630
631 Ok(())
632 }
633
634 pub(crate) fn new_checkpoint(&mut self) {
635 self.state.new_checkpoint();
636 }
637
638 fn resolve_conflict(
649 &mut self,
650 brancher: &mut impl Brancher,
651 resolver: &mut impl ConflictResolver,
652 ) {
653 pumpkin_assert_moderate!(self.solver_state.is_conflicting());
654
655 let mut conflict_analysis_context = ConflictAnalysisContext {
656 solver_state: &mut self.solver_state,
657 brancher,
658 proof_log: &mut self.internal_parameters.proof_log,
659 unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
660 restart_strategy: &mut self.restart_strategy,
661 state: &mut self.state,
662 nogood_propagator_handle: self.nogood_propagator_handle,
663 rng: &mut self.internal_parameters.random_generator,
664 };
665
666 resolver.resolve_conflict(&mut conflict_analysis_context);
667
668 self.solver_state.declare_solving();
669 }
670
671 fn restart_during_search(&mut self, brancher: &mut impl Brancher) {
681 pumpkin_assert_simple!(
682 self.get_checkpoint() > self.assumptions.len(),
683 "Sanity check: restarts should not trigger whilst assigning assumptions"
684 );
685
686 if self.get_checkpoint() <= self.assumptions.len() {
688 return;
689 }
690
691 if brancher.is_restart_pointless() {
692 return;
695 }
696
697 self.solver_statistics.engine_statistics.num_restarts += 1;
698
699 ConstraintSatisfactionSolver::backtrack(
700 &mut self.state,
701 0,
702 brancher,
703 &mut self.internal_parameters.random_generator,
704 );
705
706 self.restart_strategy.notify_restart();
707 }
708
709 #[allow(
710 clippy::too_many_arguments,
711 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"
712 )]
713 pub(crate) fn backtrack<BrancherType: Brancher + ?Sized>(
714 state: &mut State,
715 backtrack_level: usize,
716 brancher: &mut BrancherType,
717 rng: &mut dyn Random,
718 ) {
719 pumpkin_assert_simple!(backtrack_level < state.get_checkpoint());
720
721 brancher.on_backtrack();
722
723 state
724 .restore_to(backtrack_level)
725 .into_iter()
726 .for_each(|(domain_id, previous_value)| {
727 brancher.on_unassign_integer(domain_id, previous_value)
728 });
729
730 brancher.synchronise(&mut SelectionContext::new(&state.assignments, rng));
731 }
732
733 pub(crate) fn propagate(&mut self) {
735 let num_trail_entries_prev = self.state.trail_len();
736
737 let result = self.state.propagate_to_fixed_point();
738
739 if self.state.get_checkpoint() == 0 {
740 self.handle_root_propagation(num_trail_entries_prev);
741 }
742
743 if let Err(conflict) = result {
744 self.solver_state.declare_conflict(conflict.into());
745 }
746 }
747
748 fn handle_root_propagation(&mut self, start_trail_index: usize) {
755 pumpkin_assert_eq_simple!(self.get_checkpoint(), 0);
756
757 for trail_idx in start_trail_index..self.state.trail_len() {
758 let entry = self.state.trail_entry(trail_idx);
759
760 let mut reason = vec![];
763 let inference_code = self
764 .state
765 .get_propagation_reason_trail_entry(trail_idx, &mut reason);
766
767 if !self.internal_parameters.proof_log.is_logging_inferences() {
768 let _ = self
772 .unit_nogood_inference_codes
773 .insert(entry.predicate, inference_code);
774 continue;
775 }
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 let inference_code = InferenceCode::new(constraint_tag, NogoodLabel);
987 if are_all_falsified_at_root {
988 let _ = self
991 .internal_parameters
992 .proof_log
993 .log_inference(
994 &mut self.state.constraint_tags,
995 inference_code,
996 predicates.iter().copied(),
997 None,
998 &self.state.variable_names,
999 &self.state.assignments,
1000 )
1001 .expect("failed to write to proof");
1002
1003 finalize_proof(FinalizingContext {
1004 conflict: predicates.into(),
1005 proof_log: &mut self.internal_parameters.proof_log,
1006 unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
1007 state: &mut self.state,
1008 });
1009 self.solver_state
1010 .declare_conflict(StoredConflictInfo::RootLevelConflict(
1011 ConstraintOperationError::InfeasibleClause,
1012 ));
1013 return Err(ConstraintOperationError::InfeasibleClause);
1014 }
1015
1016 if let Err(constraint_operation_error) = self.add_nogood(predicates, inference_code) {
1017 let _ = self.conclude_proof_unsat();
1018
1019 self.solver_state
1020 .declare_conflict(StoredConflictInfo::RootLevelConflict(
1021 constraint_operation_error,
1022 ));
1023 return Err(constraint_operation_error);
1024 }
1025 Ok(())
1026 }
1027
1028 pub(crate) fn get_checkpoint(&self) -> usize {
1029 self.state.get_checkpoint()
1030 }
1031}
1032
1033#[derive(Default, Debug, PartialEq, Eq)]
1034enum CSPSolverStateInternal {
1035 #[default]
1036 Ready,
1037 Solving,
1038 ContainsSolution,
1039 Conflict {
1040 conflict_info: StoredConflictInfo,
1041 },
1042 Infeasible,
1043 InfeasibleUnderAssumptions {
1044 violated_assumption: Predicate,
1045 },
1046 Timeout,
1047}
1048
1049#[derive(Default, Debug)]
1050pub struct CSPSolverState {
1051 internal_state: CSPSolverStateInternal,
1052}
1053
1054impl CSPSolverState {
1055 pub fn is_ready(&self) -> bool {
1056 matches!(self.internal_state, CSPSolverStateInternal::Ready)
1057 }
1058
1059 pub fn no_conflict(&self) -> bool {
1060 !self.is_conflicting()
1061 }
1062
1063 pub fn is_conflicting(&self) -> bool {
1064 matches!(
1065 self.internal_state,
1066 CSPSolverStateInternal::Conflict { conflict_info: _ }
1067 )
1068 }
1069
1070 pub fn is_infeasible(&self) -> bool {
1071 matches!(self.internal_state, CSPSolverStateInternal::Infeasible)
1072 }
1073
1074 pub fn is_inconsistent(&self) -> bool {
1077 self.is_conflicting() || self.is_infeasible() || self.is_infeasible_under_assumptions()
1078 }
1079
1080 pub fn is_infeasible_under_assumptions(&self) -> bool {
1081 matches!(
1082 self.internal_state,
1083 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1084 violated_assumption: _
1085 }
1086 )
1087 }
1088
1089 pub fn get_violated_assumption(&self) -> Predicate {
1090 if let CSPSolverStateInternal::InfeasibleUnderAssumptions {
1091 violated_assumption,
1092 } = self.internal_state
1093 {
1094 violated_assumption
1095 } else {
1096 panic!(
1097 "Cannot extract violated assumption without getting the solver into the infeasible
1098 under assumptions state."
1099 );
1100 }
1101 }
1102
1103 pub(crate) fn get_conflict_info(&self) -> StoredConflictInfo {
1104 match &self.internal_state {
1105 CSPSolverStateInternal::Conflict { conflict_info } => conflict_info.clone(),
1106 CSPSolverStateInternal::InfeasibleUnderAssumptions {
1107 violated_assumption,
1108 } => StoredConflictInfo::InconsistentAssumptions(*violated_assumption),
1109 _ => {
1110 panic!("Cannot extract conflict clause if solver is not in a conflict.");
1111 }
1112 }
1113 }
1114
1115 pub fn timeout(&self) -> bool {
1116 matches!(self.internal_state, CSPSolverStateInternal::Timeout)
1117 }
1118
1119 pub fn has_solution(&self) -> bool {
1120 matches!(
1121 self.internal_state,
1122 CSPSolverStateInternal::ContainsSolution
1123 )
1124 }
1125
1126 pub(crate) fn declare_ready(&mut self) {
1127 self.internal_state = CSPSolverStateInternal::Ready;
1128 }
1129
1130 pub fn declare_solving(&mut self) {
1131 pumpkin_assert_simple!((self.is_ready() || self.is_conflicting()) && !self.is_infeasible());
1132 self.internal_state = CSPSolverStateInternal::Solving;
1133 }
1134
1135 pub fn declare_infeasible(&mut self) {
1136 self.internal_state = CSPSolverStateInternal::Infeasible;
1137 }
1138
1139 pub(crate) fn declare_conflict(&mut self, conflict_info: StoredConflictInfo) {
1140 self.internal_state = CSPSolverStateInternal::Conflict { conflict_info };
1141 }
1142
1143 pub fn declare_solution_found(&mut self) {
1144 pumpkin_assert_simple!(!self.is_infeasible());
1145 self.internal_state = CSPSolverStateInternal::ContainsSolution;
1146 }
1147
1148 pub fn declare_timeout(&mut self) {
1149 pumpkin_assert_simple!(!self.is_infeasible());
1150 self.internal_state = CSPSolverStateInternal::Timeout;
1151 }
1152
1153 pub fn declare_infeasible_under_assumptions(&mut self, violated_assumption: Predicate) {
1154 pumpkin_assert_simple!(!self.is_infeasible());
1155 self.internal_state = CSPSolverStateInternal::InfeasibleUnderAssumptions {
1156 violated_assumption,
1157 }
1158 }
1159}
1160
1161declare_inference_label!(pub(crate) NogoodLabel, "nogood");
1162
1163#[cfg(test)]
1164mod tests {
1165
1166 #[derive(Debug, Clone, Copy)]
1167 struct NoLearningResolver;
1168
1169 impl ConflictResolver for NoLearningResolver {
1170 fn resolve_conflict(&mut self, context: &mut ConflictAnalysisContext) {
1171 let last_decision = context
1172 .find_last_decision()
1173 .expect("the solver is not at decision level 0, so there exists a last decision");
1174
1175 let current_checkpoint = context.get_checkpoint();
1176 context.restore_to(current_checkpoint - 1);
1177
1178 let update_occurred = context
1179 .post(!last_decision)
1180 .expect("Expected enqueued predicate to not lead to conflict directly");
1181
1182 pumpkin_assert_simple!(
1183 update_occurred,
1184 "The propagated predicate should not already be true."
1185 );
1186 }
1187 }
1188 use super::ConstraintSatisfactionSolver;
1189 use super::CoreExtractionResult;
1190 use crate::DefaultBrancher;
1191 use crate::basic_types::CSPSolverExecutionFlag;
1192 use crate::conflict_resolving::ConflictAnalysisContext;
1193 use crate::conflict_resolving::ConflictResolver;
1194 use crate::predicate;
1195 use crate::predicates::Predicate;
1196 use crate::propagation::ReadDomains;
1197 use crate::pumpkin_assert_simple;
1198 use crate::termination::Indefinite;
1199
1200 fn is_same_core(core1: &[Predicate], core2: &[Predicate]) -> bool {
1201 core1.len() == core2.len() && core2.iter().all(|lit| core1.contains(lit))
1202 }
1203
1204 fn is_result_the_same(res1: &CoreExtractionResult, res2: &CoreExtractionResult) -> bool {
1205 match (res1, res2) {
1206 (
1207 CoreExtractionResult::ConflictingAssumption(assumption1),
1208 CoreExtractionResult::ConflictingAssumption(assumption2),
1209 ) => assumption1 == assumption2,
1210 (CoreExtractionResult::Core(core1), CoreExtractionResult::Core(core2)) => {
1211 is_same_core(core1, core2)
1212 }
1213 _ => false,
1214 }
1215 }
1216
1217 fn run_test(
1218 mut solver: ConstraintSatisfactionSolver,
1219 assumptions: Vec<Predicate>,
1220 expected_flag: CSPSolverExecutionFlag,
1221 expected_result: CoreExtractionResult,
1222 ) {
1223 let mut brancher = DefaultBrancher::default_over_all_variables(&solver.state.assignments);
1224 let mut resolver = NoLearningResolver;
1225
1226 let flag = solver.solve_under_assumptions(
1227 &assumptions,
1228 &mut Indefinite,
1229 &mut brancher,
1230 &mut resolver,
1231 );
1232 assert_eq!(flag, expected_flag, "The flags do not match.");
1233
1234 if matches!(flag, CSPSolverExecutionFlag::Infeasible) {
1235 assert!(
1236 is_result_the_same(
1237 &solver.extract_clausal_core(&mut brancher),
1238 &expected_result
1239 ),
1240 "The result is not the same"
1241 );
1242 }
1243 }
1244
1245 fn create_instance1() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1246 let mut solver = ConstraintSatisfactionSolver::default();
1247 let c1 = solver.new_constraint_tag();
1248 let c2 = solver.new_constraint_tag();
1249 let c3 = solver.new_constraint_tag();
1250 let lit1 = solver.create_new_literal(None).get_true_predicate();
1251 let lit2 = solver.create_new_literal(None).get_true_predicate();
1252
1253 let _ = solver.add_clause([lit1, lit2], c1);
1254 let _ = solver.add_clause([lit1, !lit2], c2);
1255 let _ = solver.add_clause([!lit1, lit2], c3);
1256 (solver, vec![lit1, lit2])
1257 }
1258
1259 #[test]
1260 fn core_extraction_unit_core() {
1261 let mut solver = ConstraintSatisfactionSolver::default();
1262 let constraint_tag = solver.new_constraint_tag();
1263 let lit1 = solver.create_new_literal(None).get_true_predicate();
1264 let _ = solver.add_clause(vec![lit1], constraint_tag);
1265
1266 run_test(
1267 solver,
1268 vec![!lit1],
1269 CSPSolverExecutionFlag::Infeasible,
1270 CoreExtractionResult::Core(vec![!lit1]),
1271 )
1272 }
1273
1274 #[test]
1275 fn simple_core_extraction_1_1() {
1276 let (solver, lits) = create_instance1();
1277 run_test(
1278 solver,
1279 vec![!lits[0], !lits[1]],
1280 CSPSolverExecutionFlag::Infeasible,
1281 CoreExtractionResult::Core(vec![!lits[0]]),
1282 )
1283 }
1284
1285 #[test]
1286 fn simple_core_extraction_1_2() {
1287 let (solver, lits) = create_instance1();
1288 run_test(
1289 solver,
1290 vec![!lits[1], !lits[0]],
1291 CSPSolverExecutionFlag::Infeasible,
1292 CoreExtractionResult::Core(vec![!lits[1]]),
1293 );
1294 }
1295
1296 #[test]
1297 fn simple_core_extraction_1_infeasible() {
1298 let (mut solver, lits) = create_instance1();
1299 let constraint_tag = solver.new_constraint_tag();
1300 let _ = solver.add_clause([!lits[0], !lits[1]], constraint_tag);
1301 run_test(
1302 solver,
1303 vec![!lits[1], !lits[0]],
1304 CSPSolverExecutionFlag::Infeasible,
1305 CoreExtractionResult::Core(vec![]),
1306 );
1307 }
1308
1309 #[test]
1310 fn simple_core_extraction_1_core_conflicting() {
1311 let (solver, lits) = create_instance1();
1312 run_test(
1313 solver,
1314 vec![!lits[1], lits[1]],
1315 CSPSolverExecutionFlag::Infeasible,
1316 CoreExtractionResult::ConflictingAssumption(!lits[1]),
1317 );
1318 }
1319 fn create_instance2() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1320 let mut solver = ConstraintSatisfactionSolver::default();
1321 let c1 = solver.new_constraint_tag();
1322 let c2 = solver.new_constraint_tag();
1323 let lit1 = solver.create_new_literal(None).get_true_predicate();
1324 let lit2 = solver.create_new_literal(None).get_true_predicate();
1325 let lit3 = solver.create_new_literal(None).get_true_predicate();
1326
1327 let _ = solver.add_clause([lit1, lit2, lit3], c1);
1328 let _ = solver.add_clause([lit1, !lit2, lit3], c2);
1329 (solver, vec![lit1, lit2, lit3])
1330 }
1331
1332 #[test]
1333 fn simple_core_extraction_2_1() {
1334 let (solver, lits) = create_instance2();
1335 run_test(
1336 solver,
1337 vec![!lits[0], lits[1], !lits[2]],
1338 CSPSolverExecutionFlag::Infeasible,
1339 CoreExtractionResult::Core(vec![!lits[0], lits[1], !lits[2]]),
1340 );
1341 }
1342
1343 #[test]
1344 fn simple_core_extraction_2_long_assumptions_with_inconsistency_at_the_end() {
1345 let (solver, lits) = create_instance2();
1346 run_test(
1347 solver,
1348 vec![!lits[0], lits[1], !lits[2], lits[0]],
1349 CSPSolverExecutionFlag::Infeasible,
1350 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1351 );
1352 }
1353
1354 #[test]
1355 fn simple_core_extraction_2_inconsistent_long_assumptions() {
1356 let (solver, lits) = create_instance2();
1357 run_test(
1358 solver,
1359 vec![!lits[0], !lits[0], !lits[1], !lits[1], lits[0]],
1360 CSPSolverExecutionFlag::Infeasible,
1361 CoreExtractionResult::ConflictingAssumption(!lits[0]),
1362 );
1363 }
1364 fn create_instance3() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1365 let mut solver = ConstraintSatisfactionSolver::default();
1366 let constraint_tag = solver.new_constraint_tag();
1367
1368 let lit1 = solver.create_new_literal(None).get_true_predicate();
1369 let lit2 = solver.create_new_literal(None).get_true_predicate();
1370 let lit3 = solver.create_new_literal(None).get_true_predicate();
1371
1372 let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1373 (solver, vec![lit1, lit2, lit3])
1374 }
1375
1376 #[test]
1377 fn simple_core_extraction_3_1() {
1378 let (solver, lits) = create_instance3();
1379 run_test(
1380 solver,
1381 vec![!lits[0], !lits[1], !lits[2]],
1382 CSPSolverExecutionFlag::Infeasible,
1383 CoreExtractionResult::Core(vec![!lits[0], !lits[1], !lits[2]]),
1384 );
1385 }
1386
1387 #[test]
1388 fn simple_core_extraction_3_2() {
1389 let (solver, lits) = create_instance3();
1390 run_test(
1391 solver,
1392 vec![!lits[0], !lits[1]],
1393 CSPSolverExecutionFlag::Feasible,
1394 CoreExtractionResult::Core(vec![]), );
1396 }
1397
1398 #[test]
1428 fn new_domain_with_negative_lower_bound() {
1429 let lb = -2;
1430 let ub = 2;
1431
1432 let mut solver = ConstraintSatisfactionSolver::default();
1433 let domain_id = solver.create_new_integer_variable(lb, ub, None);
1434
1435 assert_eq!(lb, solver.state.assignments.get_lower_bound(domain_id));
1436
1437 assert_eq!(ub, solver.state.assignments.get_upper_bound(domain_id));
1438
1439 assert!(
1440 !solver
1441 .state
1442 .assignments
1443 .is_predicate_satisfied(predicate![domain_id == lb])
1444 );
1445
1446 for value in (lb + 1)..ub {
1447 let predicate = predicate![domain_id >= value];
1448
1449 assert!(!solver.state.assignments.is_predicate_satisfied(predicate));
1450
1451 assert!(
1452 !solver
1453 .state
1454 .assignments
1455 .is_predicate_satisfied(predicate![domain_id == value])
1456 );
1457 }
1458
1459 assert!(
1460 !solver
1461 .state
1462 .assignments
1463 .is_predicate_satisfied(predicate![domain_id == ub])
1464 );
1465 }
1466
1467 }