IMT Atlantique, LS2N, TASC
-solver=choco
)Visit the website for more documentation, tutorials, javadoc , etc
int n = 8;
Model model = new Model(n + "-queens problem");
IntVar[] vs = model.intVarArray("Q", n, 1, n);
model.allDifferent(vs).post();
for(int i = 0; i < n-1; i++){
for(int j = i + 1; j < n; j++){
model.arithm(vs[i], "!=", vs[j], "-", j - i).post();
model.arithm(vs[i], "!=", vs[j], "+", j - i).post();
}
}
Solution solution = model.getSolver().findSolution();
if(solution != null){
System.out.println(solution.toString());
}
Model
instanceModel
is the central object
in the Choco-solver library
In fact, almost anything can be done via a Model
instance$^1$.
$1$: So you donβt have to know all the objects in advance.
Model m = new Model(); // 1
Model m = new Model(String name); // 2
Model m = new Model(settings settings); // 3
Model m = new Model(String name, Settings settings); // 4
Prefer choices 1 and 2 to start with.
package apetizer;
public class Example{
public static void main(String[] args){
}
}
package apetizer;
import org.chocosolver.solver.Model;
public class Example{
public static void main(String[] args){
Model myModel = new Model("My first model");
}
}
IntVar
has its values in $\mathbb{Z}^1$IntView
s relies on an IntVar
IntAffineView
BoolVar
(and BoolView
) has its values in {$0,1$}
IntVar
Task
to manage with task/interval: $s+d = e$SetVar
and SetView
(Un)DirectedGraphVar
and GraphView
RealVar
Model m = new Model();
IntVar x = m.intVar("x", 0, 4);
IntVar y = m.intVar("y", new int[]{1,3,5});
IntVar[] vs = m.intVarArray("v", 4, 1, 3);
IntVar[][] ws = m.intVarMatrix("w", 2, 2, 1, 2);
Model m = new Model();
BoolVar b = m.boolVar("b");
BoolVar[] bs = m.boolVarArray("bs", 10);
BoolVar[][] bss = m.boolVarMatrix("bss", 4, 3);
Similar APIs for other types of variables.
Model m = new Model();
IntVar x = m.intVar("x", 0, 5);
IntVar v = m.intView(2, x, -3); // v = 2.x - 3
BoolVar b = m.isEq(x, 2); // b = (x == 2)
BoolVar n = b.not(); // n = !b
And also abs(x), mu(x, 2), neg(x), isLeq(x, 3),
…
Model m = new Model();
IntVar x = m.intVar("x", 0, 4);
System.out.printf("Variable : %s = [%d, %d]\n",
x.getName(), x.getLB(), x.getUB());
System.out.printf("%s is %s instantiated\n",
x.getName(), x.isInstantiated() ? "" : "not");
outputs
Variable : x = [0, 4]
x is not instantiated
If instantiated, a variable can return its assignment value with x.getValue()
.
This clickable logo indicates that a workshop is available at Caseine
For example: $X < Y$
The goal of constraint programming is to find a combination of variable assignments that simultaneously satisfies all the specified constraints.
Constraint | Syntax | Comment |
---|---|---|
$x + y = z$ | m.arithm(x,"+",y,"=",z) |
Up to 3 variables |
$\sum_i C_i\cdot X_i = 11$ | m.scalar(X,C,"=",11) |
See also m.sum(x,op,c) |
$x\times y = z$ | m.times(x,y,z) |
Alt. Euclidean division |
$y = |x|$ | m.absolute(y,x) |
Or view: y = m.abs(x) |
$ |x-y | > 3$ | m.distance(x,y,">", 3) |
|
$z = \max(x,y)$ | m.max(z,x,y) |
Alt. $\min$ |
$\bigvee_i B_i$ | m.or(B) |
Alt. m.and(B) |
Once a constraint has been created, it must be activated or reified
Action | Syntax |
---|---|
Activate $c$ | c.post(); |
$c \iff b$ | c.reifyWith(b); BoolVar b = c.reify(); |
$c \implies b$ | c.implies(b); |
$b \implies c$ | c.impliedBy(b); |
This can be achieved with a Table constraint
Model m = new Model();
IntVar c = m.intVar("SoC", 0, 100);
IntVar v = m.intVar("cV", 1140, 1280);
Tuples tuples = new Tuples();
tuples.add(100, 1273);
tuples.add(90, 1240);
tuples.add(80, 1235);
//...
tuples.add(20, 1195);
tuples.add(10, 1151);
m.table(c, v, tuples).post();
Allowed tuples
Model m = new Model();
IntVar[] xs = m.intVarArray("x", 3, 1, 3);
// all equal
Tuples tuples = new Tuples();
tuples.add(1, 1, 1);
tuples.add(2, 2, 2);
tuples.add(3, 3, 3);
m.table(xs, tuples).post();
Forbidden tuples
Model m = new Model();
IntVar[] xs = m.intVarArray("x", 3, 1, 3);
// *not* all equal
Tuples tuples = new Tuples(false);
tuples.add(1, 1, 1);
tuples.add(2, 2, 2);
tuples.add(3, 3, 3);
m.table(xs, tuples).post();
Universal value
Like '*'
symbol in regular expression.
Model model = new Model();
IntVar[] xs = m.intVarArray("x", 3, 0, 3);
Tuples ts = new Tuples();
int star = 99;
ts.setUniversalValue(star);
ts.add(3, star, 1);
ts.add(1, 2, 3);
ts.add(2, 3, 2);
model.table(xs, ts).post();
Hybrid tuples
Model model = new Model();
IntVar[] xs = m.intVarArray("x", 3, 1, 3);
HybridTuples tuples = new HybridTuples();
tuples.add(ne(1), any(), eq(3));
tuples.add(eq(3), le(2), ne(1));
tuples.add(lt(3), eq(2), ne(b));
tuples.add(gt(2), ge(2), any());
model.table(xs, tuples).post();
It is also possible to express a constraint from a variable
Family | Syntax |
---|---|
Arithmetic | x.neg() x.abs() x.sqr() x.add(i, …) x.sub(i, …) x.div(i) x.mod(i) x.pow(i) x.dist(i) x.max(i, …) x.min(i, …) |
Relational | x.eq(i) x.ne(i) x.in(i, …) x.nin(i, …) x.lt(i) x.le(i) x.gt(i) x.ge(i) |
Logical | x.not() x.imp(r) x.iff(r) x.ift(r1, r2) x.and(r, …) x.or(r, …) x.xor(r, …) |
int
, a IntVar
or an arithmetical expression,
$(x = y + 1) \lor (x+2 \leq 6)$
IntVar x = //...
IntVar y = //...
x.eq(y.add(1)).or(x.add(2).le(6)).post();
Syntax | Ar. | Re. | Lo. |
---|---|---|---|
e.post(); |
β | β | β |
c = e.decompose(); c = e.extension(); |
β β |
β
β |
β
β |
x = e.intVar(); b = e.boolVar(); |
β
β |
β β |
β β |
an expressive and concise condition involving a non-fixed number of variables
allDifferent($x_1,x_2,\dots$)
a constraint C is often called “global” when “processing” as a whole gives better results than “processing” any conjunction of constraints that is “semantically equivalent” to C
allDifferent($x_1,x_2,x_3$) $\iff (x_1\neq x_2) \land (x_2\neq x_3) \land (x_3\neq x_1)$
cumulative(starts, durations, heights, capacity)
binpacking(bins, weigths, capacity)
circuit(successors)
Syntax | Definition |
---|---|
m.allDifferent(X) |
$x_i\neq x_j, \forall i< j$ |
m.among(n, X, v) |
$|x_i : x_i \cap v\neq \emptyset | = n$ |
m.count(y, X, n) |
$ | \{ x_i : x_i = y \} | = n$ |
m.nValues(X, n) |
$|x_i| = n$ |
Notations
Syntax | Definition |
---|---|
m.element(v, X, i, o) |
$\exists i : v = x_{i - o}$ |
m.argmax(i, o, X) |
$i \in \{j - o : x_j = \max\{x_k\}\}$ |
m.argmin(i, o, X) |
$i \in \{j - o : x_j = \max\{x_k\}\}$ |
m.max(m, X) |
$m = \max\{x_i\}$ |
m.min(m, X) |
$m = \min\{x_i\}$ |
m.inverseChanneling(X, Y) |
$ \forall i: x_i = j \iff y_j = i \quad (|X| = |Y|)$ |
Notations
Syntax | Definition |
---|---|
m.binPacking(X, S, L, o) |
$ \forall b \in \{x_i\},\sum_{i : x_i = b} S_i \leq L_b$ |
m.cumulative(A, H, c) |
$ \forall t \in \mathcal{N},\sum\{h_i : a^s_i \leq t < a^e_i\} \leq c$ |
m.diffN(X, Y, W, H, true) |
$\forall i<j, x_{i} + w_{i} \leq x_{j} \lor x_{j} + h_{j} β€ x_{i}$ $\quad\quad\quad \lor y_{i} + h_{i} \leq y_{j} \lor y_{j} + w_{j} β€ y_{i}$ |
m.knapsack(O, W, E, w, e) |
$\sum_{i} w_i \times O_i = w \land \sum_{i} e_i \times O_i = e$ |
Notations
Syntax | Definition |
---|---|
m.circuit(X) |
$\{(i, x_i) : i \neq x_i\}$ forms a circuit of size $> 1$ |
m.path(X, s, e) |
$\{(i, x_i) : i \neq x_i\}$ forms a path from $s$ to $e$ |
m.tree(X, n) |
$\{(i, x_i) : i \neq x_i\}$ is partitioned into $n$ anti-arborescences |
Notations
The solving process is managed by a unique Solver
instance attached to a model
.
Model model = new Model();
// ... problem description ...
Solver solver = model.getSolver();
if(solver.solve()){
// here you can read the variables' value
System.out.printf("%s is fixed to %d\n",
x.getName(), x.getValue());
}
A call to solver.solve()
returns true
if a solution exists, false
otherwise.
The solving process is managed by a unique Solver
instance attached to a model
.
Model model = new Model();
// ... problem description ...
Solver solver = model.getSolver();
if(solver.solve()){
// here you can read the variables' value
System.out.printf("%s is fixed to %d\n",
x.getName(), x.getValue());
}
Since the solver
stops on a solution,
all the variables are fixed and their value can be read.
The solving process is managed by a unique Solver
instance attached to a model
.
Model model = new Model();
// ... problem description ...
Solver solver = model.getSolver();
while(solver.solve()){
// here you can read the variables' value
System.out.printf("%s is fixed to %d\n",
x.getName(), x.getValue());
}
Enumerating solutions can be done in a while-loop
.
We can capture the current state of variables in a Solution
.
List<Solution> solutions = new ArrayList<>();
while(solver.solve()){
solutions.add(new Solution(model).record());
}
This can also be achieved in one line of code
Solution solution = solver.findSolution();
List<Solution> solutions = solver.findAllSolutions();
An IntVar
to maximize/minimize .
Solution bSol = solver.findOptimalSolution(obj, maximize);
List<Solution> bSols = solver.findAllOptimalSolutions(obj, max);
Branch and Propagate
β οΈ 2-way branching
Can't we just leave it to the solver?
Bring business knowledge
Help moving towards a solution
but Yes, we can
Continue as as long as necessary
input_order
, first_fail
, smallest
,…dom/wdeg
, FBA
, CHS
, ABS
, …min
, max
, med
, …geo
, luby
lc
, cos
solver.setSearch(Search.intVarSearch(
variables -> Arrays.stream(variables)
.filter(v -> !v.isInstantiated())
.min((v1, v2) -> closest(v2, map) - closest(v1, map))
.orElse(null),
v -> closest(v, map),
DecisionOperator.int_eq,
planes
));
// Override the default search strategy
solver.setSearch(
Search.lastConflict(
Search.domOverWDegSearch(vars)));
// possibly combined with restarts
solver.setLubyRestart(2, new FailCounter(model, 2), 25000);
solver.findSolution();
Have a look at the Search
factory.
It is possible to have insights on the search process
solver.showShortStatistics();
while(solver.solve()){};
Or to execute code on solutions using monitors:
solver.plugMonitor((IMonitorSolution) () -> {
// do something here
});
The search space exploration can be limited.
solver.limitTime("10s");
solver.limitSolution(5);
solver.findAllSolutions();
The first limit reached stops the search.