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>>;