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