pumpkin_core/engine/
constraint_satisfaction_solver.rs

1//! Houses the solver which attempts to find a solution to a Constraint Satisfaction Problem (CSP)
2//! using a Lazy Clause Generation approach.
3use std::collections::VecDeque;
4use std::fmt::Debug;
5use std::time::Instant;
6
7use rand::rngs::SmallRng;
8use rand::SeedableRng;
9
10use super::conflict_analysis::AnalysisMode;
11use super::conflict_analysis::ConflictAnalysisContext;
12use super::conflict_analysis::LearnedNogood;
13use super::conflict_analysis::NoLearningResolver;
14use super::conflict_analysis::SemanticMinimiser;
15use super::notifications::NotificationEngine;
16use super::propagation::constructor::PropagatorConstructor;
17use super::propagation::store::PropagatorStore;
18use super::propagation::PropagatorId;
19use super::solver_statistics::SolverStatistics;
20use super::termination::TerminationCondition;
21use super::variables::IntegerVariable;
22use super::variables::Literal;
23use super::Lbd;
24use super::ResolutionResolver;
25use super::TrailedValues;
26use super::VariableNames;
27use crate::basic_types::moving_averages::MovingAverage;
28use crate::basic_types::CSPSolverExecutionFlag;
29use crate::basic_types::ConstraintOperationError;
30use crate::basic_types::Inconsistency;
31use crate::basic_types::PredicateId;
32use crate::basic_types::PropagationStatusCP;
33use crate::basic_types::PropositionalConjunction;
34use crate::basic_types::Random;
35use crate::basic_types::SolutionReference;
36use crate::basic_types::StoredConflictInfo;
37use crate::branching::Brancher;
38use crate::branching::SelectionContext;
39use crate::containers::HashMap;
40use crate::declare_inference_label;
41use crate::engine::conflict_analysis::ConflictResolver as Resolver;
42use crate::engine::cp::PropagatorQueue;
43use crate::engine::predicates::predicate::Predicate;
44use crate::engine::propagation::constructor::PropagatorConstructorContext;
45use crate::engine::propagation::ExplanationContext;
46use crate::engine::propagation::PropagationContext;
47use crate::engine::propagation::PropagationContextMut;
48use crate::engine::propagation::Propagator;
49use crate::engine::reason::ReasonStore;
50use crate::engine::variables::DomainId;
51use crate::engine::Assignments;
52use crate::engine::DebugHelper;
53use crate::engine::RestartOptions;
54use crate::engine::RestartStrategy;
55use crate::predicate;
56use crate::proof::explain_root_assignment;
57use crate::proof::finalize_proof;
58use crate::proof::ConstraintTag;
59use crate::proof::FinalizingContext;
60use crate::proof::InferenceCode;
61use crate::proof::ProofLog;
62use crate::proof::RootExplanationContext;
63use crate::propagators::nogoods::LearningOptions;
64use crate::propagators::nogoods::NogoodPropagator;
65use crate::pumpkin_assert_advanced;
66use crate::pumpkin_assert_eq_simple;
67use crate::pumpkin_assert_extreme;
68use crate::pumpkin_assert_moderate;
69use crate::pumpkin_assert_simple;
70use crate::statistics::statistic_logger::StatisticLogger;
71use crate::statistics::statistic_logging::should_log_statistics;
72use crate::statistics::Statistic;
73#[cfg(doc)]
74use crate::Solver;
75
76/// A solver which attempts to find a solution to a Constraint Satisfaction Problem (CSP) using
77/// a Lazy Clause Generation (LCG [\[1\]](https://people.eng.unimelb.edu.au/pstuckey/papers/cp09-lc.pdf))
78/// approach.
79///
80/// It requires that all of the propagators which are added, are able to explain the
81/// propagations and conflicts they have made/found. It then uses standard SAT concepts such as
82/// 1UIP (see \[2\]) to learn clauses (also called nogoods in the CP field, see \[3\]) to avoid
83/// unnecessary exploration of the search space while utilizing the search procedure benefits from
84/// constraint programming (e.g. by preventing the exponential blow-up of problem encodings).
85///
86/// # Practical
87/// The [`ConstraintSatisfactionSolver`] makes use of certain options which allow the user to
88/// influence the behaviour of the solver; see for example the [`SatisfactionSolverOptions`].
89///
90/// The solver switches between making decisions using implementations of the [`Brancher`] (which
91/// are passed to the [`ConstraintSatisfactionSolver::solve`] method) and propagation (use
92/// [`ConstraintSatisfactionSolver::add_propagator`] to add a propagator). If a conflict is found by
93/// any of the propagators then the solver will analyse the conflict
94/// using 1UIP reasoning and backtrack if possible.
95///
96/// # Bibliography
97/// \[1\] T. Feydy and P. J. Stuckey, ‘Lazy clause generation reengineered’, in International
98/// Conference on Principles and Practice of Constraint Programming, 2009, pp. 352–366.
99///
100/// \[2\] J. Marques-Silva, I. Lynce, and S. Malik, ‘Conflict-driven clause learning SAT
101/// solvers’, in Handbook of satisfiability, IOS press, 2021
102///
103/// \[3\] F. Rossi, P. Van Beek, and T. Walsh, ‘Constraint programming’, Foundations of Artificial
104/// Intelligence, vol. 3, pp. 181–211, 2008.
105#[derive(Debug)]
106pub struct ConstraintSatisfactionSolver {
107    /// The solver continuously changes states during the search.
108    /// The state helps track additional information and contributes to making the code clearer.
109    pub(crate) state: CSPSolverState,
110    /// The list of propagators. Propagators live here and are queried when events (domain changes)
111    /// happen. The list is only traversed during synchronisation for now.
112    propagators: PropagatorStore,
113
114    /// Tracks information about the restarts. Occassionally the solver will undo all its decisions
115    /// and start the search from the root note. Note that learned clauses and other state
116    /// information is kept after a restart.
117    restart_strategy: RestartStrategy,
118    /// Holds the assumptions when the solver is queried to solve under assumptions.
119    assumptions: Vec<Predicate>,
120    semantic_minimiser: SemanticMinimiser,
121    /// Tracks information related to the assignments of integer variables.
122    pub(crate) assignments: Assignments,
123    /// Dictates the order in which propagators will be called to propagate.
124    propagator_queue: PropagatorQueue,
125    /// Handles storing information about propagation reasons, which are used later to construct
126    /// explanations during conflict analysis
127    pub(crate) reason_store: ReasonStore,
128    /// A set of counters updated during the search.
129    solver_statistics: SolverStatistics,
130    /// Miscellaneous constant parameters used by the solver.
131    internal_parameters: SatisfactionSolverOptions,
132    /// The names of the variables in the solver.
133    variable_names: VariableNames,
134    /// Computes the LBD for nogoods.
135    lbd_helper: Lbd,
136    /// A map from predicates that are propagated at the root to inference codes in the proof.
137    unit_nogood_inference_codes: HashMap<Predicate, InferenceCode>,
138    /// The resolver which is used upon a conflict.
139    conflict_resolver: Box<dyn Resolver>,
140    /// Keep track of trailed values (i.e. values which automatically backtrack)
141    pub(crate) trailed_values: TrailedValues,
142    /// Component responsible for providing notifications for changes to the domains of variables
143    /// and/or the polarity [Predicate]s
144    notification_engine: NotificationEngine,
145}
146
147impl Default for ConstraintSatisfactionSolver {
148    fn default() -> Self {
149        ConstraintSatisfactionSolver::new(SatisfactionSolverOptions::default())
150    }
151}
152
153/// The result of [`ConstraintSatisfactionSolver::extract_clausal_core`]; there are 2 cases:
154/// 1. In the case of [`CoreExtractionResult::ConflictingAssumption`], two assumptions have been
155///    given which directly conflict with one another; e.g. if the assumptions `[x, !x]` have been
156///    given then the result of [`ConstraintSatisfactionSolver::extract_clausal_core`] will be a
157///    [`CoreExtractionResult::ConflictingAssumption`] containing `x`.
158/// 2. The standard case is when a [`CoreExtractionResult::Core`] is returned which contains (a
159///    subset of) the assumptions which led to conflict.
160#[derive(Debug, Clone)]
161pub enum CoreExtractionResult {
162    /// Conflicting assumptions were provided; e.g. in the case of the assumptions `[x, !x]`, this
163    /// result will contain `!x`
164    ConflictingAssumption(Predicate),
165    /// The standard case where this result contains the core consisting of (a subset of) the
166    /// assumptions which led to conflict.
167    Core(Vec<Predicate>),
168}
169
170/// During search, the CP solver will inevitably evaluate partial assignments that violate at
171/// least one constraint. When this happens, conflict resolution is applied to restore the
172/// solver to a state from which it can continue the search.
173///
174/// The manner in which conflict resolution is done greatly impacts the performance of the
175/// solver.
176#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
177#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
178pub enum ConflictResolver {
179    NoLearning,
180    #[default]
181    UIP,
182}
183
184/// Options for the [`Solver`] which determine how it behaves.
185#[derive(Debug)]
186pub struct SatisfactionSolverOptions {
187    /// The options used by the restart strategy.
188    pub restart_options: RestartOptions,
189    /// Whether learned clause minimisation should take place
190    pub learning_clause_minimisation: bool,
191    /// A random number generator which is used by the [`Solver`] to determine randomised values.
192    pub random_generator: SmallRng,
193    /// The proof log for the solver.
194    pub proof_log: ProofLog,
195    /// The resolver used for conflict analysis
196    pub conflict_resolver: ConflictResolver,
197    /// The options which influence the learning of the solver.
198    pub learning_options: LearningOptions,
199    /// The number of MBs which are preallocated by the nogood propagator.
200    pub memory_preallocated: usize,
201}
202
203impl Default for SatisfactionSolverOptions {
204    fn default() -> Self {
205        SatisfactionSolverOptions {
206            restart_options: RestartOptions::default(),
207            learning_clause_minimisation: true,
208            random_generator: SmallRng::seed_from_u64(42),
209            proof_log: ProofLog::default(),
210            conflict_resolver: ConflictResolver::default(),
211            learning_options: LearningOptions::default(),
212            memory_preallocated: 1000,
213        }
214    }
215}
216
217impl ConstraintSatisfactionSolver {
218    pub(crate) fn get_nogood_propagator_id() -> PropagatorId {
219        PropagatorId(0)
220    }
221
222    /// This is a temporary accessor to help refactoring.
223    pub fn get_solution_reference(&self) -> SolutionReference<'_> {
224        SolutionReference::new(&self.assignments)
225    }
226
227    /// Conclude the proof with the unsatisfiable claim.
228    ///
229    /// This method will finish the proof. Any new operation will not be logged to the proof.
230    pub fn conclude_proof_unsat(&mut self) -> std::io::Result<()> {
231        let proof = std::mem::take(&mut self.internal_parameters.proof_log);
232        proof.unsat(&self.variable_names)
233    }
234
235    /// Conclude the proof with the optimality claim.
236    ///
237    /// This method will finish the proof. Any new operation will not be logged to the proof.
238    pub fn conclude_proof_optimal(&mut self, bound: Predicate) -> std::io::Result<()> {
239        let proof = std::mem::take(&mut self.internal_parameters.proof_log);
240        proof.optimal(bound, &self.variable_names)
241    }
242
243    fn complete_proof(&mut self) {
244        let conflict = match self.state.get_conflict_info() {
245            StoredConflictInfo::Propagator(conflict) => {
246                let _ = self.internal_parameters.proof_log.log_inference(
247                    conflict.inference_code,
248                    conflict.conjunction.iter().copied(),
249                    None,
250                    &self.variable_names,
251                );
252
253                conflict.conjunction.clone()
254            }
255            StoredConflictInfo::EmptyDomain { conflict_nogood } => conflict_nogood.clone(),
256            StoredConflictInfo::RootLevelConflict(_) => {
257                unreachable!("There should always be a specified conflicting constraint.")
258            }
259        };
260
261        let context = FinalizingContext {
262            conflict,
263            propagators: &mut self.propagators,
264            proof_log: &mut self.internal_parameters.proof_log,
265            unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
266            assignments: &self.assignments,
267            reason_store: &mut self.reason_store,
268            notification_engine: &mut self.notification_engine,
269            variable_names: &self.variable_names,
270        };
271
272        finalize_proof(context);
273    }
274}
275
276// methods that offer basic functionality
277impl ConstraintSatisfactionSolver {
278    pub fn new(solver_options: SatisfactionSolverOptions) -> Self {
279        let mut csp_solver: ConstraintSatisfactionSolver = ConstraintSatisfactionSolver {
280            state: CSPSolverState::default(),
281            assumptions: Vec::default(),
282            assignments: Assignments::default(),
283            propagator_queue: PropagatorQueue::new(5),
284            reason_store: ReasonStore::default(),
285            restart_strategy: RestartStrategy::new(solver_options.restart_options),
286            propagators: PropagatorStore::default(),
287            solver_statistics: SolverStatistics::default(),
288            variable_names: VariableNames::default(),
289            semantic_minimiser: SemanticMinimiser::default(),
290            lbd_helper: Lbd::default(),
291            unit_nogood_inference_codes: Default::default(),
292            conflict_resolver: match solver_options.conflict_resolver {
293                ConflictResolver::NoLearning => Box::new(NoLearningResolver),
294                ConflictResolver::UIP => Box::new(ResolutionResolver::default()),
295            },
296            internal_parameters: solver_options,
297            trailed_values: TrailedValues::default(),
298            notification_engine: NotificationEngine::default(),
299        };
300
301        // As a convention, the assignments contain a dummy domain_id=0, which represents a 0-1
302        // variable that is assigned to one. We use it to represent predicates that are
303        // trivially true. We need to adjust other data structures to take this into account.
304        let dummy_id = Predicate::trivially_true().get_domain();
305
306        csp_solver
307            .variable_names
308            .add_integer(dummy_id, "Dummy".to_owned());
309
310        let _ = csp_solver.add_propagator(NogoodPropagator::with_options(
311            // 1_000_000 bytes in 1 MB
312            (csp_solver.internal_parameters.memory_preallocated * 1_000_000)
313                / size_of::<PredicateId>(),
314            csp_solver.internal_parameters.learning_options,
315        ));
316
317        assert!(dummy_id.id() == 0);
318        assert!(csp_solver.assignments.get_lower_bound(dummy_id) == 1);
319        assert!(csp_solver.assignments.get_upper_bound(dummy_id) == 1);
320
321        csp_solver
322    }
323
324    pub fn solve(
325        &mut self,
326        termination: &mut impl TerminationCondition,
327        brancher: &mut impl Brancher,
328    ) -> CSPSolverExecutionFlag {
329        let dummy_assumptions: Vec<Predicate> = vec![];
330        self.solve_under_assumptions(&dummy_assumptions, termination, brancher)
331    }
332
333    pub fn solve_under_assumptions(
334        &mut self,
335        assumptions: &[Predicate],
336        termination: &mut impl TerminationCondition,
337        brancher: &mut impl Brancher,
338    ) -> CSPSolverExecutionFlag {
339        if self.state.is_inconsistent() {
340            return CSPSolverExecutionFlag::Infeasible;
341        }
342
343        let start_time = Instant::now();
344
345        self.initialise(assumptions);
346        let result = self.solve_internal(termination, brancher);
347
348        self.solver_statistics
349            .engine_statistics
350            .time_spent_in_solver += start_time.elapsed().as_millis() as u64;
351
352        result
353    }
354
355    pub fn get_state(&self) -> &CSPSolverState {
356        &self.state
357    }
358
359    pub fn get_random_generator(&mut self) -> &mut impl Random {
360        &mut self.internal_parameters.random_generator
361    }
362
363    pub fn log_statistics(&self) {
364        // We first check whether the statistics will/should be logged to prevent unnecessarily
365        // going through all the propagators
366        if should_log_statistics() {
367            self.solver_statistics.log(StatisticLogger::default());
368            for (index, propagator) in self.propagators.iter_propagators().enumerate() {
369                propagator.log_statistics(StatisticLogger::new([
370                    propagator.name(),
371                    "number",
372                    index.to_string().as_str(),
373                ]));
374            }
375        }
376    }
377
378    pub fn create_new_literal(&mut self, name: Option<String>) -> Literal {
379        let domain_id = self.create_new_integer_variable(0, 1, name);
380        Literal::new(domain_id)
381    }
382
383    pub fn create_new_literal_for_predicate(
384        &mut self,
385        predicate: Predicate,
386        name: Option<String>,
387        constraint_tag: ConstraintTag,
388    ) -> Literal {
389        let literal = self.create_new_literal(name);
390
391        self.internal_parameters
392            .proof_log
393            .reify_predicate(literal, predicate);
394
395        // If literal --> predicate
396        let _ = self.add_clause(
397            vec![!literal.get_true_predicate(), predicate],
398            constraint_tag,
399        );
400
401        // If !literal --> !predicate
402        let _ = self.add_clause(
403            vec![!literal.get_false_predicate(), !predicate],
404            constraint_tag,
405        );
406
407        literal
408    }
409
410    /// Create a new integer variable. Its domain will have the given lower and upper bounds.
411    pub fn create_new_integer_variable(
412        &mut self,
413        lower_bound: i32,
414        upper_bound: i32,
415        name: Option<String>,
416    ) -> DomainId {
417        assert!(
418            !self.state.is_inconsistent(),
419            "Variables cannot be created in an inconsistent state"
420        );
421
422        let domain_id = self.assignments.grow(lower_bound, upper_bound);
423        self.notification_engine.grow();
424
425        if let Some(name) = name {
426            self.variable_names.add_integer(domain_id, name);
427        }
428
429        domain_id
430    }
431
432    /// Creates an integer variable with a domain containing only the values in `values`
433    pub fn create_new_integer_variable_sparse(
434        &mut self,
435        values: Vec<i32>,
436        name: Option<String>,
437    ) -> DomainId {
438        let domain_id = self.assignments.create_new_integer_variable_sparse(values);
439
440        self.notification_engine.grow();
441
442        if let Some(name) = name {
443            self.variable_names.add_integer(domain_id, name);
444        }
445
446        domain_id
447    }
448
449    /// Create a new [`ConstraintTag`].
450    pub fn new_constraint_tag(&mut self) -> ConstraintTag {
451        self.internal_parameters.proof_log.new_constraint_tag()
452    }
453
454    /// Returns an unsatisfiable core or an [`Err`] if the provided assumptions were conflicting
455    /// with one another ([`Err`] then contain the [`Literal`] which was conflicting).
456    ///
457    /// We define an unsatisfiable core as a clause containing only negated assumption literals,
458    /// which is implied by the formula. Alternatively, it is the negation of a conjunction of
459    /// assumptions which cannot be satisfied together with the rest of the formula. The clause is
460    /// not necessarily unique or minimal.
461    ///
462    /// The unsatisfiable core can be verified with reverse unit propagation (RUP).
463    ///
464    /// *Notes:*
465    ///   - If the solver is not in an unsatisfied state, this method will panic.
466    ///   - If the solver is in an unsatisfied state, but solving was done without assumptions, this
467    ///     will return an empty vector.
468    ///   - If the assumptions are inconsistent, i.e. both literal x and !x are assumed, an error is
469    ///     returned, with the literal being one of the inconsistent assumptions.
470    ///
471    /// # Example usage
472    /// ```rust
473    /// // We construct the following SAT instance:
474    /// //   (x0 \/ x1 \/ x2) /\ (x0 \/ !x1 \/ x2)
475    /// // And solve under the assumptions:
476    /// //   !x0 /\ x1 /\ !x2
477    /// # use pumpkin_solver::Solver;
478    /// # use pumpkin_solver::termination::Indefinite;
479    /// # use pumpkin_solver::results::SatisfactionResultUnderAssumptions;
480    /// let mut solver = Solver::default();
481    ///
482    /// // We use a dummy constraint tag for this example.
483    /// let constraint_tag = solver.new_constraint_tag();
484    ///
485    /// let x = vec![
486    ///     solver.new_literal().get_true_predicate(),
487    ///     solver.new_literal().get_true_predicate(),
488    ///     solver.new_literal().get_true_predicate(),
489    /// ];
490    ///
491    /// solver.add_clause([x[0], x[1], x[2]], constraint_tag);
492    /// solver.add_clause([x[0], !x[1], x[2]], constraint_tag);
493    ///
494    /// let assumptions = [!x[0], x[1], !x[2]];
495    /// let mut termination = Indefinite;
496    /// let mut brancher = solver.default_brancher();
497    /// let result = solver.satisfy_under_assumptions(&mut brancher, &mut termination, &assumptions);
498    ///
499    /// if let SatisfactionResultUnderAssumptions::UnsatisfiableUnderAssumptions(mut unsatisfiable) =
500    ///     result
501    /// {
502    ///     {
503    ///         let core = unsatisfiable.extract_core();
504    ///
505    ///         // The order of the literals in the core is undefined, so we check for unordered
506    ///         // equality.
507    ///         assert_eq!(
508    ///             core.len(),
509    ///             assumptions.len(),
510    ///             "The core has the length of the number of assumptions"
511    ///         );
512    ///         assert!(
513    ///             core.iter().all(|&lit| assumptions.contains(&lit)),
514    ///             "All literals in the core are assumptions"
515    ///         );
516    ///     }
517    /// }
518    /// ```
519    pub fn extract_clausal_core(&mut self, brancher: &mut impl Brancher) -> CoreExtractionResult {
520        if self.state.is_infeasible() {
521            return CoreExtractionResult::Core(vec![]);
522        }
523
524        self.assumptions
525            .iter()
526            .enumerate()
527            .find(|(index, assumption)| {
528                self.assumptions
529                    .iter()
530                    .skip(index + 1)
531                    .any(|other_assumptiion| {
532                        assumption.is_mutually_exclusive_with(*other_assumptiion)
533                    })
534            })
535            .map(|(_, conflicting_assumption)| {
536                CoreExtractionResult::ConflictingAssumption(*conflicting_assumption)
537            })
538            .unwrap_or_else(|| {
539                let mut conflict_analysis_context = ConflictAnalysisContext {
540                    assignments: &mut self.assignments,
541                    counters: &mut self.solver_statistics,
542                    solver_state: &mut self.state,
543                    reason_store: &mut self.reason_store,
544                    brancher,
545                    semantic_minimiser: &mut self.semantic_minimiser,
546                    propagators: &mut self.propagators,
547                    notification_engine: &mut self.notification_engine,
548                    propagator_queue: &mut self.propagator_queue,
549                    should_minimise: self.internal_parameters.learning_clause_minimisation,
550                    proof_log: &mut self.internal_parameters.proof_log,
551                    unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
552                    trailed_values: &mut self.trailed_values,
553                    variable_names: &self.variable_names,
554                };
555
556                let mut resolver = ResolutionResolver::with_mode(AnalysisMode::AllDecision);
557                let learned_nogood = resolver
558                    .resolve_conflict(&mut conflict_analysis_context)
559                    .expect("Expected core extraction to be able to extract a core");
560
561                CoreExtractionResult::Core(learned_nogood.predicates.clone())
562            })
563    }
564
565    pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
566        let literal_is_true = self
567            .assignments
568            .is_predicate_satisfied(literal.get_true_predicate());
569        let opposite_literal_is_true = self
570            .assignments
571            .is_predicate_satisfied((!literal).get_true_predicate());
572
573        pumpkin_assert_moderate!(!(literal_is_true && opposite_literal_is_true));
574
575        // If both the literal is not true and its negation is not true then the literal is
576        // unassigned
577        if !literal_is_true && !opposite_literal_is_true {
578            None
579        } else {
580            Some(literal_is_true)
581        }
582    }
583
584    /// Get the lower bound for the given variable.
585    pub fn get_lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
586        variable.lower_bound(&self.assignments)
587    }
588
589    /// Get the upper bound for the given variable.
590    pub fn get_upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
591        variable.upper_bound(&self.assignments)
592    }
593
594    /// Determine whether `value` is in the domain of `variable`.
595    pub fn integer_variable_contains(&self, variable: &impl IntegerVariable, value: i32) -> bool {
596        variable.contains(&self.assignments, value)
597    }
598
599    /// Get the assigned integer for the given variable. If it is not assigned, `None` is returned.
600    pub fn get_assigned_integer_value(&self, variable: &impl IntegerVariable) -> Option<i32> {
601        let lb = self.get_lower_bound(variable);
602        let ub = self.get_upper_bound(variable);
603
604        if lb == ub {
605            Some(lb)
606        } else {
607            None
608        }
609    }
610
611    pub fn restore_state_at_root(&mut self, brancher: &mut impl Brancher) {
612        if self.assignments.get_decision_level() != 0 {
613            ConstraintSatisfactionSolver::backtrack(
614                &mut self.notification_engine,
615                &mut self.assignments,
616                &mut self.reason_store,
617                &mut self.propagator_queue,
618                &mut self.propagators,
619                0,
620                brancher,
621                &mut self.trailed_values,
622            );
623            self.state.declare_ready();
624        } else if self.state.internal_state == CSPSolverStateInternal::ContainsSolution {
625            self.state.declare_ready();
626        }
627    }
628}
629
630// methods that serve as the main building blocks
631impl ConstraintSatisfactionSolver {
632    fn initialise(&mut self, assumptions: &[Predicate]) {
633        pumpkin_assert_simple!(
634            !self.state.is_infeasible_under_assumptions(),
635            "Solver is not expected to be in the infeasible under assumptions state when initialising.
636             Missed extracting the core?"
637        );
638        self.state.declare_solving();
639        assumptions.clone_into(&mut self.assumptions);
640    }
641
642    fn solve_internal(
643        &mut self,
644        termination: &mut impl TerminationCondition,
645        brancher: &mut impl Brancher,
646    ) -> CSPSolverExecutionFlag {
647        loop {
648            if termination.should_stop() {
649                self.state.declare_timeout();
650                return CSPSolverExecutionFlag::Timeout;
651            }
652
653            self.propagate();
654
655            if self.state.no_conflict() {
656                // Restarts should only occur after a new decision level has been declared to
657                // account for the fact that all assumptions should be assigned when restarts take
658                // place. Since one assumption is posted per decision level, all assumptions are
659                // assigned when the decision level is strictly larger than the number of
660                // assumptions.
661                if self.get_decision_level() > self.assumptions.len()
662                    && self.restart_strategy.should_restart()
663                {
664                    self.restart_during_search(brancher);
665                }
666
667                let branching_result = self.make_next_decision(brancher);
668
669                match branching_result {
670                    Err(CSPSolverExecutionFlag::Infeasible) => {
671                        // Can happen when the branching decision was an assumption
672                        // that is inconsistent with the current assignment. We do not
673                        // have to declare a new state, as it will be done inside the
674                        // `make_next_decision` function.
675                        pumpkin_assert_simple!(self.state.is_infeasible_under_assumptions());
676
677                        self.complete_proof();
678                        return CSPSolverExecutionFlag::Infeasible;
679                    }
680
681                    Err(flag) => return flag,
682                    Ok(()) => {}
683                }
684
685                if let Err(flag) = branching_result {
686                    return flag;
687                }
688            } else {
689                if self.get_decision_level() == 0 {
690                    self.complete_proof();
691                    self.state.declare_infeasible();
692
693                    return CSPSolverExecutionFlag::Infeasible;
694                }
695
696                self.resolve_conflict_with_nogood(brancher);
697
698                brancher.on_conflict();
699                self.decay_nogood_activities();
700            }
701        }
702    }
703
704    fn decay_nogood_activities(&mut self) {
705        match self.propagators[Self::get_nogood_propagator_id()].downcast_mut::<NogoodPropagator>()
706        {
707            Some(nogood_propagator) => {
708                nogood_propagator.decay_nogood_activities();
709            }
710            None => panic!("Provided propagator should be the nogood propagator"),
711        }
712    }
713
714    fn make_next_decision(
715        &mut self,
716        brancher: &mut impl Brancher,
717    ) -> Result<(), CSPSolverExecutionFlag> {
718        // Set the next decision to be an assumption, if there are assumptions left.
719        // Currently assumptions are implemented by adding an assumption predicate
720        // at separate decision levels.
721        if let Some(assumption_literal) = self.peek_next_assumption_predicate() {
722            self.declare_new_decision_level();
723
724            let _ = self
725                .assignments
726                .post_predicate(assumption_literal, None, &mut self.notification_engine)
727                .map_err(|_| {
728                    self.state
729                        .declare_infeasible_under_assumptions(assumption_literal);
730                    CSPSolverExecutionFlag::Infeasible
731                })?;
732
733            return Ok(());
734        }
735
736        // Otherwise proceed with standard branching.
737        let context = &mut SelectionContext::new(
738            &self.assignments,
739            &mut self.internal_parameters.random_generator,
740        );
741
742        // If there is a next decision, make the decision.
743        let Some(decision_predicate) = brancher.next_decision(context) else {
744            // Otherwise there are no more decisions to be made,
745            // all predicates have been applied without a conflict,
746            // meaning the problem is feasible.
747            self.state.declare_solution_found();
748            return Err(CSPSolverExecutionFlag::Feasible);
749        };
750
751        self.declare_new_decision_level();
752
753        // Note: This also checks that the decision predicate is not already true. That is a
754        // stronger check than the `.expect(...)` used later on when handling the result of
755        // `Assignments::post_predicate`.
756        pumpkin_assert_moderate!(
757            !self.assignments.is_predicate_satisfied(decision_predicate),
758            "Decision should not already be assigned; double check the brancher"
759        );
760
761        self.solver_statistics.engine_statistics.num_decisions += 1;
762        let update_occurred = self
763            .assignments
764            .post_predicate(decision_predicate, None, &mut self.notification_engine)
765            .expect("Decisions are expected not to fail.");
766        pumpkin_assert_simple!(update_occurred);
767
768        Ok(())
769    }
770
771    pub(crate) fn declare_new_decision_level(&mut self) {
772        self.assignments.increase_decision_level();
773        self.notification_engine.increase_decision_level();
774        self.trailed_values.increase_decision_level();
775        self.reason_store.increase_decision_level();
776    }
777
778    /// Changes the state based on the conflict analysis. It performs the following:
779    /// - Derives a nogood using our CP version of the 1UIP scheme.
780    /// - Adds the learned nogood to the database.
781    /// - Performs backtracking.
782    /// - Enqueues the propagated [`Predicate`] of the learned nogood.
783    /// - Todo: Updates the internal data structures (e.g. for the restart strategy or the learned
784    ///   clause manager)
785    ///
786    /// # Note
787    /// This method performs no propagation, this is left up to the solver afterwards.
788    fn resolve_conflict_with_nogood(&mut self, brancher: &mut impl Brancher) {
789        pumpkin_assert_moderate!(self.state.is_conflicting());
790
791        let current_decision_level = self.get_decision_level();
792
793        let mut conflict_analysis_context = ConflictAnalysisContext {
794            assignments: &mut self.assignments,
795            counters: &mut self.solver_statistics,
796            solver_state: &mut self.state,
797            reason_store: &mut self.reason_store,
798            brancher,
799            semantic_minimiser: &mut self.semantic_minimiser,
800            notification_engine: &mut self.notification_engine,
801            propagators: &mut self.propagators,
802            propagator_queue: &mut self.propagator_queue,
803            should_minimise: self.internal_parameters.learning_clause_minimisation,
804            proof_log: &mut self.internal_parameters.proof_log,
805            unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
806            trailed_values: &mut self.trailed_values,
807            variable_names: &self.variable_names,
808        };
809
810        let learned_nogood = self
811            .conflict_resolver
812            .resolve_conflict(&mut conflict_analysis_context);
813
814        // important to notify about the conflict _before_ backtracking removes literals from
815        // the trail -> although in the current version this does nothing but notify that a
816        // conflict happened
817        if let Some(learned_nogood) = learned_nogood.as_ref() {
818            conflict_analysis_context
819                .counters
820                .learned_clause_statistics
821                .average_backtrack_amount
822                .add_term((current_decision_level - learned_nogood.backjump_level) as u64);
823
824            self.restart_strategy.notify_conflict(
825                self.lbd_helper.compute_lbd(
826                    &learned_nogood.predicates,
827                    conflict_analysis_context.assignments,
828                ),
829                conflict_analysis_context
830                    .assignments
831                    .get_pruned_value_count(),
832            );
833        }
834
835        let result = self
836            .conflict_resolver
837            .process(&mut conflict_analysis_context, &learned_nogood);
838        if result.is_err() {
839            unreachable!("Cannot resolve nogood and reach error")
840        }
841
842        if let Some(learned_nogood) = learned_nogood {
843            let constraint_tag = self
844                .internal_parameters
845                .proof_log
846                .log_deduction(
847                    learned_nogood.predicates.iter().copied(),
848                    &self.variable_names,
849                )
850                .expect("Failed to write proof log");
851
852            let inference_code = self
853                .internal_parameters
854                .proof_log
855                .create_inference_code(constraint_tag, NogoodLabel);
856
857            if learned_nogood.predicates.len() == 1 {
858                let _ = self
859                    .unit_nogood_inference_codes
860                    .insert(!learned_nogood.predicates[0], inference_code);
861            }
862
863            self.solver_statistics
864                .learned_clause_statistics
865                .num_unit_clauses_learned += (learned_nogood.predicates.len() == 1) as u64;
866
867            self.solver_statistics
868                .learned_clause_statistics
869                .average_learned_clause_length
870                .add_term(learned_nogood.predicates.len() as u64);
871
872            self.add_learned_nogood(learned_nogood, inference_code);
873        }
874
875        self.state.declare_solving();
876    }
877
878    fn add_learned_nogood(&mut self, learned_nogood: LearnedNogood, inference_code: InferenceCode) {
879        let mut context = PropagationContextMut::new(
880            &mut self.trailed_values,
881            &mut self.assignments,
882            &mut self.reason_store,
883            &mut self.semantic_minimiser,
884            &mut self.notification_engine,
885            Self::get_nogood_propagator_id(),
886        );
887
888        ConstraintSatisfactionSolver::add_asserting_nogood_to_nogood_propagator(
889            &mut self.propagators[Self::get_nogood_propagator_id()],
890            inference_code,
891            learned_nogood.predicates,
892            &mut context,
893            &mut self.solver_statistics,
894        )
895    }
896
897    pub(crate) fn add_asserting_nogood_to_nogood_propagator(
898        nogood_propagator: &mut dyn Propagator,
899        inference_code: InferenceCode,
900        nogood: Vec<Predicate>,
901        context: &mut PropagationContextMut,
902        statistics: &mut SolverStatistics,
903    ) {
904        match nogood_propagator.downcast_mut::<NogoodPropagator>() {
905            Some(nogood_propagator) => {
906                nogood_propagator.add_asserting_nogood(nogood, inference_code, context, statistics)
907            }
908            None => panic!("Provided propagator should be the nogood propagator"),
909        }
910    }
911
912    /// Performs a restart during the search process; it is only called when it has been determined
913    /// to be necessary by the [`ConstraintSatisfactionSolver::restart_strategy`]. A 'restart'
914    /// differs from backtracking to level zero in that a restart backtracks to decision level
915    /// zero and then performs additional operations, e.g., clean up learned clauses, adjust
916    /// restart frequency, etc.
917    ///
918    /// This method will also increase the decision level after backtracking.
919    ///
920    /// Returns true if a restart took place and false otherwise.
921    fn restart_during_search(&mut self, brancher: &mut impl Brancher) {
922        pumpkin_assert_simple!(
923            self.get_decision_level() > self.assumptions.len(),
924            "Sanity check: restarts should not trigger whilst assigning assumptions"
925        );
926
927        // no point backtracking past the assumption level
928        if self.get_decision_level() <= self.assumptions.len() {
929            return;
930        }
931
932        if brancher.is_restart_pointless() {
933            // If the brancher is static then there is no point in restarting as it would make the
934            // exact same decision
935            return;
936        }
937
938        self.solver_statistics.engine_statistics.num_restarts += 1;
939
940        ConstraintSatisfactionSolver::backtrack(
941            &mut self.notification_engine,
942            &mut self.assignments,
943            &mut self.reason_store,
944            &mut self.propagator_queue,
945            &mut self.propagators,
946            0,
947            brancher,
948            &mut self.trailed_values,
949        );
950
951        self.restart_strategy.notify_restart();
952    }
953
954    #[allow(
955        clippy::too_many_arguments,
956        reason = "This method requires this many arguments, though a backtracking context could be considered; for now this function needs to be used by conflict analysis"
957    )]
958    pub(crate) fn backtrack<BrancherType: Brancher + ?Sized>(
959        notification_engine: &mut NotificationEngine,
960        assignments: &mut Assignments,
961        reason_store: &mut ReasonStore,
962        propagator_queue: &mut PropagatorQueue,
963        propagators: &mut PropagatorStore,
964        backtrack_level: usize,
965        brancher: &mut BrancherType,
966        trailed_values: &mut TrailedValues,
967    ) {
968        pumpkin_assert_simple!(backtrack_level < assignments.get_decision_level());
969
970        brancher.on_backtrack();
971
972        assignments
973            .synchronise(backtrack_level, notification_engine)
974            .iter()
975            .for_each(|(domain_id, previous_value)| {
976                brancher.on_unassign_integer(*domain_id, *previous_value)
977            });
978
979        trailed_values.synchronise(backtrack_level);
980
981        reason_store.synchronise(backtrack_level);
982        propagator_queue.clear();
983        // For now all propagators are called to synchronise, in the future this will be improved in
984        // two ways:
985        //      + allow incremental synchronisation
986        //      + only call the subset of propagators that were notified since last backtrack
987        for propagator in propagators.iter_propagators_mut() {
988            let context = PropagationContext::new(assignments);
989            propagator.synchronise(context);
990        }
991
992        brancher.synchronise(assignments);
993
994        let _ = notification_engine.process_backtrack_events(assignments, propagators);
995        notification_engine.clear_event_drain();
996
997        notification_engine.update_last_notified_index(assignments);
998        // Should be done after the assignments and trailed values have been synchronised
999        notification_engine.synchronise(backtrack_level, assignments, trailed_values);
1000    }
1001
1002    pub(crate) fn compute_reason_for_empty_domain(&mut self) -> PropositionalConjunction {
1003        // The empty domain happened after posting the last predicate on the trail.
1004        // The reason for this empty domain is computed as the reason for the bounds before the last
1005        // trail predicate was posted, plus the reason for the last trail predicate.
1006
1007        // The last predicate on the trail reveals the domain id that has resulted
1008        // in an empty domain.
1009        let entry = self.assignments.get_last_entry_on_trail();
1010        let (entry_reason, entry_inference_code) = entry
1011            .reason
1012            .expect("Cannot cause an empty domain using a decision.");
1013        let conflict_domain = entry.predicate.get_domain();
1014        assert!(
1015            entry.old_lower_bound != self.assignments.get_lower_bound(conflict_domain)
1016                || entry.old_upper_bound != self.assignments.get_upper_bound(conflict_domain),
1017            "One of the two bounds had to change."
1018        );
1019
1020        // Look up the reason for the bound that changed.
1021        // The reason for changing the bound cannot be a decision, so we can safely unwrap.
1022        let mut empty_domain_reason: Vec<Predicate> = vec![];
1023        let _ = self.reason_store.get_or_compute(
1024            entry_reason,
1025            ExplanationContext::without_working_nogood(
1026                &self.assignments,
1027                self.assignments.num_trail_entries() - 1,
1028                &mut self.notification_engine,
1029            ),
1030            &mut self.propagators,
1031            &mut empty_domain_reason,
1032        );
1033
1034        // We also need to log this last propagation to the proof log as an inference.
1035        let _ = self.internal_parameters.proof_log.log_inference(
1036            entry_inference_code,
1037            empty_domain_reason.iter().copied(),
1038            Some(entry.predicate),
1039            &self.variable_names,
1040        );
1041
1042        empty_domain_reason.extend([
1043            predicate!(conflict_domain >= entry.old_lower_bound),
1044            predicate!(conflict_domain <= entry.old_upper_bound),
1045        ]);
1046
1047        empty_domain_reason.into()
1048    }
1049
1050    /// Main propagation loop.
1051    pub(crate) fn propagate(&mut self) {
1052        // Record the number of predicates on the trail for statistics purposes.
1053        let num_assigned_variables_old = self.assignments.num_trail_entries();
1054        // The initial domain events are due to the decision predicate.
1055        self.notification_engine
1056            .notify_propagators_about_domain_events(
1057                &mut self.assignments,
1058                &mut self.trailed_values,
1059                &mut self.propagators,
1060                &mut self.propagator_queue,
1061            );
1062        // Keep propagating until there are unprocessed propagators, or a conflict is detected.
1063        while let Some(propagator_id) = self.propagator_queue.pop() {
1064            let num_trail_entries_before = self.assignments.num_trail_entries();
1065
1066            let propagation_status = {
1067                let propagator = &mut self.propagators[propagator_id];
1068                let context = PropagationContextMut::new(
1069                    &mut self.trailed_values,
1070                    &mut self.assignments,
1071                    &mut self.reason_store,
1072                    &mut self.semantic_minimiser,
1073                    &mut self.notification_engine,
1074                    propagator_id,
1075                );
1076                propagator.propagate(context)
1077            };
1078
1079            if self.assignments.get_decision_level() == 0 {
1080                self.handle_root_propagation(num_trail_entries_before);
1081            }
1082            match propagation_status {
1083                Ok(_) => {
1084                    // Notify other propagators of the propagations and continue.
1085                    self.notification_engine
1086                        .notify_propagators_about_domain_events(
1087                            &mut self.assignments,
1088                            &mut self.trailed_values,
1089                            &mut self.propagators,
1090                            &mut self.propagator_queue,
1091                        );
1092                }
1093                Err(inconsistency) => match inconsistency {
1094                    // A propagator did a change that resulted in an empty domain.
1095                    Inconsistency::EmptyDomain => {
1096                        self.prepare_for_conflict_resolution();
1097                        break;
1098                    }
1099                    // A propagator-specific reason for the current conflict.
1100                    Inconsistency::Conflict(conflict) => {
1101                        pumpkin_assert_advanced!(DebugHelper::debug_reported_failure(
1102                            &self.trailed_values,
1103                            &self.assignments,
1104                            &conflict.conjunction,
1105                            &self.propagators[propagator_id],
1106                            propagator_id,
1107                            &self.notification_engine
1108                        ));
1109
1110                        let stored_conflict_info = StoredConflictInfo::Propagator(conflict);
1111                        self.state.declare_conflict(stored_conflict_info);
1112                        break;
1113                    }
1114                },
1115            }
1116            pumpkin_assert_extreme!(
1117                DebugHelper::debug_check_propagations(
1118                    num_trail_entries_before,
1119                    propagator_id,
1120                    &self.trailed_values,
1121                    &self.assignments,
1122                    &mut self.reason_store,
1123                    &mut self.propagators,
1124                    &self.notification_engine
1125                ),
1126                "Checking the propagations performed by the propagator led to inconsistencies!"
1127            );
1128        }
1129        // Record statistics.
1130        self.solver_statistics.engine_statistics.num_conflicts +=
1131            self.state.is_conflicting() as u64;
1132        self.solver_statistics.engine_statistics.num_propagations +=
1133            self.assignments.num_trail_entries() as u64 - num_assigned_variables_old as u64;
1134        // Only check fixed point propagation if there was no reported conflict,
1135        // since otherwise the state may be inconsistent.
1136        pumpkin_assert_extreme!(
1137            self.state.is_conflicting()
1138                || DebugHelper::debug_fixed_point_propagation(
1139                    &self.trailed_values,
1140                    &self.assignments,
1141                    &self.propagators,
1142                    &self.notification_engine
1143                )
1144        );
1145    }
1146
1147    /// Introduces any root-level propagations to the proof by introducing them as
1148    /// nogoods.
1149    ///
1150    /// The inference `R -> l` is logged to the proof as follows:
1151    /// 1. Infernce `R /\ ~l -> false`
1152    /// 2. Nogood (clause) `l`
1153    fn handle_root_propagation(&mut self, start_trail_index: usize) {
1154        pumpkin_assert_eq_simple!(self.get_decision_level(), 0);
1155
1156        for trail_idx in start_trail_index..self.assignments.num_trail_entries() {
1157            let entry = self.assignments.get_trail_entry(trail_idx);
1158            let (reason_ref, inference_code) = entry
1159                .reason
1160                .expect("Added by a propagator and must therefore have a reason");
1161
1162            if !self.internal_parameters.proof_log.is_logging_inferences() {
1163                // In case we are not logging inferences, we only need to keep track
1164                // of the root-level inferences to allow us to correctly finalize the
1165                // proof.
1166                let _ = self
1167                    .unit_nogood_inference_codes
1168                    .insert(entry.predicate, inference_code);
1169                continue;
1170            }
1171
1172            // Get the conjunction of predicates explaining the propagation.
1173            let mut reason = vec![];
1174            let _ = self.reason_store.get_or_compute(
1175                reason_ref,
1176                ExplanationContext::without_working_nogood(
1177                    &self.assignments,
1178                    trail_idx,
1179                    &mut self.notification_engine,
1180                ),
1181                &mut self.propagators,
1182                &mut reason,
1183            );
1184
1185            let propagated = entry.predicate;
1186
1187            // The proof inference for the propagation `R -> l` is `R /\ ~l -> false`.
1188            let inference_premises = reason.iter().copied().chain(std::iter::once(!propagated));
1189            let _ = self.internal_parameters.proof_log.log_inference(
1190                inference_code,
1191                inference_premises,
1192                None,
1193                &self.variable_names,
1194            );
1195
1196            // Since inference steps are only related to the nogood they directly precede,
1197            // facts derived at the root are also logged as nogoods so they can be used in the
1198            // derivation of other nogoods.
1199            //
1200            // In case we are logging hints, we must therefore identify what proof steps contribute
1201            // to the derivation of the current nogood, and therefore are in the premise of the
1202            // previously logged inference. These proof steps are necessarily unit nogoods, and
1203            // therefore we recursively look up which unit nogoods are involved in the premise of
1204            // the inference.
1205
1206            let mut to_explain: VecDeque<Predicate> = reason.iter().copied().collect();
1207
1208            while let Some(premise) = to_explain.pop_front() {
1209                pumpkin_assert_simple!(self.assignments.is_predicate_satisfied(premise));
1210
1211                let mut context = RootExplanationContext {
1212                    propagators: &mut self.propagators,
1213                    proof_log: &mut self.internal_parameters.proof_log,
1214                    unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
1215                    assignments: &self.assignments,
1216                    reason_store: &mut self.reason_store,
1217                    notification_engine: &mut self.notification_engine,
1218                    variable_names: &self.variable_names,
1219                };
1220
1221                explain_root_assignment(&mut context, premise);
1222            }
1223
1224            // Log the nogood which adds the root-level knowledge to the proof.
1225            let constraint_tag = self
1226                .internal_parameters
1227                .proof_log
1228                .log_deduction([!propagated], &self.variable_names);
1229
1230            if let Ok(constraint_tag) = constraint_tag {
1231                let inference_code = self
1232                    .internal_parameters
1233                    .proof_log
1234                    .create_inference_code(constraint_tag, NogoodLabel);
1235
1236                let _ = self
1237                    .unit_nogood_inference_codes
1238                    .insert(propagated, inference_code);
1239            }
1240        }
1241    }
1242
1243    fn peek_next_assumption_predicate(&self) -> Option<Predicate> {
1244        // The convention is that at decision level i, the (i-1)th assumption is posted.
1245        // Note that decisions start being posted start at 1, hence the minus one.
1246        let next_assumption_index = self.get_decision_level();
1247        self.assumptions.get(next_assumption_index).copied()
1248    }
1249}
1250
1251// methods for adding constraints (propagators and clauses)
1252impl ConstraintSatisfactionSolver {
1253    /// See [`crate::Solver::add_propagator`] for documentation.
1254    pub(crate) fn add_propagator<Constructor>(
1255        &mut self,
1256        constructor: Constructor,
1257    ) -> Result<(), ConstraintOperationError>
1258    where
1259        Constructor: PropagatorConstructor,
1260        Constructor::PropagatorImpl: 'static,
1261    {
1262        if self.state.is_inconsistent() {
1263            return Err(ConstraintOperationError::InfeasiblePropagator);
1264        }
1265
1266        let propagator_slot = self.propagators.new_propagator();
1267
1268        let constructor_context = PropagatorConstructorContext::new(
1269            &mut self.notification_engine,
1270            &mut self.trailed_values,
1271            &mut self.internal_parameters.proof_log,
1272            propagator_slot.key(),
1273            &mut self.assignments,
1274        );
1275
1276        let propagator = Box::new(constructor.create(constructor_context));
1277
1278        pumpkin_assert_simple!(
1279            propagator.priority() <= 3,
1280            "The propagator priority exceeds 3.
1281             Currently we only support values up to 3,
1282             but this can easily be changed if there is a good reason."
1283        );
1284        let new_propagator_id = propagator_slot.populate(propagator);
1285
1286        let new_propagator = &mut self.propagators[new_propagator_id];
1287
1288        self.propagator_queue
1289            .enqueue_propagator(new_propagator_id, new_propagator.priority());
1290
1291        self.propagate();
1292
1293        if self.state.no_conflict() {
1294            Ok(())
1295        } else {
1296            self.complete_proof();
1297            let _ = self.conclude_proof_unsat();
1298            Err(ConstraintOperationError::InfeasiblePropagator)
1299        }
1300    }
1301
1302    pub fn post_predicate(&mut self, predicate: Predicate) -> Result<(), ConstraintOperationError> {
1303        assert!(
1304            self.get_decision_level() == 0,
1305            "Can only post predicates at the root level."
1306        );
1307
1308        if self.state.is_infeasible() {
1309            Err(ConstraintOperationError::InfeasibleState)
1310        } else {
1311            match self
1312                .assignments
1313                .post_predicate(predicate, None, &mut self.notification_engine)
1314            {
1315                Ok(_) => Ok(()),
1316                Err(_) => Err(ConstraintOperationError::InfeasibleNogood),
1317            }
1318        }
1319    }
1320
1321    fn add_nogood(
1322        &mut self,
1323        nogood: Vec<Predicate>,
1324        inference_code: InferenceCode,
1325    ) -> Result<(), ConstraintOperationError> {
1326        pumpkin_assert_eq_simple!(self.get_decision_level(), 0);
1327        let num_trail_entries = self.assignments.num_trail_entries();
1328
1329        let mut propagation_context = PropagationContextMut::new(
1330            &mut self.trailed_values,
1331            &mut self.assignments,
1332            &mut self.reason_store,
1333            &mut self.semantic_minimiser,
1334            &mut self.notification_engine,
1335            Self::get_nogood_propagator_id(),
1336        );
1337        let nogood_propagator_id = Self::get_nogood_propagator_id();
1338
1339        let addition_result = ConstraintSatisfactionSolver::add_nogood_to_nogood_propagator(
1340            &mut self.propagators[nogood_propagator_id],
1341            nogood,
1342            inference_code,
1343            &mut propagation_context,
1344        );
1345
1346        if addition_result.is_err() || self.state.is_conflicting() {
1347            self.prepare_for_conflict_resolution();
1348            self.handle_root_propagation(num_trail_entries);
1349            self.complete_proof();
1350            return Err(ConstraintOperationError::InfeasibleNogood);
1351        }
1352
1353        self.handle_root_propagation(num_trail_entries);
1354
1355        // temporary hack for the nogood propagator that does propagation from scratch
1356        self.propagator_queue.enqueue_propagator(PropagatorId(0), 0);
1357        self.propagate();
1358
1359        self.handle_root_propagation(num_trail_entries);
1360
1361        if self.state.is_infeasible() {
1362            self.prepare_for_conflict_resolution();
1363            self.complete_proof();
1364            Err(ConstraintOperationError::InfeasibleState)
1365        } else {
1366            Ok(())
1367        }
1368    }
1369
1370    fn prepare_for_conflict_resolution(&mut self) {
1371        if self.state.is_conflicting() {
1372            return;
1373        }
1374
1375        let empty_domain_reason = self.compute_reason_for_empty_domain();
1376
1377        // TODO: As a temporary solution, we remove the last trail element.
1378        // This way we guarantee that the assignment is consistent, which is needed
1379        // for the conflict analysis data structures. The proper alternative would
1380        // be to forbid the assignments from getting into an inconsistent state.
1381        self.assignments.remove_last_trail_element();
1382
1383        let stored_conflict_info = StoredConflictInfo::EmptyDomain {
1384            conflict_nogood: empty_domain_reason,
1385        };
1386        self.state.declare_conflict(stored_conflict_info);
1387    }
1388
1389    fn add_nogood_to_nogood_propagator(
1390        nogood_propagator: &mut dyn Propagator,
1391        nogood: Vec<Predicate>,
1392        inference_code: InferenceCode,
1393        context: &mut PropagationContextMut,
1394    ) -> PropagationStatusCP {
1395        match nogood_propagator.downcast_mut::<NogoodPropagator>() {
1396            Some(nogood_propagator) => {
1397                nogood_propagator.add_nogood(nogood, inference_code, context)
1398            }
1399            None => {
1400                panic!("Provided propagator should be the nogood propagator",)
1401            }
1402        }
1403    }
1404
1405    /// Creates a clause from `literals` and adds it to the current formula.
1406    ///
1407    /// If the formula becomes trivially unsatisfiable, a [`ConstraintOperationError`] will be
1408    /// returned. Subsequent calls to this m\Zethod will always return an error, and no
1409    /// modification of the solver will take place.
1410    pub fn add_clause(
1411        &mut self,
1412        predicates: impl IntoIterator<Item = Predicate>,
1413        constraint_tag: ConstraintTag,
1414    ) -> Result<(), ConstraintOperationError> {
1415        pumpkin_assert_simple!(
1416            self.get_decision_level() == 0,
1417            "Clauses can only be added in the root"
1418        );
1419
1420        if self.state.is_inconsistent() {
1421            return Err(ConstraintOperationError::InfeasiblePropagator);
1422        }
1423
1424        // We can simply negate the clause and retrieve a nogood, e.g. if we have the
1425        // clause `[x1 >= 5] \/ [x2 != 3] \/ [x3 <= 5]`, then it **cannot** be the case that `[x1 <
1426        // 5] /\ [x2 = 3] /\ [x3 > 5]`
1427
1428        let mut are_all_falsified_at_root = true;
1429        let predicates = predicates
1430            .into_iter()
1431            .map(|predicate| {
1432                are_all_falsified_at_root &= self.assignments.is_predicate_falsified(predicate);
1433                !predicate
1434            })
1435            .collect::<Vec<_>>();
1436
1437        if predicates.is_empty() {
1438            // This breaks the proof. If it occurs, we should fix up the proof logging.
1439            // The main issue is that nogoods are not tagged. In the proof that is problematic.
1440            self.state
1441                .declare_conflict(StoredConflictInfo::RootLevelConflict(
1442                    ConstraintOperationError::InfeasibleClause,
1443                ));
1444            return Err(ConstraintOperationError::InfeasibleClause);
1445        }
1446
1447        if are_all_falsified_at_root {
1448            finalize_proof(FinalizingContext {
1449                conflict: predicates.into(),
1450                propagators: &mut self.propagators,
1451                proof_log: &mut self.internal_parameters.proof_log,
1452                unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
1453                assignments: &self.assignments,
1454                reason_store: &mut self.reason_store,
1455                notification_engine: &mut self.notification_engine,
1456                variable_names: &self.variable_names,
1457            });
1458            self.state
1459                .declare_conflict(StoredConflictInfo::RootLevelConflict(
1460                    ConstraintOperationError::InfeasibleClause,
1461                ));
1462            return Err(ConstraintOperationError::InfeasibleClause);
1463        }
1464
1465        let inference_code = self
1466            .internal_parameters
1467            .proof_log
1468            .create_inference_code(constraint_tag, NogoodLabel);
1469        if let Err(constraint_operation_error) = self.add_nogood(predicates, inference_code) {
1470            let _ = self.conclude_proof_unsat();
1471
1472            self.state
1473                .declare_conflict(StoredConflictInfo::RootLevelConflict(
1474                    constraint_operation_error,
1475                ));
1476            return Err(constraint_operation_error);
1477        }
1478        Ok(())
1479    }
1480
1481    pub(crate) fn get_decision_level(&self) -> usize {
1482        self.assignments.get_decision_level()
1483    }
1484}
1485
1486#[derive(Default, Debug, PartialEq, Eq)]
1487enum CSPSolverStateInternal {
1488    #[default]
1489    Ready,
1490    Solving,
1491    ContainsSolution,
1492    Conflict {
1493        conflict_info: StoredConflictInfo,
1494    },
1495    Infeasible,
1496    InfeasibleUnderAssumptions {
1497        violated_assumption: Predicate,
1498    },
1499    Timeout,
1500}
1501
1502#[derive(Default, Debug)]
1503pub struct CSPSolverState {
1504    internal_state: CSPSolverStateInternal,
1505}
1506
1507impl CSPSolverState {
1508    pub fn is_ready(&self) -> bool {
1509        matches!(self.internal_state, CSPSolverStateInternal::Ready)
1510    }
1511
1512    pub fn no_conflict(&self) -> bool {
1513        !self.is_conflicting()
1514    }
1515
1516    pub fn is_conflicting(&self) -> bool {
1517        matches!(
1518            self.internal_state,
1519            CSPSolverStateInternal::Conflict { conflict_info: _ }
1520        )
1521    }
1522
1523    pub fn is_infeasible(&self) -> bool {
1524        matches!(self.internal_state, CSPSolverStateInternal::Infeasible)
1525    }
1526
1527    /// Determines whether the current state is inconsistent; i.e. whether it is conflicting,
1528    /// infeasible or infeasible under assumptions
1529    pub fn is_inconsistent(&self) -> bool {
1530        self.is_conflicting() || self.is_infeasible() || self.is_infeasible_under_assumptions()
1531    }
1532
1533    pub fn is_infeasible_under_assumptions(&self) -> bool {
1534        matches!(
1535            self.internal_state,
1536            CSPSolverStateInternal::InfeasibleUnderAssumptions {
1537                violated_assumption: _
1538            }
1539        )
1540    }
1541
1542    pub fn get_violated_assumption(&self) -> Predicate {
1543        if let CSPSolverStateInternal::InfeasibleUnderAssumptions {
1544            violated_assumption,
1545        } = self.internal_state
1546        {
1547            violated_assumption
1548        } else {
1549            panic!(
1550                "Cannot extract violated assumption without getting the solver into the infeasible
1551                 under assumptions state."
1552            );
1553        }
1554    }
1555
1556    pub(crate) fn get_conflict_info(&self) -> StoredConflictInfo {
1557        match &self.internal_state {
1558            CSPSolverStateInternal::Conflict { conflict_info } => conflict_info.clone(),
1559            CSPSolverStateInternal::InfeasibleUnderAssumptions {
1560                violated_assumption,
1561            } => StoredConflictInfo::EmptyDomain {
1562                conflict_nogood: vec![*violated_assumption, !(*violated_assumption)].into(),
1563            },
1564            _ => {
1565                panic!("Cannot extract conflict clause if solver is not in a conflict.");
1566            }
1567        }
1568    }
1569
1570    pub fn timeout(&self) -> bool {
1571        matches!(self.internal_state, CSPSolverStateInternal::Timeout)
1572    }
1573
1574    pub fn has_solution(&self) -> bool {
1575        matches!(
1576            self.internal_state,
1577            CSPSolverStateInternal::ContainsSolution
1578        )
1579    }
1580
1581    pub(crate) fn declare_ready(&mut self) {
1582        self.internal_state = CSPSolverStateInternal::Ready;
1583    }
1584
1585    pub fn declare_solving(&mut self) {
1586        pumpkin_assert_simple!((self.is_ready() || self.is_conflicting()) && !self.is_infeasible());
1587        self.internal_state = CSPSolverStateInternal::Solving;
1588    }
1589
1590    fn declare_infeasible(&mut self) {
1591        self.internal_state = CSPSolverStateInternal::Infeasible;
1592    }
1593
1594    fn declare_conflict(&mut self, conflict_info: StoredConflictInfo) {
1595        self.internal_state = CSPSolverStateInternal::Conflict { conflict_info };
1596    }
1597
1598    fn declare_solution_found(&mut self) {
1599        pumpkin_assert_simple!(!self.is_infeasible());
1600        self.internal_state = CSPSolverStateInternal::ContainsSolution;
1601    }
1602
1603    fn declare_timeout(&mut self) {
1604        pumpkin_assert_simple!(!self.is_infeasible());
1605        self.internal_state = CSPSolverStateInternal::Timeout;
1606    }
1607
1608    fn declare_infeasible_under_assumptions(&mut self, violated_assumption: Predicate) {
1609        pumpkin_assert_simple!(!self.is_infeasible());
1610        self.internal_state = CSPSolverStateInternal::InfeasibleUnderAssumptions {
1611            violated_assumption,
1612        }
1613    }
1614}
1615
1616declare_inference_label!(NogoodLabel, "nogood");
1617
1618#[cfg(test)]
1619mod tests {
1620    use super::ConstraintSatisfactionSolver;
1621    use super::CoreExtractionResult;
1622    use crate::basic_types::CSPSolverExecutionFlag;
1623    use crate::predicate;
1624    use crate::predicates::Predicate;
1625    use crate::propagators::linear_not_equal::LinearNotEqualPropagatorArgs;
1626    use crate::termination::Indefinite;
1627    use crate::variables::TransformableVariable;
1628    use crate::DefaultBrancher;
1629
1630    fn is_same_core(core1: &[Predicate], core2: &[Predicate]) -> bool {
1631        core1.len() == core2.len() && core2.iter().all(|lit| core1.contains(lit))
1632    }
1633
1634    fn is_result_the_same(res1: &CoreExtractionResult, res2: &CoreExtractionResult) -> bool {
1635        match (res1, res2) {
1636            (
1637                CoreExtractionResult::ConflictingAssumption(assumption1),
1638                CoreExtractionResult::ConflictingAssumption(assumption2),
1639            ) => assumption1 == assumption2,
1640            (CoreExtractionResult::Core(core1), CoreExtractionResult::Core(core2)) => {
1641                is_same_core(core1, core2)
1642            }
1643            _ => false,
1644        }
1645    }
1646
1647    fn run_test(
1648        mut solver: ConstraintSatisfactionSolver,
1649        assumptions: Vec<Predicate>,
1650        expected_flag: CSPSolverExecutionFlag,
1651        expected_result: CoreExtractionResult,
1652    ) {
1653        let mut brancher = DefaultBrancher::default_over_all_variables(&solver.assignments);
1654        let flag = solver.solve_under_assumptions(&assumptions, &mut Indefinite, &mut brancher);
1655        assert_eq!(flag, expected_flag, "The flags do not match.");
1656
1657        if matches!(flag, CSPSolverExecutionFlag::Infeasible) {
1658            assert!(
1659                is_result_the_same(
1660                    &solver.extract_clausal_core(&mut brancher),
1661                    &expected_result
1662                ),
1663                "The result is not the same"
1664            );
1665        }
1666    }
1667
1668    fn create_instance1() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1669        let mut solver = ConstraintSatisfactionSolver::default();
1670        let constraint_tag = solver.new_constraint_tag();
1671        let lit1 = solver.create_new_literal(None).get_true_predicate();
1672        let lit2 = solver.create_new_literal(None).get_true_predicate();
1673
1674        let _ = solver.add_clause([lit1, lit2], constraint_tag);
1675        let _ = solver.add_clause([lit1, !lit2], constraint_tag);
1676        let _ = solver.add_clause([!lit1, lit2], constraint_tag);
1677        (solver, vec![lit1, lit2])
1678    }
1679
1680    #[test]
1681    fn core_extraction_unit_core() {
1682        let mut solver = ConstraintSatisfactionSolver::default();
1683        let constraint_tag = solver.new_constraint_tag();
1684        let lit1 = solver.create_new_literal(None).get_true_predicate();
1685        let _ = solver.add_clause(vec![lit1], constraint_tag);
1686
1687        run_test(
1688            solver,
1689            vec![!lit1],
1690            CSPSolverExecutionFlag::Infeasible,
1691            CoreExtractionResult::Core(vec![!lit1]),
1692        )
1693    }
1694
1695    #[test]
1696    fn simple_core_extraction_1_1() {
1697        let (solver, lits) = create_instance1();
1698        run_test(
1699            solver,
1700            vec![!lits[0], !lits[1]],
1701            CSPSolverExecutionFlag::Infeasible,
1702            CoreExtractionResult::Core(vec![!lits[0]]),
1703        )
1704    }
1705
1706    #[test]
1707    fn simple_core_extraction_1_2() {
1708        let (solver, lits) = create_instance1();
1709        run_test(
1710            solver,
1711            vec![!lits[1], !lits[0]],
1712            CSPSolverExecutionFlag::Infeasible,
1713            CoreExtractionResult::Core(vec![!lits[1]]),
1714        );
1715    }
1716
1717    #[test]
1718    fn simple_core_extraction_1_infeasible() {
1719        let (mut solver, lits) = create_instance1();
1720        let constraint_tag = solver.new_constraint_tag();
1721        let _ = solver.add_clause([!lits[0], !lits[1]], constraint_tag);
1722        run_test(
1723            solver,
1724            vec![!lits[1], !lits[0]],
1725            CSPSolverExecutionFlag::Infeasible,
1726            CoreExtractionResult::Core(vec![]),
1727        );
1728    }
1729
1730    #[test]
1731    fn simple_core_extraction_1_core_conflicting() {
1732        let (solver, lits) = create_instance1();
1733        run_test(
1734            solver,
1735            vec![!lits[1], lits[1]],
1736            CSPSolverExecutionFlag::Infeasible,
1737            CoreExtractionResult::ConflictingAssumption(!lits[1]),
1738        );
1739    }
1740    fn create_instance2() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1741        let mut solver = ConstraintSatisfactionSolver::default();
1742        let constraint_tag = solver.new_constraint_tag();
1743        let lit1 = solver.create_new_literal(None).get_true_predicate();
1744        let lit2 = solver.create_new_literal(None).get_true_predicate();
1745        let lit3 = solver.create_new_literal(None).get_true_predicate();
1746
1747        let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1748        let _ = solver.add_clause([lit1, !lit2, lit3], constraint_tag);
1749        (solver, vec![lit1, lit2, lit3])
1750    }
1751
1752    #[test]
1753    fn simple_core_extraction_2_1() {
1754        let (solver, lits) = create_instance2();
1755        run_test(
1756            solver,
1757            vec![!lits[0], lits[1], !lits[2]],
1758            CSPSolverExecutionFlag::Infeasible,
1759            CoreExtractionResult::Core(vec![!lits[0], lits[1], !lits[2]]),
1760        );
1761    }
1762
1763    #[test]
1764    fn simple_core_extraction_2_long_assumptions_with_inconsistency_at_the_end() {
1765        let (solver, lits) = create_instance2();
1766        run_test(
1767            solver,
1768            vec![!lits[0], lits[1], !lits[2], lits[0]],
1769            CSPSolverExecutionFlag::Infeasible,
1770            CoreExtractionResult::ConflictingAssumption(!lits[0]),
1771        );
1772    }
1773
1774    #[test]
1775    fn simple_core_extraction_2_inconsistent_long_assumptions() {
1776        let (solver, lits) = create_instance2();
1777        run_test(
1778            solver,
1779            vec![!lits[0], !lits[0], !lits[1], !lits[1], lits[0]],
1780            CSPSolverExecutionFlag::Infeasible,
1781            CoreExtractionResult::ConflictingAssumption(!lits[0]),
1782        );
1783    }
1784    fn create_instance3() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1785        let mut solver = ConstraintSatisfactionSolver::default();
1786        let constraint_tag = solver.new_constraint_tag();
1787
1788        let lit1 = solver.create_new_literal(None).get_true_predicate();
1789        let lit2 = solver.create_new_literal(None).get_true_predicate();
1790        let lit3 = solver.create_new_literal(None).get_true_predicate();
1791
1792        let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1793        (solver, vec![lit1, lit2, lit3])
1794    }
1795
1796    #[test]
1797    fn simple_core_extraction_3_1() {
1798        let (solver, lits) = create_instance3();
1799        run_test(
1800            solver,
1801            vec![!lits[0], !lits[1], !lits[2]],
1802            CSPSolverExecutionFlag::Infeasible,
1803            CoreExtractionResult::Core(vec![!lits[0], !lits[1], !lits[2]]),
1804        );
1805    }
1806
1807    #[test]
1808    fn simple_core_extraction_3_2() {
1809        let (solver, lits) = create_instance3();
1810        run_test(
1811            solver,
1812            vec![!lits[0], !lits[1]],
1813            CSPSolverExecutionFlag::Feasible,
1814            CoreExtractionResult::Core(vec![]), // will be ignored in the test
1815        );
1816    }
1817
1818    #[test]
1819    fn core_extraction_equality_assumption() {
1820        let mut solver = ConstraintSatisfactionSolver::default();
1821
1822        let x = solver.create_new_integer_variable(0, 10, None);
1823        let y = solver.create_new_integer_variable(0, 10, None);
1824        let z = solver.create_new_integer_variable(0, 10, None);
1825
1826        let constraint_tag = solver.new_constraint_tag();
1827
1828        let result = solver.add_propagator(LinearNotEqualPropagatorArgs {
1829            terms: [x.scaled(1), y.scaled(-1)].into(),
1830            rhs: 0,
1831            constraint_tag,
1832        });
1833        assert!(result.is_ok());
1834        run_test(
1835            solver,
1836            vec![
1837                predicate!(x >= 5),
1838                predicate!(z != 10),
1839                predicate!(y == 5),
1840                predicate!(x <= 5),
1841            ],
1842            CSPSolverExecutionFlag::Infeasible,
1843            CoreExtractionResult::Core(vec![predicate!(x == 5), predicate!(y == 5)]),
1844        )
1845    }
1846
1847    #[test]
1848    fn new_domain_with_negative_lower_bound() {
1849        let lb = -2;
1850        let ub = 2;
1851
1852        let mut solver = ConstraintSatisfactionSolver::default();
1853        let domain_id = solver.create_new_integer_variable(lb, ub, None);
1854
1855        assert_eq!(lb, solver.assignments.get_lower_bound(domain_id));
1856
1857        assert_eq!(ub, solver.assignments.get_upper_bound(domain_id));
1858
1859        assert!(!solver
1860            .assignments
1861            .is_predicate_satisfied(predicate![domain_id == lb]));
1862
1863        for value in (lb + 1)..ub {
1864            let predicate = predicate![domain_id >= value];
1865
1866            assert!(!solver.assignments.is_predicate_satisfied(predicate));
1867
1868            assert!(!solver
1869                .assignments
1870                .is_predicate_satisfied(predicate![domain_id == value]));
1871        }
1872
1873        assert!(!solver
1874            .assignments
1875            .is_predicate_satisfied(predicate![domain_id == ub]));
1876    }
1877
1878    #[test]
1879    fn check_can_compute_1uip_with_propagator_initialisation_conflict() {
1880        let mut solver = ConstraintSatisfactionSolver::default();
1881
1882        let x = solver.create_new_integer_variable(1, 1, None);
1883        let y = solver.create_new_integer_variable(2, 2, None);
1884
1885        let constraint_tag = solver.new_constraint_tag();
1886
1887        let propagator = LinearNotEqualPropagatorArgs {
1888            terms: vec![x, y].into(),
1889            rhs: 3,
1890            constraint_tag,
1891        };
1892        let result = solver.add_propagator(propagator);
1893        assert!(result.is_err());
1894    }
1895}