Skip to main content

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::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/// A solver which attempts to find a solution to a Constraint Satisfaction Problem (CSP) using
63/// a Lazy Clause Generation (LCG [\[1\]](https://people.eng.unimelb.edu.au/pstuckey/papers/cp09-lc.pdf))
64/// approach.
65///
66/// It requires that all of the propagators which are added, are able to explain the
67/// propagations and conflicts they have made/found. It then uses standard SAT concepts such as
68/// 1UIP (see \[2\]) to learn clauses (also called nogoods in the CP field, see \[3\]) to avoid
69/// unnecessary exploration of the search space while utilizing the search procedure benefits from
70/// constraint programming (e.g. by preventing the exponential blow-up of problem encodings).
71///
72/// # Practical
73/// The [`ConstraintSatisfactionSolver`] makes use of certain options which allow the user to
74/// influence the behaviour of the solver; see for example the [`SatisfactionSolverOptions`].
75///
76/// The solver switches between making decisions using implementations of the [`Brancher`] (which
77/// are passed to the [`ConstraintSatisfactionSolver::solve`] method) and propagation (use
78/// [`ConstraintSatisfactionSolver::add_propagator`] to add a propagator). If a conflict is found by
79/// any of the propagators then the solver will analyse the conflict
80/// using 1UIP reasoning and backtrack if possible.
81///
82/// # Bibliography
83/// \[1\] T. Feydy and P. J. Stuckey, ‘Lazy clause generation reengineered’, in International
84/// Conference on Principles and Practice of Constraint Programming, 2009, pp. 352–366.
85///
86/// \[2\] J. Marques-Silva, I. Lynce, and S. Malik, ‘Conflict-driven clause learning SAT
87/// solvers’, in Handbook of satisfiability, IOS press, 2021
88///
89/// \[3\] F. Rossi, P. Van Beek, and T. Walsh, ‘Constraint programming’, Foundations of Artificial
90/// Intelligence, vol. 3, pp. 181–211, 2008.
91#[derive(Debug)]
92pub struct ConstraintSatisfactionSolver {
93    /// The solver continuously changes states during the search.
94    /// The state helps track additional information and contributes to making the code clearer.
95    pub(crate) solver_state: CSPSolverState,
96    pub(crate) state: State,
97    pub(crate) nogood_propagator_handle: PropagatorHandle<NogoodPropagator>,
98
99    /// Tracks information about the restarts. Occassionally the solver will undo all its decisions
100    /// and start the search from the root note. Note that learned clauses and other state
101    /// information is kept after a restart.
102    pub(crate) restart_strategy: RestartStrategy,
103    /// Holds the assumptions when the solver is queried to solve under assumptions.
104    assumptions: Vec<Predicate>,
105    /// A set of counters updated during the search.
106    solver_statistics: SolverStatistics,
107    /// Miscellaneous constant parameters used by the solver.
108    pub(crate) internal_parameters: SatisfactionSolverOptions,
109    /// A map from predicates that are propagated at the root to inference codes in the proof.
110    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/// The result of [`ConstraintSatisfactionSolver::extract_clausal_core`]; there are 2 cases:
120/// 1. In the case of [`CoreExtractionResult::ConflictingAssumption`], two assumptions have been
121///    given which directly conflict with one another; e.g. if the assumptions `[x, !x]` have been
122///    given then the result of [`ConstraintSatisfactionSolver::extract_clausal_core`] will be a
123///    [`CoreExtractionResult::ConflictingAssumption`] containing `x`.
124/// 2. The standard case is when a [`CoreExtractionResult::Core`] is returned which contains (a
125///    subset of) the assumptions which led to conflict.
126#[derive(Debug, Clone)]
127pub enum CoreExtractionResult {
128    /// Conflicting assumptions were provided; e.g. in the case of the assumptions `[x, !x]`, this
129    /// result will contain `!x`
130    ConflictingAssumption(Predicate),
131    /// The standard case where this result contains the core consisting of (a subset of) the
132    /// assumptions which led to conflict.
133    Core(Vec<Predicate>),
134}
135
136/// During search, the CP solver will inevitably evaluate partial assignments that violate at
137/// least one constraint. When this happens, conflict resolution is applied to restore the
138/// solver to a state from which it can continue the search.
139///
140/// The manner in which conflict resolution is done greatly impacts the performance of the
141/// solver.
142#[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/// Options for the [`Solver`] which determine how it behaves.
151#[derive(Debug)]
152pub struct SatisfactionSolverOptions {
153    /// The options used by the restart strategy.
154    pub restart_options: RestartOptions,
155    /// Whether learned clause minimisation should take place
156    pub should_minimise_nogoods: bool,
157    /// A random number generator which is used by the [`Solver`] to determine randomised values.
158    pub random_generator: SmallRng,
159    /// The proof log for the solver.
160    pub proof_log: ProofLog,
161    /// The options which influence the learning of the solver.
162    pub learning_options: LearningOptions,
163    /// The number of MBs which are preallocated by the nogood propagator.
164    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    /// This is a temporary accessor to help refactoring.
186    pub fn get_solution_reference(&self) -> SolutionReference<'_> {
187        self.state.get_solution_reference()
188    }
189
190    /// Conclude the proof with the unsatisfiable claim.
191    ///
192    /// This method will finish the proof. Any new operation will not be logged to the proof.
193    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    /// Conclude the proof with the optimality claim.
199    ///
200    /// This method will finish the proof. Any new operation will not be logged to the proof.
201    pub fn conclude_proof_optimal(&mut self, bound: Predicate) -> std::io::Result<()> {
202        let proof = std::mem::take(&mut self.internal_parameters.proof_log);
203        proof.optimal(bound, self.state.variable_names())
204    }
205
206    fn complete_proof(&mut self) {
207        struct DummyBrancher;
208
209        impl Brancher for DummyBrancher {
210            fn next_decision(&mut self, _: &mut SelectionContext) -> Option<Predicate> {
211                unreachable!()
212            }
213
214            fn subscribe_to_events(&self) -> Vec<crate::branching::BrancherEvent> {
215                unreachable!()
216            }
217        }
218
219        let mut conflict_analysis_context = ConflictAnalysisContext {
220            solver_state: &mut self.solver_state,
221            brancher: &mut DummyBrancher,
222            proof_log: &mut self.internal_parameters.proof_log,
223            unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
224            restart_strategy: &mut self.restart_strategy,
225
226            state: &mut self.state,
227            nogood_propagator_handle: self.nogood_propagator_handle,
228
229            rng: &mut self.internal_parameters.random_generator,
230        };
231
232        let conflict = conflict_analysis_context.get_conflict_nogood();
233
234        let context = FinalizingContext {
235            conflict: conflict.into(),
236            proof_log: &mut self.internal_parameters.proof_log,
237            unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
238            state: &mut self.state,
239        };
240
241        finalize_proof(context);
242    }
243
244    pub(crate) fn is_logging_proof(&self) -> bool {
245        self.internal_parameters.proof_log.is_logging_proof()
246    }
247}
248
249// methods that offer basic functionality
250impl ConstraintSatisfactionSolver {
251    pub fn new(solver_options: SatisfactionSolverOptions) -> Self {
252        let mut state = State::default();
253        let handle = state.add_propagator(NogoodPropagatorConstructor::new(
254            (solver_options.memory_preallocated * 1_000_000) / size_of::<PredicateId>(),
255            solver_options.learning_options,
256        ));
257
258        ConstraintSatisfactionSolver {
259            solver_state: CSPSolverState::default(),
260            assumptions: Vec::default(),
261            restart_strategy: RestartStrategy::new(solver_options.restart_options),
262            nogood_propagator_handle: handle,
263            solver_statistics: SolverStatistics::default(),
264            unit_nogood_inference_codes: Default::default(),
265            internal_parameters: solver_options,
266            state,
267        }
268    }
269
270    pub fn solve(
271        &mut self,
272        termination: &mut impl TerminationCondition,
273        brancher: &mut impl Brancher,
274        resolver: &mut impl ConflictResolver,
275    ) -> CSPSolverExecutionFlag {
276        let dummy_assumptions: Vec<Predicate> = vec![];
277        self.solve_under_assumptions(&dummy_assumptions, termination, brancher, resolver)
278    }
279
280    pub fn solve_under_assumptions(
281        &mut self,
282        assumptions: &[Predicate],
283        termination: &mut impl TerminationCondition,
284        brancher: &mut impl Brancher,
285        resolver: &mut impl ConflictResolver,
286    ) -> CSPSolverExecutionFlag {
287        if self.solver_state.is_inconsistent() {
288            return CSPSolverExecutionFlag::Infeasible;
289        }
290
291        let start_time = Instant::now();
292
293        self.initialise(assumptions);
294        let result = self.solve_internal(termination, brancher, resolver);
295
296        self.solver_statistics
297            .engine_statistics
298            .time_spent_in_solver += start_time.elapsed();
299
300        result
301    }
302
303    pub fn get_state(&self) -> &CSPSolverState {
304        &self.solver_state
305    }
306
307    pub fn get_random_generator(&mut self) -> &mut impl Random {
308        &mut self.internal_parameters.random_generator
309    }
310
311    pub fn log_statistics(&self, verbose: bool) {
312        // We first check whether the statistics will/should be logged to prevent unnecessarily
313        // going through all the propagators
314        if !should_log_statistics() {
315            return;
316        }
317
318        self.solver_statistics
319            .log(StatisticLogger::default(), verbose);
320        self.state.log_statistics(verbose);
321    }
322
323    /// Create a new [`ConstraintTag`].
324    pub fn new_constraint_tag(&mut self) -> ConstraintTag {
325        self.state.new_constraint_tag()
326    }
327
328    pub fn create_new_literal(&mut self, name: Option<Arc<str>>) -> Literal {
329        self.state.new_literal(name)
330    }
331
332    pub fn create_new_literal_for_predicate(
333        &mut self,
334        predicate: Predicate,
335        name: Option<Arc<str>>,
336        constraint_tag: ConstraintTag,
337    ) -> Literal {
338        let literal = self.state.new_literal(name);
339
340        self.internal_parameters
341            .proof_log
342            .reify_predicate(literal, predicate);
343
344        // If literal --> predicate
345        let _ = self.add_clause(
346            vec![!literal.get_true_predicate(), predicate],
347            constraint_tag,
348        );
349
350        // If !literal --> !predicate
351        let _ = self.add_clause(
352            vec![!literal.get_false_predicate(), !predicate],
353            constraint_tag,
354        );
355
356        literal
357    }
358
359    /// Create a new integer variable. Its domain will have the given lower and upper bounds.
360    pub fn create_new_integer_variable(
361        &mut self,
362        lower_bound: i32,
363        upper_bound: i32,
364        name: Option<Arc<str>>,
365    ) -> DomainId {
366        assert!(
367            !self.solver_state.is_inconsistent(),
368            "Variables cannot be created in an inconsistent state"
369        );
370
371        self.state
372            .new_interval_variable(lower_bound, upper_bound, name)
373    }
374
375    /// Creates an integer variable with a domain containing only the values in `values`
376    pub fn create_new_integer_variable_sparse(
377        &mut self,
378        values: Vec<i32>,
379        name: Option<String>,
380    ) -> DomainId {
381        self.state.new_sparse_variable(values, name)
382    }
383
384    /// Returns an unsatisfiable core or an [`Err`] if the provided assumptions were conflicting
385    /// with one another ([`Err`] then contain the [`Literal`] which was conflicting).
386    ///
387    /// We define an unsatisfiable core as a clause containing only negated assumption literals,
388    /// which is implied by the formula. Alternatively, it is the negation of a conjunction of
389    /// assumptions which cannot be satisfied together with the rest of the formula. The clause is
390    /// not necessarily unique or minimal.
391    ///
392    /// The unsatisfiable core can be verified with reverse unit propagation (RUP).
393    ///
394    /// *Notes:*
395    ///   - If the solver is not in an unsatisfied state, this method will panic.
396    ///   - If the solver is in an unsatisfied state, but solving was done without assumptions, this
397    ///     will return an empty vector.
398    ///   - If the assumptions are inconsistent, i.e. both literal x and !x are assumed, an error is
399    ///     returned, with the literal being one of the inconsistent assumptions.
400    pub fn extract_clausal_core(&mut self, brancher: &mut impl Brancher) -> CoreExtractionResult {
401        if self.solver_state.is_infeasible() {
402            return CoreExtractionResult::Core(vec![]);
403        }
404
405        self.assumptions
406            .iter()
407            .enumerate()
408            .find(|(index, assumption)| {
409                self.assumptions
410                    .iter()
411                    .skip(index + 1)
412                    .any(|other_assumption| {
413                        assumption.is_mutually_exclusive_with(*other_assumption)
414                    })
415            })
416            .map(|(_, conflicting_assumption)| {
417                CoreExtractionResult::ConflictingAssumption(*conflicting_assumption)
418            })
419            .unwrap_or_else(|| {
420                let mut context = ConflictAnalysisContext {
421                    solver_state: &mut self.solver_state,
422                    brancher,
423                    proof_log: &mut self.internal_parameters.proof_log,
424                    unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
425                    restart_strategy: &mut self.restart_strategy,
426                    state: &mut self.state,
427                    nogood_propagator_handle: self.nogood_propagator_handle,
428
429                    rng: &mut self.internal_parameters.random_generator,
430                };
431                let mut predicates = context.get_conflict_nogood();
432                let mut core: HashSet<Predicate> = HashSet::default();
433
434                while let Some(predicate) = predicates.pop() {
435                    if context.state.assignments.is_decision_predicate(&predicate) {
436                        let _ = core.insert(predicate);
437                        continue;
438                    }
439
440                    ConflictAnalysisContext::get_propagation_reason_inner(
441                        predicate,
442                        CurrentNogood::empty(),
443                        context.proof_log,
444                        context.unit_nogood_inference_codes,
445                        &mut predicates,
446                        context.state,
447                    );
448                }
449
450                CoreExtractionResult::Core(core.into_iter().collect())
451            })
452    }
453
454    pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
455        self.state.get_literal_value(literal)
456    }
457
458    /// Get the lower bound for the given variable.
459    pub fn get_lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
460        self.state.lower_bound(variable.clone())
461    }
462
463    /// Get the upper bound for the given variable.
464    pub fn get_upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
465        self.state.upper_bound(variable.clone())
466    }
467
468    /// Determine whether `value` is in the domain of `variable`.
469    pub fn integer_variable_contains(&self, variable: &impl IntegerVariable, value: i32) -> bool {
470        self.state.contains(variable.clone(), value)
471    }
472
473    /// Get the assigned integer for the given variable. If it is not assigned, `None` is returned.
474    pub fn get_assigned_integer_value(&self, variable: &impl IntegerVariable) -> Option<i32> {
475        self.state.fixed_value(variable.clone())
476    }
477
478    pub fn restore_state_at_root(&mut self, brancher: &mut impl Brancher) {
479        if self.state.get_checkpoint() != 0 {
480            ConstraintSatisfactionSolver::backtrack(
481                &mut self.state,
482                0,
483                brancher,
484                &mut self.internal_parameters.random_generator,
485            );
486            self.solver_state.declare_ready();
487        } else if self.solver_state.internal_state == CSPSolverStateInternal::ContainsSolution {
488            self.solver_state.declare_ready();
489        }
490    }
491}
492
493// methods that serve as the main building blocks
494impl ConstraintSatisfactionSolver {
495    fn initialise(&mut self, assumptions: &[Predicate]) {
496        pumpkin_assert_simple!(
497            !self.solver_state.is_infeasible_under_assumptions(),
498            "Solver is not expected to be in the infeasible under assumptions state when initialising.
499             Missed extracting the core?"
500        );
501        self.solver_state.declare_solving();
502        assumptions.clone_into(&mut self.assumptions);
503    }
504
505    fn solve_internal(
506        &mut self,
507        termination: &mut impl TerminationCondition,
508        brancher: &mut impl Brancher,
509        resolver: &mut impl ConflictResolver,
510    ) -> CSPSolverExecutionFlag {
511        loop {
512            if termination.should_stop() {
513                self.solver_state.declare_timeout();
514                return CSPSolverExecutionFlag::Timeout;
515            }
516
517            self.propagate();
518
519            if self.solver_state.no_conflict() {
520                // Restarts should only occur after a new decision level has been declared to
521                // account for the fact that all assumptions should be assigned when restarts take
522                // place. Since one assumption is posted per decision level, all assumptions are
523                // assigned when the decision level is strictly larger than the number of
524                // assumptions.
525                if self.get_checkpoint() > self.assumptions.len()
526                    && self.restart_strategy.should_restart()
527                {
528                    self.restart_during_search(brancher);
529                }
530
531                let branching_result = self.make_next_decision(brancher);
532
533                self.solver_statistics.engine_statistics.peak_depth = max(
534                    self.solver_statistics.engine_statistics.peak_depth,
535                    self.state.get_checkpoint() as u64,
536                );
537
538                match branching_result {
539                    Err(CSPSolverExecutionFlag::Infeasible) => {
540                        // Can happen when the branching decision was an assumption
541                        // that is inconsistent with the current assignment. We do not
542                        // have to declare a new state, as it will be done inside the
543                        // `make_next_decision` function.
544                        pumpkin_assert_simple!(self.solver_state.is_infeasible_under_assumptions());
545
546                        self.complete_proof();
547                        return CSPSolverExecutionFlag::Infeasible;
548                    }
549
550                    Err(flag) => return flag,
551                    Ok(()) => {}
552                }
553            } else {
554                if self.get_checkpoint() == 0 {
555                    self.complete_proof();
556                    self.solver_state.declare_infeasible();
557
558                    return CSPSolverExecutionFlag::Infeasible;
559                }
560
561                self.resolve_conflict(brancher, resolver);
562
563                brancher.on_conflict();
564                self.decay_nogood_activities();
565            }
566        }
567    }
568
569    fn decay_nogood_activities(&mut self) {
570        match self.state.get_propagator_mut(self.nogood_propagator_handle) {
571            Some(nogood_propagator) => {
572                nogood_propagator.decay_nogood_activities();
573            }
574            None => panic!("Provided propagator should be the nogood propagator"),
575        }
576    }
577
578    fn make_next_decision(
579        &mut self,
580        brancher: &mut impl Brancher,
581    ) -> Result<(), CSPSolverExecutionFlag> {
582        // Set the next decision to be an assumption, if there are assumptions left.
583        // Currently assumptions are implemented by adding an assumption predicate
584        // at separate decision levels.
585        if let Some(assumption_literal) = self.peek_next_assumption_predicate() {
586            self.new_checkpoint();
587
588            let _ = self.state.post(assumption_literal).map_err(|_| {
589                self.solver_state
590                    .declare_infeasible_under_assumptions(assumption_literal);
591                CSPSolverExecutionFlag::Infeasible
592            })?;
593
594            return Ok(());
595        }
596
597        // Otherwise proceed with standard branching.
598        let context = &mut SelectionContext::new(
599            &self.state.assignments,
600            &mut self.internal_parameters.random_generator,
601        );
602
603        // If there is a next decision, make the decision.
604        let Some(decision_predicate) = brancher.next_decision(context) else {
605            // Otherwise there are no more decisions to be made,
606            // all predicates have been applied without a conflict,
607            // meaning the problem is feasible.
608            self.solver_state.declare_solution_found();
609            return Err(CSPSolverExecutionFlag::Feasible);
610        };
611
612        self.new_checkpoint();
613
614        // Note: This also checks that the decision predicate is not already true. That is a
615        // stronger check than the `.expect(...)` used later on when handling the result of
616        // `Assignments::post_predicate`.
617        pumpkin_assert_ne_moderate!(
618            self.state.truth_value(decision_predicate),
619            Some(true),
620            "Decision should not already be assigned; double check the brancher"
621        );
622
623        self.solver_statistics.engine_statistics.num_decisions += 1;
624        let update_occurred = self
625            .state
626            .post(decision_predicate)
627            .expect("Decisions are expected not to fail.");
628        pumpkin_assert_simple!(update_occurred);
629
630        Ok(())
631    }
632
633    pub(crate) fn new_checkpoint(&mut self) {
634        self.state.new_checkpoint();
635    }
636
637    /// Changes the state based on the conflict analysis. It performs the following:
638    /// - Derives a nogood using our CP version of the 1UIP scheme.
639    /// - Adds the learned nogood to the database.
640    /// - Performs backtracking.
641    /// - Enqueues the propagated [`Predicate`] of the learned nogood.
642    /// - Todo: Updates the internal data structures (e.g. for the restart strategy or the learned
643    ///   clause manager)
644    ///
645    /// # Note
646    /// This method performs no propagation, this is left up to the solver afterwards.
647    fn resolve_conflict(
648        &mut self,
649        brancher: &mut impl Brancher,
650        resolver: &mut impl ConflictResolver,
651    ) {
652        pumpkin_assert_moderate!(self.solver_state.is_conflicting());
653
654        let mut conflict_analysis_context = ConflictAnalysisContext {
655            solver_state: &mut self.solver_state,
656            brancher,
657            proof_log: &mut self.internal_parameters.proof_log,
658            unit_nogood_inference_codes: &mut self.unit_nogood_inference_codes,
659            restart_strategy: &mut self.restart_strategy,
660            state: &mut self.state,
661            nogood_propagator_handle: self.nogood_propagator_handle,
662            rng: &mut self.internal_parameters.random_generator,
663        };
664
665        resolver.resolve_conflict(&mut conflict_analysis_context);
666
667        self.solver_state.declare_solving();
668    }
669
670    /// Performs a restart during the search process; it is only called when it has been determined
671    /// to be necessary by the [`ConstraintSatisfactionSolver::restart_strategy`]. A 'restart'
672    /// differs from backtracking to level zero in that a restart backtracks to decision level
673    /// zero and then performs additional operations, e.g., clean up learned clauses, adjust
674    /// restart frequency, etc.
675    ///
676    /// This method will also increase the decision level after backtracking.
677    ///
678    /// Returns true if a restart took place and false otherwise.
679    fn restart_during_search(&mut self, brancher: &mut impl Brancher) {
680        pumpkin_assert_simple!(
681            self.get_checkpoint() > self.assumptions.len(),
682            "Sanity check: restarts should not trigger whilst assigning assumptions"
683        );
684
685        // no point backtracking past the assumption level
686        if self.get_checkpoint() <= self.assumptions.len() {
687            return;
688        }
689
690        if brancher.is_restart_pointless() {
691            // If the brancher is static then there is no point in restarting as it would make the
692            // exact same decision
693            return;
694        }
695
696        self.solver_statistics.engine_statistics.num_restarts += 1;
697
698        ConstraintSatisfactionSolver::backtrack(
699            &mut self.state,
700            0,
701            brancher,
702            &mut self.internal_parameters.random_generator,
703        );
704
705        self.restart_strategy.notify_restart();
706    }
707
708    #[allow(
709        clippy::too_many_arguments,
710        reason = "This method requires this many arguments, though a backtracking context could be considered; for now this function needs to be used by conflict analysis"
711    )]
712    pub(crate) fn backtrack<BrancherType: Brancher + ?Sized>(
713        state: &mut State,
714        backtrack_level: usize,
715        brancher: &mut BrancherType,
716        rng: &mut dyn Random,
717    ) {
718        pumpkin_assert_simple!(backtrack_level < state.get_checkpoint());
719
720        brancher.on_backtrack();
721
722        state
723            .restore_to(backtrack_level)
724            .into_iter()
725            .for_each(|(domain_id, previous_value)| {
726                brancher.on_unassign_integer(domain_id, previous_value)
727            });
728
729        brancher.synchronise(&mut SelectionContext::new(&state.assignments, rng));
730    }
731
732    /// Main propagation loop.
733    pub(crate) fn propagate(&mut self) {
734        let num_trail_entries_prev = self.state.trail_len();
735
736        let result = self.state.propagate_to_fixed_point();
737
738        if self.state.get_checkpoint() == 0 {
739            self.handle_root_propagation(num_trail_entries_prev);
740        }
741
742        if let Err(conflict) = result {
743            self.solver_state.declare_conflict(conflict.into());
744        }
745    }
746
747    /// Introduces any root-level propagations to the proof by introducing them as
748    /// nogoods.
749    ///
750    /// The inference `R -> l` is logged to the proof as follows:
751    /// 1. Infernce `R /\ ~l -> false`
752    /// 2. Nogood (clause) `l`
753    fn handle_root_propagation(&mut self, start_trail_index: usize) {
754        pumpkin_assert_eq_simple!(self.get_checkpoint(), 0);
755
756        for trail_idx in start_trail_index..self.state.trail_len() {
757            let entry = self.state.trail_entry(trail_idx);
758            let (_, inference_code) = entry
759                .reason
760                .expect("Added by a propagator and must therefore have a reason");
761
762            if !self.internal_parameters.proof_log.is_logging_inferences() {
763                // In case we are not logging inferences, we only need to keep track
764                // of the root-level inferences to allow us to correctly finalize the
765                // proof.
766                let _ = self
767                    .unit_nogood_inference_codes
768                    .insert(entry.predicate, inference_code);
769                continue;
770            }
771
772            // Get the conjunction of predicates explaining the propagation.
773            let mut reason = vec![];
774            self.state
775                .get_propagation_reason_trail_entry(trail_idx, &mut reason);
776
777            let propagated = entry.predicate;
778
779            // The proof inference for the propagation `R -> l` is `R /\ ~l -> false`.
780            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            // Since inference steps are only related to the nogood they directly precede,
791            // facts derived at the root are also logged as nogoods so they can be used in the
792            // derivation of other nogoods.
793            //
794            // In case we are logging hints, we must therefore identify what proof steps contribute
795            // to the derivation of the current nogood, and therefore are in the premise of the
796            // previously logged inference. These proof steps are necessarily unit nogoods, and
797            // therefore we recursively look up which unit nogoods are involved in the premise of
798            // the inference.
799
800            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            // Log the nogood which adds the root-level knowledge to the proof.
819            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        // The convention is that at decision level i, the (i-1)th assumption is posted.
838        // Note that decisions start being posted start at 1, hence the minus one.
839        let next_assumption_index = self.get_checkpoint();
840        self.assumptions.get(next_assumption_index).copied()
841    }
842}
843
844/// Methods for adding constraints (propagators and clauses)
845impl ConstraintSatisfactionSolver {
846    /// See [`crate::Solver::add_propagator`] for documentation.
847    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    /// Creates a clause from `literals` and adds it to the current formula.
945    ///
946    /// If the formula becomes trivially unsatisfiable, a [`ConstraintOperationError`] will be
947    /// returned. Subsequent calls to this m\Zethod will always return an error, and no
948    /// modification of the solver will take place.
949    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        // We can simply negate the clause and retrieve a nogood, e.g. if we have the
964        // clause `[x1 >= 5] \/ [x2 != 3] \/ [x3 <= 5]`, then it **cannot** be the case that `[x1 <
965        // 5] /\ [x2 = 3] /\ [x3 > 5]`
966
967        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            // This breaks the proof. If it occurs, we should fix up the proof logging.
978            // The main issue is that nogoods are not tagged. In the proof that is problematic.
979            self.solver_state
980                .declare_conflict(StoredConflictInfo::RootLevelConflict(
981                    ConstraintOperationError::InfeasibleClause,
982                ));
983            return Err(ConstraintOperationError::InfeasibleClause);
984        }
985
986        if are_all_falsified_at_root {
987            finalize_proof(FinalizingContext {
988                conflict: predicates.into(),
989                proof_log: &mut self.internal_parameters.proof_log,
990                unit_nogood_inference_codes: &self.unit_nogood_inference_codes,
991                state: &mut self.state,
992            });
993            self.solver_state
994                .declare_conflict(StoredConflictInfo::RootLevelConflict(
995                    ConstraintOperationError::InfeasibleClause,
996                ));
997            return Err(ConstraintOperationError::InfeasibleClause);
998        }
999
1000        let inference_code = InferenceCode::new(constraint_tag, NogoodLabel);
1001        if let Err(constraint_operation_error) = self.add_nogood(predicates, inference_code) {
1002            let _ = self.conclude_proof_unsat();
1003
1004            self.solver_state
1005                .declare_conflict(StoredConflictInfo::RootLevelConflict(
1006                    constraint_operation_error,
1007                ));
1008            return Err(constraint_operation_error);
1009        }
1010        Ok(())
1011    }
1012
1013    pub(crate) fn get_checkpoint(&self) -> usize {
1014        self.state.get_checkpoint()
1015    }
1016}
1017
1018#[derive(Default, Debug, PartialEq, Eq)]
1019enum CSPSolverStateInternal {
1020    #[default]
1021    Ready,
1022    Solving,
1023    ContainsSolution,
1024    Conflict {
1025        conflict_info: StoredConflictInfo,
1026    },
1027    Infeasible,
1028    InfeasibleUnderAssumptions {
1029        violated_assumption: Predicate,
1030    },
1031    Timeout,
1032}
1033
1034#[derive(Default, Debug)]
1035pub struct CSPSolverState {
1036    internal_state: CSPSolverStateInternal,
1037}
1038
1039impl CSPSolverState {
1040    pub fn is_ready(&self) -> bool {
1041        matches!(self.internal_state, CSPSolverStateInternal::Ready)
1042    }
1043
1044    pub fn no_conflict(&self) -> bool {
1045        !self.is_conflicting()
1046    }
1047
1048    pub fn is_conflicting(&self) -> bool {
1049        matches!(
1050            self.internal_state,
1051            CSPSolverStateInternal::Conflict { conflict_info: _ }
1052        )
1053    }
1054
1055    pub fn is_infeasible(&self) -> bool {
1056        matches!(self.internal_state, CSPSolverStateInternal::Infeasible)
1057    }
1058
1059    /// Determines whether the current state is inconsistent; i.e. whether it is conflicting,
1060    /// infeasible or infeasible under assumptions
1061    pub fn is_inconsistent(&self) -> bool {
1062        self.is_conflicting() || self.is_infeasible() || self.is_infeasible_under_assumptions()
1063    }
1064
1065    pub fn is_infeasible_under_assumptions(&self) -> bool {
1066        matches!(
1067            self.internal_state,
1068            CSPSolverStateInternal::InfeasibleUnderAssumptions {
1069                violated_assumption: _
1070            }
1071        )
1072    }
1073
1074    pub fn get_violated_assumption(&self) -> Predicate {
1075        if let CSPSolverStateInternal::InfeasibleUnderAssumptions {
1076            violated_assumption,
1077        } = self.internal_state
1078        {
1079            violated_assumption
1080        } else {
1081            panic!(
1082                "Cannot extract violated assumption without getting the solver into the infeasible
1083                 under assumptions state."
1084            );
1085        }
1086    }
1087
1088    pub(crate) fn get_conflict_info(&self) -> StoredConflictInfo {
1089        match &self.internal_state {
1090            CSPSolverStateInternal::Conflict { conflict_info } => conflict_info.clone(),
1091            CSPSolverStateInternal::InfeasibleUnderAssumptions {
1092                violated_assumption,
1093            } => StoredConflictInfo::InconsistentAssumptions(*violated_assumption),
1094            _ => {
1095                panic!("Cannot extract conflict clause if solver is not in a conflict.");
1096            }
1097        }
1098    }
1099
1100    pub fn timeout(&self) -> bool {
1101        matches!(self.internal_state, CSPSolverStateInternal::Timeout)
1102    }
1103
1104    pub fn has_solution(&self) -> bool {
1105        matches!(
1106            self.internal_state,
1107            CSPSolverStateInternal::ContainsSolution
1108        )
1109    }
1110
1111    pub(crate) fn declare_ready(&mut self) {
1112        self.internal_state = CSPSolverStateInternal::Ready;
1113    }
1114
1115    pub fn declare_solving(&mut self) {
1116        pumpkin_assert_simple!((self.is_ready() || self.is_conflicting()) && !self.is_infeasible());
1117        self.internal_state = CSPSolverStateInternal::Solving;
1118    }
1119
1120    pub fn declare_infeasible(&mut self) {
1121        self.internal_state = CSPSolverStateInternal::Infeasible;
1122    }
1123
1124    pub(crate) fn declare_conflict(&mut self, conflict_info: StoredConflictInfo) {
1125        self.internal_state = CSPSolverStateInternal::Conflict { conflict_info };
1126    }
1127
1128    pub fn declare_solution_found(&mut self) {
1129        pumpkin_assert_simple!(!self.is_infeasible());
1130        self.internal_state = CSPSolverStateInternal::ContainsSolution;
1131    }
1132
1133    pub fn declare_timeout(&mut self) {
1134        pumpkin_assert_simple!(!self.is_infeasible());
1135        self.internal_state = CSPSolverStateInternal::Timeout;
1136    }
1137
1138    pub fn declare_infeasible_under_assumptions(&mut self, violated_assumption: Predicate) {
1139        pumpkin_assert_simple!(!self.is_infeasible());
1140        self.internal_state = CSPSolverStateInternal::InfeasibleUnderAssumptions {
1141            violated_assumption,
1142        }
1143    }
1144}
1145
1146declare_inference_label!(pub(crate) NogoodLabel, "nogood");
1147
1148#[cfg(test)]
1149mod tests {
1150
1151    #[derive(Debug, Clone, Copy)]
1152    struct NoLearningResolver;
1153
1154    impl ConflictResolver for NoLearningResolver {
1155        fn resolve_conflict(&mut self, context: &mut ConflictAnalysisContext) {
1156            let last_decision = context
1157                .find_last_decision()
1158                .expect("the solver is not at decision level 0, so there exists a last decision");
1159
1160            let current_checkpoint = context.get_checkpoint();
1161            context.restore_to(current_checkpoint - 1);
1162
1163            let update_occurred = context
1164                .post(!last_decision)
1165                .expect("Expected enqueued predicate to not lead to conflict directly");
1166
1167            pumpkin_assert_simple!(
1168                update_occurred,
1169                "The propagated predicate should not already be true."
1170            );
1171        }
1172    }
1173    use super::ConstraintSatisfactionSolver;
1174    use super::CoreExtractionResult;
1175    use crate::DefaultBrancher;
1176    use crate::basic_types::CSPSolverExecutionFlag;
1177    use crate::conflict_resolving::ConflictAnalysisContext;
1178    use crate::conflict_resolving::ConflictResolver;
1179    use crate::predicate;
1180    use crate::predicates::Predicate;
1181    use crate::propagation::ReadDomains;
1182    use crate::pumpkin_assert_simple;
1183    use crate::termination::Indefinite;
1184
1185    fn is_same_core(core1: &[Predicate], core2: &[Predicate]) -> bool {
1186        core1.len() == core2.len() && core2.iter().all(|lit| core1.contains(lit))
1187    }
1188
1189    fn is_result_the_same(res1: &CoreExtractionResult, res2: &CoreExtractionResult) -> bool {
1190        match (res1, res2) {
1191            (
1192                CoreExtractionResult::ConflictingAssumption(assumption1),
1193                CoreExtractionResult::ConflictingAssumption(assumption2),
1194            ) => assumption1 == assumption2,
1195            (CoreExtractionResult::Core(core1), CoreExtractionResult::Core(core2)) => {
1196                is_same_core(core1, core2)
1197            }
1198            _ => false,
1199        }
1200    }
1201
1202    fn run_test(
1203        mut solver: ConstraintSatisfactionSolver,
1204        assumptions: Vec<Predicate>,
1205        expected_flag: CSPSolverExecutionFlag,
1206        expected_result: CoreExtractionResult,
1207    ) {
1208        let mut brancher = DefaultBrancher::default_over_all_variables(&solver.state.assignments);
1209        let mut resolver = NoLearningResolver;
1210
1211        let flag = solver.solve_under_assumptions(
1212            &assumptions,
1213            &mut Indefinite,
1214            &mut brancher,
1215            &mut resolver,
1216        );
1217        assert_eq!(flag, expected_flag, "The flags do not match.");
1218
1219        if matches!(flag, CSPSolverExecutionFlag::Infeasible) {
1220            assert!(
1221                is_result_the_same(
1222                    &solver.extract_clausal_core(&mut brancher),
1223                    &expected_result
1224                ),
1225                "The result is not the same"
1226            );
1227        }
1228    }
1229
1230    fn create_instance1() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1231        let mut solver = ConstraintSatisfactionSolver::default();
1232        let c1 = solver.new_constraint_tag();
1233        let c2 = solver.new_constraint_tag();
1234        let c3 = solver.new_constraint_tag();
1235        let lit1 = solver.create_new_literal(None).get_true_predicate();
1236        let lit2 = solver.create_new_literal(None).get_true_predicate();
1237
1238        let _ = solver.add_clause([lit1, lit2], c1);
1239        let _ = solver.add_clause([lit1, !lit2], c2);
1240        let _ = solver.add_clause([!lit1, lit2], c3);
1241        (solver, vec![lit1, lit2])
1242    }
1243
1244    #[test]
1245    fn core_extraction_unit_core() {
1246        let mut solver = ConstraintSatisfactionSolver::default();
1247        let constraint_tag = solver.new_constraint_tag();
1248        let lit1 = solver.create_new_literal(None).get_true_predicate();
1249        let _ = solver.add_clause(vec![lit1], constraint_tag);
1250
1251        run_test(
1252            solver,
1253            vec![!lit1],
1254            CSPSolverExecutionFlag::Infeasible,
1255            CoreExtractionResult::Core(vec![!lit1]),
1256        )
1257    }
1258
1259    #[test]
1260    fn simple_core_extraction_1_1() {
1261        let (solver, lits) = create_instance1();
1262        run_test(
1263            solver,
1264            vec![!lits[0], !lits[1]],
1265            CSPSolverExecutionFlag::Infeasible,
1266            CoreExtractionResult::Core(vec![!lits[0]]),
1267        )
1268    }
1269
1270    #[test]
1271    fn simple_core_extraction_1_2() {
1272        let (solver, lits) = create_instance1();
1273        run_test(
1274            solver,
1275            vec![!lits[1], !lits[0]],
1276            CSPSolverExecutionFlag::Infeasible,
1277            CoreExtractionResult::Core(vec![!lits[1]]),
1278        );
1279    }
1280
1281    #[test]
1282    fn simple_core_extraction_1_infeasible() {
1283        let (mut solver, lits) = create_instance1();
1284        let constraint_tag = solver.new_constraint_tag();
1285        let _ = solver.add_clause([!lits[0], !lits[1]], constraint_tag);
1286        run_test(
1287            solver,
1288            vec![!lits[1], !lits[0]],
1289            CSPSolverExecutionFlag::Infeasible,
1290            CoreExtractionResult::Core(vec![]),
1291        );
1292    }
1293
1294    #[test]
1295    fn simple_core_extraction_1_core_conflicting() {
1296        let (solver, lits) = create_instance1();
1297        run_test(
1298            solver,
1299            vec![!lits[1], lits[1]],
1300            CSPSolverExecutionFlag::Infeasible,
1301            CoreExtractionResult::ConflictingAssumption(!lits[1]),
1302        );
1303    }
1304    fn create_instance2() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1305        let mut solver = ConstraintSatisfactionSolver::default();
1306        let c1 = solver.new_constraint_tag();
1307        let c2 = solver.new_constraint_tag();
1308        let lit1 = solver.create_new_literal(None).get_true_predicate();
1309        let lit2 = solver.create_new_literal(None).get_true_predicate();
1310        let lit3 = solver.create_new_literal(None).get_true_predicate();
1311
1312        let _ = solver.add_clause([lit1, lit2, lit3], c1);
1313        let _ = solver.add_clause([lit1, !lit2, lit3], c2);
1314        (solver, vec![lit1, lit2, lit3])
1315    }
1316
1317    #[test]
1318    fn simple_core_extraction_2_1() {
1319        let (solver, lits) = create_instance2();
1320        run_test(
1321            solver,
1322            vec![!lits[0], lits[1], !lits[2]],
1323            CSPSolverExecutionFlag::Infeasible,
1324            CoreExtractionResult::Core(vec![!lits[0], lits[1], !lits[2]]),
1325        );
1326    }
1327
1328    #[test]
1329    fn simple_core_extraction_2_long_assumptions_with_inconsistency_at_the_end() {
1330        let (solver, lits) = create_instance2();
1331        run_test(
1332            solver,
1333            vec![!lits[0], lits[1], !lits[2], lits[0]],
1334            CSPSolverExecutionFlag::Infeasible,
1335            CoreExtractionResult::ConflictingAssumption(!lits[0]),
1336        );
1337    }
1338
1339    #[test]
1340    fn simple_core_extraction_2_inconsistent_long_assumptions() {
1341        let (solver, lits) = create_instance2();
1342        run_test(
1343            solver,
1344            vec![!lits[0], !lits[0], !lits[1], !lits[1], lits[0]],
1345            CSPSolverExecutionFlag::Infeasible,
1346            CoreExtractionResult::ConflictingAssumption(!lits[0]),
1347        );
1348    }
1349    fn create_instance3() -> (ConstraintSatisfactionSolver, Vec<Predicate>) {
1350        let mut solver = ConstraintSatisfactionSolver::default();
1351        let constraint_tag = solver.new_constraint_tag();
1352
1353        let lit1 = solver.create_new_literal(None).get_true_predicate();
1354        let lit2 = solver.create_new_literal(None).get_true_predicate();
1355        let lit3 = solver.create_new_literal(None).get_true_predicate();
1356
1357        let _ = solver.add_clause([lit1, lit2, lit3], constraint_tag);
1358        (solver, vec![lit1, lit2, lit3])
1359    }
1360
1361    #[test]
1362    fn simple_core_extraction_3_1() {
1363        let (solver, lits) = create_instance3();
1364        run_test(
1365            solver,
1366            vec![!lits[0], !lits[1], !lits[2]],
1367            CSPSolverExecutionFlag::Infeasible,
1368            CoreExtractionResult::Core(vec![!lits[0], !lits[1], !lits[2]]),
1369        );
1370    }
1371
1372    #[test]
1373    fn simple_core_extraction_3_2() {
1374        let (solver, lits) = create_instance3();
1375        run_test(
1376            solver,
1377            vec![!lits[0], !lits[1]],
1378            CSPSolverExecutionFlag::Feasible,
1379            CoreExtractionResult::Core(vec![]), // will be ignored in the test
1380        );
1381    }
1382
1383    // #[test]
1384    // fn core_extraction_equality_assumption() {
1385    //     let mut solver = ConstraintSatisfactionSolver::default();
1386    //
1387    //     let x = solver.create_new_integer_variable(0, 10, None);
1388    //     let y = solver.create_new_integer_variable(0, 10, None);
1389    //     let z = solver.create_new_integer_variable(0, 10, None);
1390    //
1391    //     let constraint_tag = solver.new_constraint_tag();
1392    //
1393    //     let result = solver.add_propagator(LinearNotEqualPropagatorArgs {
1394    //         terms: [x.scaled(1), y.scaled(-1)].into(),
1395    //         rhs: 0,
1396    //         constraint_tag,
1397    //     });
1398    //     assert!(result.is_ok());
1399    //     run_test(
1400    //         solver,
1401    //         vec![
1402    //             predicate!(x >= 5),
1403    //             predicate!(z != 10),
1404    //             predicate!(y == 5),
1405    //             predicate!(x <= 5),
1406    //         ],
1407    //         CSPSolverExecutionFlag::Infeasible,
1408    //         CoreExtractionResult::Core(vec![predicate!(x == 5), predicate!(y == 5)]),
1409    //     )
1410    // }
1411
1412    #[test]
1413    fn new_domain_with_negative_lower_bound() {
1414        let lb = -2;
1415        let ub = 2;
1416
1417        let mut solver = ConstraintSatisfactionSolver::default();
1418        let domain_id = solver.create_new_integer_variable(lb, ub, None);
1419
1420        assert_eq!(lb, solver.state.assignments.get_lower_bound(domain_id));
1421
1422        assert_eq!(ub, solver.state.assignments.get_upper_bound(domain_id));
1423
1424        assert!(
1425            !solver
1426                .state
1427                .assignments
1428                .is_predicate_satisfied(predicate![domain_id == lb])
1429        );
1430
1431        for value in (lb + 1)..ub {
1432            let predicate = predicate![domain_id >= value];
1433
1434            assert!(!solver.state.assignments.is_predicate_satisfied(predicate));
1435
1436            assert!(
1437                !solver
1438                    .state
1439                    .assignments
1440                    .is_predicate_satisfied(predicate![domain_id == value])
1441            );
1442        }
1443
1444        assert!(
1445            !solver
1446                .state
1447                .assignments
1448                .is_predicate_satisfied(predicate![domain_id == ub])
1449        );
1450    }
1451
1452    // #[test]
1453    // fn check_can_compute_1uip_with_propagator_initialisation_conflict() {
1454    //     let mut solver = ConstraintSatisfactionSolver::default();
1455    //
1456    //     let x = solver.create_new_integer_variable(1, 1, None);
1457    //     let y = solver.create_new_integer_variable(2, 2, None);
1458    //
1459    //     let constraint_tag = solver.new_constraint_tag();
1460    //
1461    //     let propagator = LinearNotEqualPropagatorArgs {
1462    //         terms: vec![x, y].into(),
1463    //         rhs: 3,
1464    //         constraint_tag,
1465    //     };
1466    //     let result = solver.add_propagator(propagator);
1467    //     assert!(result.is_err());
1468    // }
1469}