Skip to main content

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