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