Pumpkin
Pumpkin is a combinatorial optimisation solver developed by the ConSol Lab at TU Delft. It is based on the (lazy clause generation) constraint programming paradigm.
Our goal is to keep the solver efficient, easy to use, and well-documented. The solver is written in pure Rust and follows Rust best practices.
A unique feature of Pumpkin is that it can produce a certificate of unsatisfiability. See our CP’24 paper for more details.
The solver currently supports integer variables and a number of (global) constraints:
- [Cumulative global constraint][crate::constraints::cumulative].
- [Element global constraint][crate::constraints::element].
- Arithmetic constraints: [linear integer (in)equalities][crate::constraints::less_than_or_equals], [integer division][crate::constraints::division], [integer multiplication][crate::constraints::times], [maximum][crate::constraints::maximum], [absolute value][crate::constraints::absolute].
- [Clausal constraints][Solver::add_clause].
We are actively developing Pumpkin and would be happy to hear from you should you have any questions or feature requests!
Using Pumpkin
Pumpkin can be used to solve a variety of problems. The first step to solving a problem is adding variables:
# use Solver;
# use OptimisationResult;
# use Indefinite;
# use ProblemSolution;
# use Constraint;
# use max;
// We create the solver with default options
let mut solver = default;
// We create 3 variables
let x = solver.new_bounded_integer;
let y = solver.new_bounded_integer;
let z = solver.new_bounded_integer;
Then we can add constraints supported by the [Solver]:
# use Solver;
# use OptimisationResult;
# use Indefinite;
# use ProblemSolution;
# use constraints;
# use Constraint;
# use max;
# let mut solver = default;
# let x = solver.new_bounded_integer;
# let y = solver.new_bounded_integer;
# let z = solver.new_bounded_integer;
// We create the constraint:
// x + y + z = 17
let c1 = solver.new_constraint_tag;
solver
.add_constraint
.post;
For finding a solution, a [TerminationCondition] and a [Brancher] should be specified, which
determine when the solver should stop searching and the variable/value selection strategy which
should be used:
# use Solver;
# use Indefinite;
# let mut solver = default;
// We create a termination condition which allows the solver to run indefinitely
let mut termination = Indefinite;
// And we create a search strategy (in this case, simply the default)
let mut brancher = solver.default_brancher;
Finding a solution to this problem can be done by using [Solver::satisfy]:
# use Solver;
# use SatisfactionResult;
# use Indefinite;
# use ProblemSolution;
# use constraints;
# use Constraint;
# use max;
# let mut solver = default;
# let x = solver.new_bounded_integer;
# let y = solver.new_bounded_integer;
# let z = solver.new_bounded_integer;
# let c1 = solver.new_constraint_tag;
# solver.add_constraint.post;
# let mut termination = Indefinite;
# let mut brancher = solver.default_brancher;
// Then we find a solution to the problem
let result = solver.satisfy;
if let Satisfiable = result else
Optimizing an objective can be done in a similar way using [Solver::optimise]; first the
objective variable and a constraint over this value are added:
# use Solver;
# use constraints;
# use Constraint;
# let mut solver = default;
# let x = solver.new_bounded_integer;
# let y = solver.new_bounded_integer;
# let z = solver.new_bounded_integer;
// We add another variable, the objective
let objective = solver.new_bounded_integer;
// We add a constraint which specifies the value of the objective
let c1 = solver.new_constraint_tag;
solver
.add_constraint
.post;
Then we can find the optimal solution using [Solver::optimise]:
# use Solver;
# use OptimisationResult;
# use Indefinite;
# use ProblemSolution;
# use constraints;
# use Constraint;
# use OptimisationDirection;
# use LinearSatUnsat;
# use max;
# use crateOptimisationProcedure;
# use SolutionReference;
# use DefaultBrancher;
# let mut solver = default;
# let x = solver.new_bounded_integer;
# let y = solver.new_bounded_integer;
# let z = solver.new_bounded_integer;
# let objective = solver.new_bounded_integer;
# let c1 = solver.new_constraint_tag;
# solver.add_constraint.post;
# solver.add_constraint.post;
# let mut termination = Indefinite;
# let mut brancher = solver.default_brancher;
// Then we solve to optimality
let callback: fn = ;
let result = solver.optimise;
if let Optimal = result else
Obtaining multiple solutions
Pumpkin supports obtaining multiple solutions from the [Solver] when solving satisfaction
problems. The same solution is prevented from occurring multiple times by adding blocking
clauses to the solver which means that after iterating over solutions, these solutions will
remain blocked if the solver is used again.
# use Solver;
# use SatisfactionResult;
# use Indefinite;
# use ProblemSolution;
# use IteratedSolution;
# use constraints;
# use Constraint;
// We create the solver with default options
let mut solver = default;
// We create 3 variables with domains within the range [0, 2]
let x = solver.new_bounded_integer;
let y = solver.new_bounded_integer;
let z = solver.new_bounded_integer;
// We create the all-different constraint
let c1 = solver.new_constraint_tag;
solver.add_constraint.post;
// We create a termination condition which allows the solver to run indefinitely
let mut termination = Indefinite;
// And we create a search strategy (in this case, simply the default)
let mut brancher = solver.default_brancher;
// Then we solve to satisfaction
let mut solution_iterator = solver.get_solution_iterator;
let mut number_of_solutions = 0;
// We keep track of a list of known solutions
let mut known_solutions = Vecnew;
loop
// There are six possible solutions to this problem
assert_eq!
Obtaining an unsatisfiable core
Pumpkin allows the user to specify assumptions which can then be used to extract an
unsatisfiable core (see [UnsatisfiableUnderAssumptions::extract_core]).
# use Solver;
# use SatisfactionResultUnderAssumptions;
# use Indefinite;
# use predicate;
# use constraints;
# use Constraint;
// We create the solver with default options
let mut solver = default;
// We create 3 variables with domains within the range [0, 2]
let x = solver.new_bounded_integer;
let y = solver.new_bounded_integer;
let z = solver.new_bounded_integer;
// We create the all-different constraint
let c1 = solver.new_constraint_tag;
solver.add_constraint.post;
// We create a termination condition which allows the solver to run indefinitely
let mut termination = Indefinite;
// And we create a search strategy (in this case, simply the default)
let mut brancher = solver.default_brancher;
// Then we solve to satisfaction
let assumptions = vec!;
let result =
solver.satisfy_under_assumptions;
if let UnsatisfiableUnderAssumptions = result