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