pumpkin_solver/
lib.rs

1//! # Pumpkin
2//! Pumpkin is a combinatorial optimisation solver developed by the ConSol Lab at TU Delft. It is
3//! based on the (lazy clause generation) constraint programming paradigm.
4//!
5//! Our goal is to keep the solver efficient, easy to use, and well-documented. The solver is
6//! written in pure Rust and follows Rust best practices.
7//!
8//! A unique feature of Pumpkin is that it can produce a _certificate of unsatisfiability_. See [our CP’24 paper](https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.CP.2024.11) for more details.
9//!
10//! The solver currently supports integer variables and a number of (global) constraints:
11//! * [Cumulative global constraint][crate::constraints::cumulative].
12//! * [Element global constraint][crate::constraints::element].
13//! * Arithmetic constraints: [linear integer
14//!   (in)equalities][crate::constraints::less_than_or_equals], [integer
15//!   division][crate::constraints::division], [integer multiplication][crate::constraints::times],
16//!   [maximum][crate::constraints::maximum], [absolute value][crate::constraints::absolute].
17//! * [Clausal constraints][Solver::add_clause].
18//!
19//! We are actively developing Pumpkin and would be happy to hear from you should you have any
20//! questions or feature requests!
21//!
22//!  # Using Pumpkin
23//! Pumpkin can be used to solve a variety of problems. The first step to solving a problem is
24//! **adding variables**:
25//! ```rust
26//! # use pumpkin_solver::Solver;
27//! # use pumpkin_solver::results::OptimisationResult;
28//! # use pumpkin_solver::termination::Indefinite;
29//! # use pumpkin_solver::results::ProblemSolution;
30//! # use pumpkin_solver::constraints::Constraint;
31//! # use std::cmp::max;
32//! // We create the solver with default options
33//! let mut solver = Solver::default();
34//!
35//! // We create 3 variables
36//! let x = solver.new_bounded_integer(5, 10);
37//! let y = solver.new_bounded_integer(-3, 15);
38//! let z = solver.new_bounded_integer(7, 25);
39//! ```
40//!
41//! Then we can **add constraints** supported by the [`Solver`]:
42//! ```rust
43//! # use pumpkin_solver::Solver;
44//! # use pumpkin_solver::results::OptimisationResult;
45//! # use pumpkin_solver::termination::Indefinite;
46//! # use pumpkin_solver::results::ProblemSolution;
47//! # use pumpkin_solver::constraints;
48//! # use pumpkin_solver::constraints::Constraint;
49//! # use std::cmp::max;
50//! # let mut solver = Solver::default();
51//! # let x = solver.new_bounded_integer(5, 10);
52//! # let y = solver.new_bounded_integer(-3, 15);
53//! # let z = solver.new_bounded_integer(7, 25);
54//! // We create the constraint:
55//! // x + y + z = 17
56//! let c1 = solver.new_constraint_tag();
57//! solver
58//!     .add_constraint(constraints::equals(vec![x, y, z], 17, c1))
59//!     .post();
60//! ```
61//!
62//! For finding a solution, a [`termination::TerminationCondition`] and a [`branching::Brancher`]
63//! should be specified, which determine when the solver should stop searching and the
64//! variable/value selection strategy which should be used:
65//! ```rust
66//! # use pumpkin_solver::Solver;
67//! # use pumpkin_solver::termination::Indefinite;
68//! # let mut solver = Solver::default();
69//! // We create a termination condition which allows the solver to run indefinitely
70//! let mut termination = Indefinite;
71//! // And we create a search strategy (in this case, simply the default)
72//! let mut brancher = solver.default_brancher();
73//! ```
74//!
75//!
76//! **Finding a solution** to this problem can be done by using [`Solver::satisfy`]:
77//! ```rust
78//! # use pumpkin_solver::Solver;
79//! # use pumpkin_solver::results::SatisfactionResult;
80//! # use pumpkin_solver::termination::Indefinite;
81//! # use pumpkin_solver::results::ProblemSolution;
82//! # use pumpkin_solver::constraints;
83//! # use pumpkin_solver::constraints::Constraint;
84//! # use std::cmp::max;
85//! # let mut solver = Solver::default();
86//! # let x = solver.new_bounded_integer(5, 10);
87//! # let y = solver.new_bounded_integer(-3, 15);
88//! # let z = solver.new_bounded_integer(7, 25);
89//! # let c1 = solver.new_constraint_tag();
90//! # solver.add_constraint(constraints::equals(vec![x, y, z], 17, c1)).post();
91//! # let mut termination = Indefinite;
92//! # let mut brancher = solver.default_brancher();
93//! // Then we find a solution to the problem
94//! let result = solver.satisfy(&mut brancher, &mut termination);
95//!
96//! if let SatisfactionResult::Satisfiable(satisfiable) = result {
97//!     let solution = satisfiable.solution();
98//!
99//!     let value_x = solution.get_integer_value(x);
100//!     let value_y = solution.get_integer_value(y);
101//!     let value_z = solution.get_integer_value(z);
102//!
103//!     // The constraint should hold for this solution
104//!     assert!(value_x + value_y + value_z == 17);
105//! } else {
106//!     panic!("This problem should have a solution")
107//! }
108//! ```
109//!
110//! **Optimizing an objective** can be done in a similar way using [`Solver::optimise`]; first the
111//! objective variable and a constraint over this value are added:
112//!
113//! ```rust
114//! # use pumpkin_solver::Solver;
115//! # use pumpkin_solver::constraints;
116//! # use pumpkin_solver::constraints::Constraint;
117//! # let mut solver = Solver::default();
118//! # let x = solver.new_bounded_integer(5, 10);
119//! # let y = solver.new_bounded_integer(-3, 15);
120//! # let z = solver.new_bounded_integer(7, 25);
121//! // We add another variable, the objective
122//! let objective = solver.new_bounded_integer(-10, 30);
123//!
124//! // We add a constraint which specifies the value of the objective
125//! let c1 = solver.new_constraint_tag();
126//! solver
127//!     .add_constraint(constraints::maximum(vec![x, y, z], objective, c1))
128//!     .post();
129//! ```
130//!
131//! Then we can find the optimal solution using [`Solver::optimise`]:
132//! ```rust
133//! # use pumpkin_solver::Solver;
134//! # use pumpkin_solver::results::OptimisationResult;
135//! # use pumpkin_solver::termination::Indefinite;
136//! # use pumpkin_solver::results::ProblemSolution;
137//! # use pumpkin_solver::constraints;
138//! # use pumpkin_solver::constraints::Constraint;
139//! # use pumpkin_solver::optimisation::OptimisationDirection;
140//! # use pumpkin_solver::optimisation::linear_sat_unsat::LinearSatUnsat;
141//! # use std::cmp::max;
142//! # use crate::pumpkin_solver::optimisation::OptimisationProcedure;
143//! # use pumpkin_solver::results::SolutionReference;
144//! # use pumpkin_solver::DefaultBrancher;
145//! # let mut solver = Solver::default();
146//! # let x = solver.new_bounded_integer(5, 10);
147//! # let y = solver.new_bounded_integer(-3, 15);
148//! # let z = solver.new_bounded_integer(7, 25);
149//! # let objective = solver.new_bounded_integer(-10, 30);
150//! # let c1 = solver.new_constraint_tag();
151//! # solver.add_constraint(constraints::equals(vec![x, y, z], 17, c1)).post();
152//! # solver.add_constraint(constraints::maximum(vec![x, y, z], objective, c1)).post();
153//! # let mut termination = Indefinite;
154//! # let mut brancher = solver.default_brancher();
155//! // Then we solve to optimality
156//! let callback: fn(&Solver, SolutionReference, &DefaultBrancher) = |_, _, _| {};
157//! let result = solver.optimise(
158//!     &mut brancher,
159//!     &mut termination,
160//!     LinearSatUnsat::new(OptimisationDirection::Minimise, objective, callback),
161//! );
162//!
163//! if let OptimisationResult::Optimal(optimal_solution) = result {
164//!     let value_x = optimal_solution.get_integer_value(x);
165//!     let value_y = optimal_solution.get_integer_value(y);
166//!     let value_z = optimal_solution.get_integer_value(z);
167//!     // The maximum objective values is 7;
168//!     // with one possible solution being: {x = 5, y = 5, z = 7, objective = 7}.
169//!
170//!     // We check whether the constraint holds again
171//!     assert!(value_x + value_y + value_z == 17);
172//!     // We check whether the newly added constraint for the objective value holds
173//!     assert!(
174//!         max(value_x, max(value_y, value_z)) == optimal_solution.get_integer_value(objective)
175//!     );
176//!     // We check whether this is actually an optimal solution
177//!     assert_eq!(optimal_solution.get_integer_value(objective), 7);
178//! } else {
179//!     panic!("This problem should have an optimal solution")
180//! }
181//! ```
182//!
183//! # Obtaining multiple solutions
184//! Pumpkin supports obtaining multiple solutions from the [`Solver`] when solving satisfaction
185//! problems. The same solution is prevented from occurring multiple times by adding blocking
186//! clauses to the solver which means that after iterating over solutions, these solutions will
187//! remain blocked if the solver is used again.
188//! ```rust
189//! # use pumpkin_solver::Solver;
190//! # use pumpkin_solver::results::SatisfactionResult;
191//! # use pumpkin_solver::termination::Indefinite;
192//! # use pumpkin_solver::results::ProblemSolution;
193//! # use pumpkin_solver::results::solution_iterator::IteratedSolution;
194//! # use pumpkin_solver::constraints;
195//! # use pumpkin_solver::constraints::Constraint;
196//! // We create the solver with default options
197//! let mut solver = Solver::default();
198//!
199//! // We create 3 variables with domains within the range [0, 2]
200//! let x = solver.new_bounded_integer(0, 2);
201//! let y = solver.new_bounded_integer(0, 2);
202//! let z = solver.new_bounded_integer(0, 2);
203//!
204//! // We create the all-different constraint
205//! let c1 = solver.new_constraint_tag();
206//! solver.add_constraint(constraints::all_different(vec![x, y, z], c1)).post();
207//!
208//! // We create a termination condition which allows the solver to run indefinitely
209//! let mut termination = Indefinite;
210//! // And we create a search strategy (in this case, simply the default)
211//! let mut brancher = solver.default_brancher();
212//!
213//! // Then we solve to satisfaction
214//! let mut solution_iterator = solver.get_solution_iterator(&mut brancher, &mut termination);
215//!
216//! let mut number_of_solutions = 0;
217//!
218//! // We keep track of a list of known solutions
219//! let mut known_solutions = Vec::new();
220//!
221//! loop {
222//!     match solution_iterator.next_solution() {
223//!         IteratedSolution::Solution(solution, _, _) => {
224//!             number_of_solutions += 1;
225//!             // We have found another solution, the same invariant should hold
226//!             let value_x = solution.get_integer_value(x);
227//!             let value_y = solution.get_integer_value(y);
228//!             let value_z = solution.get_integer_value(z);
229//!             assert!(x != y && x != z && y != z);
230//!
231//!             // It should also be the case that we have not found this solution before
232//!             assert!(!known_solutions.contains(&(value_x, value_y, value_z)));
233//!             known_solutions.push((value_x, value_y, value_z));
234//!         }
235//!         IteratedSolution::Finished => {
236//!             // No more solutions exist
237//!             break;
238//!         }
239//!         IteratedSolution::Unknown => {
240//!             // Our termination condition has caused the solver to terminate
241//!             break;
242//!         }
243//!         IteratedSolution::Unsatisfiable => {
244//!             panic!("Problem should be satisfiable")
245//!         }
246//!     }
247//! }
248//! // There are six possible solutions to this problem
249//! assert_eq!(number_of_solutions, 6)
250//!  ```
251//!
252//! # Obtaining an unsatisfiable core
253//! Pumpkin allows the user to specify assumptions which can then be used to extract an
254//! unsatisfiable core (see [`results::unsatisfiable::UnsatisfiableUnderAssumptions::extract_core`]).
255//! ```rust
256//! # use pumpkin_solver::Solver;
257//! # use pumpkin_solver::results::SatisfactionResultUnderAssumptions;
258//! # use pumpkin_solver::termination::Indefinite;
259//! # use pumpkin_solver::predicate;
260//! # use pumpkin_solver::constraints;
261//! # use pumpkin_solver::constraints::Constraint;
262//! // We create the solver with default options
263//! let mut solver = Solver::default();
264//!
265//! // We create 3 variables with domains within the range [0, 2]
266//! let x = solver.new_bounded_integer(0, 2);
267//! let y = solver.new_bounded_integer(0, 2);
268//! let z = solver.new_bounded_integer(0, 2);
269//!
270//! // We create the all-different constraint
271//! let c1 = solver.new_constraint_tag();
272//! solver.add_constraint(constraints::all_different(vec![x, y, z], c1)).post();
273//!
274//! // We create a termination condition which allows the solver to run indefinitely
275//! let mut termination = Indefinite;
276//! // And we create a search strategy (in this case, simply the default)
277//! let mut brancher = solver.default_brancher();
278//!
279//! // Then we solve to satisfaction
280//! let assumptions = vec![
281//!     predicate!(x == 1),
282//!     predicate!(y <= 1),
283//!     predicate!(y != 0),
284//! ];
285//! let result =
286//!     solver.satisfy_under_assumptions(&mut brancher, &mut termination, &assumptions);
287//!
288//! if let SatisfactionResultUnderAssumptions::UnsatisfiableUnderAssumptions(
289//!     mut unsatisfiable,
290//! ) = result
291//! {
292//!     {
293//!         let core = unsatisfiable.extract_core();
294//!
295//!         // In this case, the core should be equal to all of the assumption literals
296//!         assert_eq!(core, vec![predicate!(y == 1), predicate!(x == 1)].into());
297//!     }
298//! }
299//! ```
300//! ## Feature Flags
301//! - `gzipped-proofs` (default): Write proofs to a gzipped file.
302//! - `debug-checks`: Enable expensive assertions in the solver. Turning this on slows down the
303//!   solver by several orders of magnitude, so it is turned off by default.
304pub use pumpkin_core::*;