pumpkin_core/api/
solver.rs

1use super::outputs::Satisfiable;
2use super::outputs::SolutionReference;
3use super::results::OptimisationResult;
4use super::results::SatisfactionResult;
5use super::results::SatisfactionResultUnderAssumptions;
6use crate::basic_types::CSPSolverExecutionFlag;
7use crate::basic_types::ConstraintOperationError;
8use crate::basic_types::Solution;
9use crate::branching::branchers::autonomous_search::AutonomousSearch;
10use crate::branching::branchers::independent_variable_value_brancher::IndependentVariableValueBrancher;
11use crate::branching::value_selection::RandomSplitter;
12#[cfg(doc)]
13use crate::branching::value_selection::ValueSelector;
14use crate::branching::variable_selection::RandomSelector;
15#[cfg(doc)]
16use crate::branching::variable_selection::VariableSelector;
17use crate::branching::Brancher;
18use crate::constraints::ConstraintPoster;
19use crate::containers::HashSet;
20use crate::engine::predicates::predicate::Predicate;
21use crate::engine::propagation::constructor::PropagatorConstructor;
22pub use crate::engine::propagation::store::PropagatorHandle;
23use crate::engine::termination::TerminationCondition;
24use crate::engine::variables::DomainId;
25use crate::engine::variables::IntegerVariable;
26use crate::engine::variables::Literal;
27use crate::engine::ConstraintSatisfactionSolver;
28#[cfg(doc)]
29use crate::optimisation::linear_sat_unsat::LinearSatUnsat;
30#[cfg(doc)]
31use crate::optimisation::linear_unsat_sat::LinearUnsatSat;
32use crate::optimisation::solution_callback::SolutionCallback;
33use crate::optimisation::OptimisationProcedure;
34use crate::options::SolverOptions;
35#[cfg(doc)]
36use crate::predicates;
37use crate::proof::ConstraintTag;
38use crate::results::solution_iterator::SolutionIterator;
39use crate::results::unsatisfiable::UnsatisfiableUnderAssumptions;
40use crate::statistics::log_statistic;
41use crate::statistics::log_statistic_postfix;
42use crate::statistics::StatisticLogger;
43
44/// The main interaction point which allows the creation of variables, the addition of constraints,
45/// and solving problems.
46///
47///
48/// # Creating Variables
49/// As stated in [`crate::variables`], we can create two types of variables: propositional variables
50/// and integer variables.
51///
52/// ```rust
53/// # use pumpkin_core::Solver;
54/// # use pumpkin_core::variables::TransformableVariable;
55/// let mut solver = Solver::default();
56///
57/// // Integer Variables
58///
59/// // We can create an integer variable with a domain in the range [0, 10]
60/// let integer_between_bounds = solver.new_bounded_integer(0, 10);
61///
62/// // We can also create such a variable with a name
63/// let named_integer_between_bounds = solver.new_named_bounded_integer(0, 10, "x");
64///
65/// // We can also create an integer variable with a non-continuous domain in the follow way
66/// let mut sparse_integer = solver.new_sparse_integer(vec![0, 3, 5]);
67///
68/// // We can also create such a variable with a name
69/// let named_sparse_integer = solver.new_named_sparse_integer(vec![0, 3, 5], "y");
70///
71/// // Additionally, we can also create an affine view over a variable with both a scale and an offset (or either)
72/// let view_over_integer = integer_between_bounds.scaled(-1).offset(15);
73///
74///
75/// // Propositional Variable
76///
77/// // We can create a literal
78/// let literal = solver.new_literal();
79///
80/// // We can also create such a variable with a name
81/// let named_literal = solver.new_named_literal("z");
82///
83/// // We can also get the predicate from the literal
84/// let true_predicate = literal.get_true_predicate();
85///
86/// // We can also create an iterator of new literals and get a number of them at once
87/// let list_of_5_literals = solver.new_literals().take(5).collect::<Vec<_>>();
88/// assert_eq!(list_of_5_literals.len(), 5);
89/// ```
90///
91/// # Using the Solver
92/// For examples on how to use the solver, see the [root-level crate documentation](crate) or [one of these examples](https://github.com/ConSol-Lab/Pumpkin/tree/master/pumpkin-lib/examples).
93#[derive(Debug)]
94pub struct Solver {
95    /// The internal [`ConstraintSatisfactionSolver`] which is used to solve the problems.
96    pub(crate) satisfaction_solver: ConstraintSatisfactionSolver,
97    true_literal: Literal,
98}
99
100impl Default for Solver {
101    fn default() -> Self {
102        let satisfaction_solver = ConstraintSatisfactionSolver::default();
103        let true_literal = Literal::new(Predicate::trivially_true().get_domain());
104        Self {
105            satisfaction_solver,
106            true_literal,
107        }
108    }
109}
110
111impl Solver {
112    /// Creates a solver with the provided [`SolverOptions`].
113    pub fn with_options(solver_options: SolverOptions) -> Self {
114        let satisfaction_solver = ConstraintSatisfactionSolver::new(solver_options);
115        let true_literal = Literal::new(Predicate::trivially_true().get_domain());
116        Self {
117            satisfaction_solver,
118            true_literal,
119        }
120    }
121
122    /// Logs the statistics currently present in the solver with the provided objective value.
123    pub fn log_statistics_with_objective(
124        &self,
125        brancher: Option<&impl Brancher>,
126        objective_value: i64,
127        verbose: bool,
128    ) {
129        log_statistic("objective", objective_value);
130        self.log_statistics(brancher, verbose);
131    }
132
133    /// Logs the statistics currently present in the solver.
134    pub fn log_statistics(&self, brancher: Option<&impl Brancher>, verbose: bool) {
135        self.satisfaction_solver.log_statistics(verbose);
136        if verbose {
137            if let Some(brancher) = brancher {
138                brancher.log_statistics(StatisticLogger::new(["brancher"]));
139            }
140        }
141        log_statistic_postfix();
142    }
143
144    pub fn get_solution_reference(&self) -> SolutionReference<'_> {
145        self.satisfaction_solver.get_solution_reference()
146    }
147
148    pub(crate) fn is_logging_full_proof(&self) -> bool {
149        self.satisfaction_solver.is_logging_full_proof()
150    }
151}
152
153/// Methods to retrieve information about variables
154impl Solver {
155    /// Get the value of the given [`Literal`] at the root level (after propagation), which could be
156    /// unassigned.
157    pub fn get_literal_value(&self, literal: Literal) -> Option<bool> {
158        self.satisfaction_solver.get_literal_value(literal)
159    }
160
161    /// Get the lower-bound of the given [`IntegerVariable`] at the root level (after propagation).
162    pub fn lower_bound(&self, variable: &impl IntegerVariable) -> i32 {
163        self.satisfaction_solver.get_lower_bound(variable)
164    }
165
166    /// Get the upper-bound of the given [`IntegerVariable`] at the root level (after propagation).
167    pub fn upper_bound(&self, variable: &impl IntegerVariable) -> i32 {
168        self.satisfaction_solver.get_upper_bound(variable)
169    }
170}
171
172/// Functions to create and retrieve integer and propositional variables.
173impl Solver {
174    /// Returns an infinite iterator of positive literals of new variables. The new variables will
175    /// be unnamed.
176    ///
177    /// # Example
178    /// ```
179    /// # use pumpkin_core::Solver;
180    /// # use pumpkin_core::variables::Literal;
181    /// let mut solver = Solver::default();
182    /// let literals: Vec<Literal> = solver.new_literals().take(5).collect();
183    ///
184    /// // `literals` contains 5 positive literals of newly created propositional variables.
185    /// assert_eq!(literals.len(), 5);
186    /// ```
187    ///
188    /// Note that this method captures the lifetime of the immutable reference to `self`.
189    pub fn new_literals(&mut self) -> impl Iterator<Item = Literal> + '_ {
190        std::iter::from_fn(|| Some(self.new_literal()))
191    }
192
193    /// Create a fresh propositional variable and return the literal with positive polarity.
194    ///
195    /// # Example
196    /// ```rust
197    /// # use pumpkin_core::Solver;
198    /// let mut solver = Solver::default();
199    ///
200    /// // We can create a literal
201    /// let literal = solver.new_literal();
202    /// ```
203    pub fn new_literal(&mut self) -> Literal {
204        self.satisfaction_solver.create_new_literal(None)
205    }
206
207    pub fn new_literal_for_predicate(
208        &mut self,
209        predicate: Predicate,
210        constraint_tag: ConstraintTag,
211    ) -> Literal {
212        self.satisfaction_solver
213            .create_new_literal_for_predicate(predicate, None, constraint_tag)
214    }
215
216    /// Create a fresh propositional variable with a given name and return the literal with positive
217    /// polarity.
218    ///
219    /// # Example
220    /// ```rust
221    /// # use pumpkin_core::Solver;
222    /// let mut solver = Solver::default();
223    ///
224    /// // We can also create such a variable with a name
225    /// let named_literal = solver.new_named_literal("z");
226    /// ```
227    pub fn new_named_literal(&mut self, name: impl Into<String>) -> Literal {
228        self.satisfaction_solver
229            .create_new_literal(Some(name.into()))
230    }
231
232    /// Get a literal which is always true.
233    pub fn get_true_literal(&self) -> Literal {
234        self.true_literal
235    }
236
237    /// Get a literal which is always false.
238    pub fn get_false_literal(&self) -> Literal {
239        !self.true_literal
240    }
241
242    /// Create a new integer variable with the given bounds.
243    ///
244    /// # Example
245    /// ```rust
246    /// # use pumpkin_core::Solver;
247    /// let mut solver = Solver::default();
248    ///
249    /// // We can create an integer variable with a domain in the range [0, 10]
250    /// let integer_between_bounds = solver.new_bounded_integer(0, 10);
251    /// ```
252    pub fn new_bounded_integer(&mut self, lower_bound: i32, upper_bound: i32) -> DomainId {
253        self.satisfaction_solver
254            .create_new_integer_variable(lower_bound, upper_bound, None)
255    }
256
257    /// Create a new named integer variable with the given bounds.
258    ///
259    /// # Example
260    /// ```rust
261    /// # use pumpkin_core::Solver;
262    /// let mut solver = Solver::default();
263    ///
264    /// // We can also create such a variable with a name
265    /// let named_integer_between_bounds = solver.new_named_bounded_integer(0, 10, "x");
266    /// ```
267    pub fn new_named_bounded_integer(
268        &mut self,
269        lower_bound: i32,
270        upper_bound: i32,
271        name: impl Into<String>,
272    ) -> DomainId {
273        self.satisfaction_solver.create_new_integer_variable(
274            lower_bound,
275            upper_bound,
276            Some(name.into()),
277        )
278    }
279
280    /// Create a new integer variable which has a domain of predefined values. We remove duplicates
281    /// by converting to a hash set
282    ///
283    /// # Example
284    /// ```rust
285    /// # use pumpkin_core::Solver;
286    /// let mut solver = Solver::default();
287    ///
288    /// // We can also create an integer variable with a non-continuous domain in the follow way
289    /// let mut sparse_integer = solver.new_sparse_integer(vec![0, 3, 5]);
290    /// ```
291    pub fn new_sparse_integer(&mut self, values: impl Into<Vec<i32>>) -> DomainId {
292        let values: HashSet<i32> = values.into().into_iter().collect();
293
294        self.satisfaction_solver
295            .create_new_integer_variable_sparse(values.into_iter().collect(), None)
296    }
297
298    /// Create a new named integer variable which has a domain of predefined values.
299    ///
300    /// # Example
301    /// ```rust
302    /// # use pumpkin_core::Solver;
303    /// let mut solver = Solver::default();
304    ///
305    /// // We can also create such a variable with a name
306    /// let named_sparse_integer = solver.new_named_sparse_integer(vec![0, 3, 5], "y");
307    /// ```
308    pub fn new_named_sparse_integer(
309        &mut self,
310        values: impl Into<Vec<i32>>,
311        name: impl Into<String>,
312    ) -> DomainId {
313        self.satisfaction_solver
314            .create_new_integer_variable_sparse(values.into(), Some(name.into()))
315    }
316}
317
318/// Functions for solving with the constraints that have been added to the [`Solver`].
319impl Solver {
320    /// Solves the current model in the [`Solver`] until it finds a solution (or is indicated to
321    /// terminate by the provided [`TerminationCondition`]) and returns a [`SatisfactionResult`]
322    /// which can be used to obtain the found solution or find other solutions.
323    pub fn satisfy<'this, 'brancher, B: Brancher, T: TerminationCondition>(
324        &'this mut self,
325        brancher: &'brancher mut B,
326        termination: &mut T,
327    ) -> SatisfactionResult<'this, 'brancher, B> {
328        match self.satisfaction_solver.solve(termination, brancher) {
329            CSPSolverExecutionFlag::Feasible => {
330                brancher.on_solution(self.satisfaction_solver.get_solution_reference());
331
332                SatisfactionResult::Satisfiable(Satisfiable::new(self, brancher))
333            }
334            CSPSolverExecutionFlag::Infeasible => {
335                // Reset the state whenever we return a result
336                self.satisfaction_solver.restore_state_at_root(brancher);
337                let _ = self.satisfaction_solver.conclude_proof_unsat();
338
339                SatisfactionResult::Unsatisfiable(self, brancher)
340            }
341            CSPSolverExecutionFlag::Timeout => {
342                // Reset the state whenever we return a result
343                self.satisfaction_solver.restore_state_at_root(brancher);
344                SatisfactionResult::Unknown(self, brancher)
345            }
346        }
347    }
348
349    pub fn get_solution_iterator<
350        'this,
351        'brancher,
352        'termination,
353        B: Brancher,
354        T: TerminationCondition,
355    >(
356        &'this mut self,
357        brancher: &'brancher mut B,
358        termination: &'termination mut T,
359    ) -> SolutionIterator<'this, 'brancher, 'termination, B, T> {
360        SolutionIterator::new(self, brancher, termination)
361    }
362
363    /// Solves the current model in the [`Solver`] until it finds a solution (or is indicated to
364    /// terminate by the provided [`TerminationCondition`]) and returns a [`SatisfactionResult`]
365    /// which can be used to obtain the found solution or find other solutions.
366    ///
367    /// This method takes as input a list of [`Predicate`]s which represent so-called assumptions
368    /// (see \[1\] for a more detailed explanation). See the [`predicates`] documentation for how
369    /// to construct these predicates.
370    ///
371    /// # Bibliography
372    /// \[1\] N. Eén and N. Sörensson, ‘Temporal induction by incremental SAT solving’, Electronic
373    /// Notes in Theoretical Computer Science, vol. 89, no. 4, pp. 543–560, 2003.
374    pub fn satisfy_under_assumptions<'this, 'brancher, B: Brancher, T: TerminationCondition>(
375        &'this mut self,
376        brancher: &'brancher mut B,
377        termination: &mut T,
378        assumptions: &[Predicate],
379    ) -> SatisfactionResultUnderAssumptions<'this, 'brancher, B> {
380        match self
381            .satisfaction_solver
382            .solve_under_assumptions(assumptions, termination, brancher)
383        {
384            CSPSolverExecutionFlag::Feasible => {
385                let solution: Solution = self.satisfaction_solver.get_solution_reference().into();
386                // Reset the state whenever we return a result
387                brancher.on_solution(solution.as_reference());
388                SatisfactionResultUnderAssumptions::Satisfiable(Satisfiable::new(self, brancher))
389            }
390            CSPSolverExecutionFlag::Infeasible => {
391                if self
392                    .satisfaction_solver
393                    .state
394                    .is_infeasible_under_assumptions()
395                {
396                    // The state is automatically reset when we return this result
397                    SatisfactionResultUnderAssumptions::UnsatisfiableUnderAssumptions(
398                        UnsatisfiableUnderAssumptions::new(&mut self.satisfaction_solver, brancher),
399                    )
400                } else {
401                    // Reset the state whenever we return a result
402                    self.satisfaction_solver.restore_state_at_root(brancher);
403                    SatisfactionResultUnderAssumptions::Unsatisfiable(self)
404                }
405            }
406            CSPSolverExecutionFlag::Timeout => {
407                // Reset the state whenever we return a result
408                self.satisfaction_solver.restore_state_at_root(brancher);
409                SatisfactionResultUnderAssumptions::Unknown(self)
410            }
411        }
412    }
413
414    /// Solves the model currently in the [`Solver`] to optimality where the provided
415    /// `objective_variable` is optimised as indicated by the `direction` (or is indicated to
416    /// terminate by the provided [`TerminationCondition`]). Uses a search strategy based on the
417    /// provided [`OptimisationProcedure`], currently [`LinearSatUnsat`] and
418    /// [`LinearUnsatSat`] are supported.
419    ///
420    /// It returns an [`OptimisationResult`] which can be used to retrieve the optimal solution if
421    /// it exists.
422    pub fn optimise<B, Callback>(
423        &mut self,
424        brancher: &mut B,
425        termination: &mut impl TerminationCondition,
426        mut optimisation_procedure: impl OptimisationProcedure<B, Callback>,
427    ) -> OptimisationResult
428    where
429        B: Brancher,
430        Callback: SolutionCallback<B>,
431    {
432        optimisation_procedure.optimise(brancher, termination, self)
433    }
434}
435
436/// Functions for adding new constraints to the solver.
437impl Solver {
438    /// Creates a new [`ConstraintTag`] that can be used to add constraints to the solver.
439    ///
440    /// See the [`ConstraintTag`] documentation for information on how the tags are used.
441    pub fn new_constraint_tag(&mut self) -> ConstraintTag {
442        self.satisfaction_solver.new_constraint_tag()
443    }
444
445    /// Add a constraint to the solver. This returns a [`ConstraintPoster`] which enables control
446    /// on whether to add the constraint as-is, or whether to (half) reify it.
447    ///
448    /// All constraints require a [`ConstraintTag`] to be supplied. See its documentation for more
449    /// information.
450    ///
451    /// If none of the methods on [`ConstraintPoster`] are used, the constraint _is not_ actually
452    /// added to the solver. In this case, a warning is emitted.
453    ///
454    /// # Example
455    /// ```
456    /// # use pumpkin_core::constraints;
457    /// # use pumpkin_core::Solver;
458    /// let mut solver = Solver::default();
459    ///
460    /// let a = solver.new_bounded_integer(0, 3);
461    /// let b = solver.new_bounded_integer(0, 3);
462    ///
463    /// let constraint_tag = solver.new_constraint_tag();
464    ///
465    /// solver
466    ///     .add_constraint(constraints::equals([a, b], 0, constraint_tag))
467    ///     .post();
468    /// ```
469    pub fn add_constraint<Constraint>(
470        &mut self,
471        constraint: Constraint,
472    ) -> ConstraintPoster<'_, Constraint> {
473        ConstraintPoster::new(self, constraint)
474    }
475
476    /// Creates a clause from `literals` and adds it to the current formula.
477    ///
478    /// If the formula becomes trivially unsatisfiable, a [`ConstraintOperationError`] will be
479    /// returned. Subsequent calls to this method will always return an error, and no
480    /// modification of the solver will take place.
481    pub fn add_clause(
482        &mut self,
483        clause: impl IntoIterator<Item = Predicate>,
484        constraint_tag: ConstraintTag,
485    ) -> Result<(), ConstraintOperationError> {
486        self.satisfaction_solver.add_clause(clause, constraint_tag)
487    }
488
489    /// Post a new propagator to the solver. If unsatisfiability can be immediately determined
490    /// through propagation, this will return a [`ConstraintOperationError`].
491    ///
492    /// A propagator is provided through an implementation of [`PropagatorConstructor`]. The
493    /// propagator that will be added is [`PropagatorConstructor::PropagatorImpl`].
494    ///
495    /// If the solver is already in a conflicting state, i.e. a previous call to this method
496    /// already returned `false`, calling this again will not alter the solver in any way, and
497    /// `false` will be returned again.
498    pub(crate) fn add_propagator<Constructor>(
499        &mut self,
500        constructor: Constructor,
501    ) -> Result<PropagatorHandle<Constructor::PropagatorImpl>, ConstraintOperationError>
502    where
503        Constructor: PropagatorConstructor,
504        Constructor::PropagatorImpl: 'static,
505    {
506        self.satisfaction_solver.add_propagator(constructor)
507    }
508}
509
510/// Default brancher implementation
511impl Solver {
512    /// Creates an instance of the [`DefaultBrancher`].
513    pub fn default_brancher(&self) -> DefaultBrancher {
514        DefaultBrancher::default_over_all_variables(&self.satisfaction_solver.assignments)
515    }
516}
517
518/// Proof logging methods
519impl Solver {
520    #[doc(hidden)]
521    /// Conclude the proof with the unsatisfiable claim.
522    ///
523    /// This method will finish the proof. Any new operation will not be logged to the proof.
524    pub fn conclude_proof_unsat(&mut self) {
525        let _ = self.satisfaction_solver.conclude_proof_unsat();
526    }
527
528    /// Conclude the proof with the optimality claim.
529    ///
530    /// This method will finish the proof. Any new operation will not be logged to the proof.
531    pub fn conclude_proof_dual_bound(&mut self, bound: Predicate) {
532        let _ = self.satisfaction_solver.conclude_proof_optimal(bound);
533    }
534}
535
536/// A brancher which makes use of VSIDS \[1\] and solution-based phase saving (both adapted for CP).
537///
538/// If VSIDS does not contain any (unfixed) predicates then it will default to the
539/// [`IndependentVariableValueBrancher`] using [`RandomSelector`] for variable selection
540/// (over the variables in the order in which they were defined) and [`RandomSplitter`] for
541/// value selection.
542///
543/// # Bibliography
544/// \[1\] M. W. Moskewicz, C. F. Madigan, Y. Zhao, L. Zhang, and S. Malik, ‘Chaff: Engineering an
545/// efficient SAT solver’, in Proceedings of the 38th annual Design Automation Conference, 2001.
546///
547/// \[2\] E. Demirović, G. Chu, and P. J. Stuckey, ‘Solution-based phase saving for CP: A
548/// value-selection heuristic to simulate local search behavior in complete solvers’, in the
549/// proceedings of the Principles and Practice of Constraint Programming (CP 2018).
550pub type DefaultBrancher =
551    AutonomousSearch<IndependentVariableValueBrancher<DomainId, RandomSelector, RandomSplitter>>;