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