diff --git a/content/en/tutos/Aircraft Landing Problem/Code.md b/content/en/tutos/Aircraft Landing Problem/Code.md new file mode 100644 index 0000000..7731ac9 --- /dev/null +++ b/content/en/tutos/Aircraft Landing Problem/Code.md @@ -0,0 +1,356 @@ +--- +title: "Code" +date: 2020-02-03T13:41:29+01:00 +type: docs +math: "true" +weight: 43 +description: > + A bunch of code. +--- + +```java +// load parameters +// ... +// A new model instance +Model model = new Model("Aircraft landing"); +// Variables declaration +IntVar[] planes = IntStream + .range(0, N) + .mapToObj(i -> model.intVar("plane #" + i, LT[i][0], LT[i][2], false)) + .toArray(IntVar[]::new); +IntVar[] earliness = IntStream + .range(0, N) + .mapToObj(i -> model.intVar("earliness #" + i, 0, LT[i][1] - LT[i][0], false)) + .toArray(IntVar[]::new); +IntVar[] tardiness = IntStream + .range(0, N) + .mapToObj(i -> model.intVar("tardiness #" + i, 0, LT[i][2] - LT[i][1], false)) + .toArray(IntVar[]::new); + +IntVar tot_dev = model.intVar("tot_dev", 0, IntVar.MAX_INT_BOUND); + +// Constraint posting +// one plane per runway at a time: +model.allDifferent(planes).post(); +// for each plane 'i' +for(int i = 0; i < N; i++){ + // maintain earliness + earliness[i].eq((planes[i].neg().add(LT[i][1])).max(0)).post(); + // and tardiness + tardiness[i].eq((planes[i].sub(LT[i][1])).max(0)).post(); + // disjunctions: 'i' lands before 'j' or 'j' lands before 'i' + for(int j = i+1; j < N; j++){ + Constraint iBeforej = model.arithm(planes[i], "<=", planes[j], "-", ST[i][j]); + Constraint jBeforei = model.arithm(planes[j], "<=", planes[i], "-", ST[j][i]); + model.addClausesBoolNot(iBeforej.reify(), jBeforei.reify()); + } +} +// prepare coefficients of the scalar product +int[] cs = new int[N*2]; +for(int i = 0 ; i < N; i++){ + cs[i] = PC[i][0]; + cs[i + N] = PC[i][1]; +} +model.scalar(ArrayUtils.append(earliness, tardiness), cs, "=", tot_dev).post(); +``` + +Note that all variables could be bounded, since no constraint makes +holes in the domain. However, turning them into enumerated ones will be +required to design an efficient search strategy. + +The objective variable, 'tot\_dev' is declared with an arbitrary large +upper bound. Instead of using `Integer.MAX\_VALUE`, we call +`IntVar.MAX\_INT\_BOUND` a pre-defined parameter not too big to limit +overflow. A better solution would be to compute the real bounds of the +variable, based on LT and PC. In our case $[\\![0,117790]\\!]$ is the +smallest interval that eliminates no solution. + +The *alldifferent* constraint (line 23) is redundant with disjunction +constraints (lines 31-35). But it provides stronger filtering. + +The declaration of the disjunction (line 34) does not require to post +the constraint. Calling method like 'addClause\*' add clauses to a +specific clause store which acts as specific singleton constraint. The +code can however replaced by : + +```java +model.or(iBeforej,jBeforei).post(); +``` + +In that case, the logical expression will be transformed into a sum +constraint. Yet, $\frac{N \times (N-1}{2}$ constraints will be added to +the solver. + +A search strategy +----------------- + +Intuitively, a good strategy to solve the problem is to select first the +variable whom distance to the target landing time and the closest +possible landing time is the biggest. It tends to avoid letting a plane +with already late (resp. early) being even more late (resp. early). + +Then, for a given plane, we want to minimize the distance to the target +landing time. So we simply choose the value in its domain closest to the +target landing time. + +First, we map each plane with its target landing time: + +```java +Map map = + IntStream.range(0, N) + .boxed() + .collect(Collectors.toMap(i -> planes[i], i -> LT[i][1])); +``` + +Then, for a given plane, a function is created to look for the possible +landing time closest to the target landing time: + +```java +private static int closest(IntVar var, Map map) { + int target = map.get(var); + if (var.contains(target)) { + return target; + } else { + int p = var.previousValue(target); + int n = var.nextValue(target); + return Math.abs(target - p) < Math.abs(n - target) ? p : n; + } +} +``` + +Note that, `var.previousValue(target)` can return `Integer.MIN\_VALUE` which +indicates that there is no value before target in the domain of var +(same goes with `var.nextValue(target)` and `Integer.MAX\_VALUE)`. That's +why the absolute difference is computed, and the minimum is returned. + +Finally, the search strategy is defined: + +```java +solver.setSearch(Search.intVarSearch( + variables -> Arrays.stream(variables) + .filter(v -> !v.isInstantiated()) + .min((v1, v2) -> closest(v2, map) - closest(v1, map)) + .orElse(null), + var -> closest(var, map), + DecisionOperator.int_eq, + planes +)); +``` + +Lines 2-7: non-instantiated variables are filtered and the more distant +to the target landing time is returned. Note that if all variables are +instantiated, null is expected to indicate that the strategy runs dry. +Line 8: the closest possible landing time for a given variable is +returned. Line 9: the decision is based on the assignment operator. Left +decision branch is assignment, right decision branch (refutation) is +value removal. That is why the domain of planes must be enumerated. Line +10: the scope variables is defined. + +The three instructions (Lines2-10) are input in +`SearchStrategyFactory.intVarSearch(VariableSelector,IntValueSelector,IntVar...)` which builds in return a integer +variable search strategy. + +The resolution objective +------------------------ + +The objective is to minimize 'tot\_dev'. + +```java +// Find a solution that minimizes 'tot_dev' +Solution best = solver.findOptimalSolution(tot_dev, false); +``` + +This method attempts to find the optimal solution. + +If one wants to interact with each solution without using the unfold +resolution process, she/he can plug a solution monitor to the solver. +Such monitor implements an one-method interface called on each solution: + +```java +solver.plugMonitor((IMonitorSolution) () -> { + for (int i = 0; i < N; i++) { + System.out.printf("%s lands at %d (%d)\n", + planes[i].getName(), + planes[i].getValue(), + planes[i].getValue() - LT[i][1]); + } + System.out.printf("Deviation cost: %d\n", tot_dev.getValue()); +}); +``` + +We print here the real landing time and the distance to the target +landing time for each plane and the total deviation cost. + +The entire code +--------------- + +```java +// number of planes +int N = 10; +// Times per plane: +// {earliest landing time, target landing time, latest landing time} +int[][] LT = { + {129, 155, 559}, + {195, 258, 744}, + {89, 98, 510}, + {96, 106, 521}, + {110, 123, 555}, + {120, 135, 576}, + {124, 138, 577}, + {126, 140, 573}, + {135, 150, 591}, + {160, 180, 657}}; +// penalty cost penalty cost per unit of time per plane: +// {for landing before target, after target} +int[][] PC = { + {10, 10}, + {10, 10}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}}; +// Separation time required after i lands before j can land +int[][] ST = { + {99999, 3, 15, 15, 15, 15, 15, 15, 15, 15}, + {3, 99999, 15, 15, 15, 15, 15, 15, 15, 15}, + {15, 15, 99999, 8, 8, 8, 8, 8, 8, 8}, + {15, 15, 8, 99999, 8, 8, 8, 8, 8, 8}, + {15, 15, 8, 8, 99999, 8, 8, 8, 8, 8}, + {15, 15, 8, 8, 8, 99999, 8, 8, 8, 8}, + {15, 15, 8, 8, 8, 8, 99999, 8, 8, 8}, + {15, 15, 8, 8, 8, 8, 8, 999999, 8, 8}, + {15, 15, 8, 8, 8, 8, 8, 8, 99999, 8}, + {15, 15, 8, 8, 8, 8, 8, 8, 8, 99999}}; + +Model model = new Model("Aircraft landing"); +// Variables declaration +IntVar[] planes = IntStream + .range(0, N) + .mapToObj(i -> model.intVar("plane #" + i, LT[i][0], LT[i][2], false)) + .toArray(IntVar[]::new); +IntVar[] earliness = IntStream + .range(0, N) + .mapToObj(i -> model.intVar("earliness #" + i, 0, LT[i][1] - LT[i][0], false)) + .toArray(IntVar[]::new); +IntVar[] tardiness = IntStream + .range(0, N) + .mapToObj(i -> model.intVar("tardiness #" + i, 0, LT[i][2] - LT[i][1], false)) + .toArray(IntVar[]::new); +IntVar tot_dev = model.intVar("tot_dev", 0, IntVar.MAX_INT_BOUND); +// Constraint posting +// one plane per runway at a time: +model.allDifferent(planes).post(); +// for each plane 'i' +for (int i = 0; i < N; i++) { + // maintain earliness + earliness[i].eq((planes[i].neg().add(LT[i][1])).max(0)).post(); + // and tardiness + tardiness[i].eq((planes[i].sub(LT[i][1])).max(0)).post(); + // disjunctions: 'i' lands before 'j' or 'j' lands before 'i' + for (int j = i + 1; j < N; j++) { + Constraint iBeforej = model.arithm(planes[i], "<=", planes[j], "-", ST[i][j]); + Constraint jBeforei = model.arithm(planes[j], "<=", planes[i], "-", ST[j][i]); + model.addClausesBoolNot(iBeforej.reify(), jBeforei.reify()); // no need to post + } +} +// prepare coefficients of the scalar product +int[] cs = new int[N * 2]; +for (int i = 0; i < N; i++) { + cs[i] = PC[i][0]; + cs[i + N] = PC[i][1]; +} +model.scalar(ArrayUtils.append(earliness, tardiness), cs, "=", tot_dev).post(); +// Resolution process +Solver solver = model.getSolver(); +solver.plugMonitor((IMonitorSolution) () -> { + for (int i = 0; i < N; i++) { + System.out.printf("%s lands at %d (%d)\n", + planes[i].getName(), + planes[i].getValue(), + planes[i].getValue() - LT[i][1]); + } + System.out.printf("Deviation cost: %d\n", tot_dev.getValue()); +}); +Map map = IntStream + .range(0, N) + .boxed() + .collect(Collectors.toMap(i -> planes[i], i -> LT[i][1])); +solver.setSearch(Search.intVarSearch( + variables -> Arrays.stream(variables) + .filter(v -> !v.isInstantiated()) + .min((v1, v2) -> closest(v2, map) - closest(v1, map)) + .orElse(null), + var -> closest(var, map), + DecisionOperatorFactory.makeIntEq(), + planes +)); +solver.showShortStatistics(); +solver.findOptimalSolution(tot_dev, false); +``` + +The best solution found is: + +``` +plane #0 lands at 165 (10) +plane #1 lands at 258 (0) +plane #2 lands at 98 (0) +plane #3 lands at 106 (0) +plane #4 lands at 118 (-5) +plane #5 lands at 134 (-1) +plane #6 lands at 126 (-12) +plane #7 lands at 142 (2) +plane #8 lands at 150 (0) +plane #9 lands at 180 (0) +Deviation cost: 700 +Model[Aircraft landing], 7 Solutions, Minimize tot_dev = 700, Resolution time 0,326s, 906 Nodes (2 781,1 n/s), 1756 Backtracks, 883 Fails, 0 Restarts +Model[Aircraft landing], 7 Solutions, Minimize tot_dev = 700, Resolution time 12,608s, 246096 Nodes (19 519,6 n/s), 492179 Backtracks, 246083 Fails, 0 Restarts +``` + +The second to last line of the console sums up the resolution statistics +when the last solution was found : + +- this is the twelfth solution, its cost is 700 ('tot\_dev'), it took + 326ms and 906 nodes were opened to find it. + +The last line of the console sums up to resolution statistics of the +entire resolution, including optimality proof: + +- 7 solutions were found, 12,608s seconds and 246096 nodes were needed +to explore the entire search space and prove the optimality of the last +solution found. + +If the plane selection is turned upside down (less late (early) plane is +selected first) the resolution statistics change a bit: + +``` +Model[Aircraft landing], 12 Solutions, Minimize tot_dev = 700, Resolution time 0,514s, 2147 Nodes (4 180,8 n/s), 4222 Backtracks, 2119 Fails, 0 Restarts +Model[Aircraft landing], 12 Solutions, Minimize tot_dev = 700, Resolution time 4,505s, 71596 Nodes (15 892,4 n/s), 143169 Backtracks, 71573 Fails, 0 Restarts +``` + +We can see that more intermediate solutions were found (12 vs. 7) and +that it took more time to find the best solution (514ms and 2147 nodes +vs. 326ms and 906 nodes) but the optimality is proven faster (4,505s and +71596 nodes vs. 12,608s and 246096 nodes). + +This demonstrates that a strategy that is quick to produce the best +solution may be unable to prove its optimality efficiently. + +Things to remember +------------------ + +- A good estimation of the variables domain is important to limit + overflow and reduce the induce search space. +- Redundant constraints can reduce the search space too, but can also + slow down the propagation loop. Their benefit should be evaluated. +- Most of the time adding clauses instead of logical constraints + limits the memory footprint and provide an equivalent filtering + quality. +- A decision, result of a search strategy, is a combination of a + variable, a value and an operator. +- Monitors can be plugged to the solver to interact with the search, + specifically on solution. +- Accurate search strategy design is the key to efficient resolution. \ No newline at end of file diff --git a/content/en/tutos/Aircraft Landing Problem/Description.md b/content/en/tutos/Aircraft Landing Problem/Description.md new file mode 100644 index 0000000..94af727 --- /dev/null +++ b/content/en/tutos/Aircraft Landing Problem/Description.md @@ -0,0 +1,75 @@ +--- +title: "Description" +date: 2020-02-03T13:41:20+01:00 +type: docs +weight: 41 +description: > + A description of the problem to model and solve. +--- + +Given a set of planes and runways, the objective is to minimize the +total (weighted) deviation from the target landing time for each plane. + +There are costs associated with landing either earlier or later than a +target landing time for each plane. + +Each plane has to land on one of the runways within its predetermined +time windows such that separation criteria between all pairs of planes +are satisfied. + +This type of problem is a large-scale optimization problem, which occurs +at busy airports where making optimal use of the bottleneck resource +(the runways) is crucial to keep the airport operating smoothly. + +See [this +page](http://people.brunel.ac.uk/~mastjjb/jeb/orlib/airlandinfo.html) +for more details. + +Input data +---------- + +We consider here the following input (in java): + +```java +// number of planes +int N = 10; +// Times per plane: {earliest landing time, target landing time, latest landing time} +int[][] LT = { + {129, 155, 559}, + {195, 258, 744}, + {89, 98, 510}, + {96, 106, 521}, + {110, 123, 555}, + {120, 135, 576}, + {124, 138, 577}, + {126, 140, 573}, + {135, 150, 591}, + {160, 180, 657}}; +// penalty cost penalty cost per unit of time per plane: {for landing before target, after target} +int[][] PC = { + {10, 10}, + {10, 10}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}, + {30, 30}}; + +// Separation time required after i lands before j can land +int[][] ST = { + {99999, 3, 15, 15, 15, 15, 15, 15, 15, 15}, + {3, 99999, 15, 15, 15, 15, 15, 15, 15, 15}, + {15, 15, 99999, 8, 8, 8, 8, 8, 8, 8}, + {15, 15, 8, 99999, 8, 8, 8, 8, 8, 8}, + {15, 15, 8, 8, 99999, 8, 8, 8, 8, 8}, + {15, 15, 8, 8, 8, 99999, 8, 8, 8, 8}, + {15, 15, 8, 8, 8, 8, 99999, 8, 8, 8}, + {15, 15, 8, 8, 8, 8, 8, 99999, 8, 8}, + {15, 15, 8, 8, 8, 8, 8, 8, 99999, 8}, + {15, 15, 8, 8, 8, 8, 8, 8, 8, 99999}}; +``` + + diff --git a/content/en/tutos/Aircraft Landing Problem/Math.md b/content/en/tutos/Aircraft Landing Problem/Math.md new file mode 100644 index 0000000..8f7de56 --- /dev/null +++ b/content/en/tutos/Aircraft Landing Problem/Math.md @@ -0,0 +1,85 @@ +--- +title: "Math" +date: 2020-02-03T13:41:25+01:00 +type: docs +math: "true" +weight: 42 +description: > + A mathematical model of the problem. +--- + +Variables +--------- + +- An integer variable $\text{plane}\_i$ per plane *i* indicates its + landing time. + + $$\forall i \in [1,10],\\, \text{plane}\_i = [\\![LT_{i,0},LT_{i,2}]\\!]$$ + +- An integer variable $\text{earliness}\_j$ per plane *i* indicates how + early a plane lands. + + $$\forall i \in [1,10],\\, \text{earliness}\_i = [\\![0,LT_{i,1} - LT_{i,0}]\\!]$$ + +- An integer variable $\text{tardiness}\_j$ per plane *i* indicates how + late a plane lands. + + $$\forall i \in [1,10],\\, \text{tardiness}\_i = [\\![0,LT_{i,2} - LT_{i,1}]\\!]$$ + +- An integer variable $tot_{dev}$ totals all costs: + + $$tot\_{dev} = [\\![0, {+\infty})$$ + + +{{%alert title="Remark" color="primary"%}} +With the current input data, there is no need to distinguish earliness +and tardiness since penalty costs are symetric. A simple distance +between the target landing time and the real landing time is enough. +{{%/alert%}} + +Constraints +----------- + +- One plane per runway at a time: + + $$\forall i,j \in [1,10]^2,\\, i\ne j, \text{plane}\_{i} \ne \text{plane}\_{j}$$ + + We saw this type of constraint before, it is an *alldifferent* + constraint. + +- the earliness of a plane *i*: + + $$\forall i \in [1,10],\\, \text{earliness}\_i = max(0,-\text{plane}\_i + \text{LT}\_{i,1})$$ + + When the plane is on time or late, its earliness is equal to 0. + Otherwise, a positive value is computed that corresponds to the + difference between the real landing time and the target landing + time. + +- the tardiness of a plane *i*: + + $$\forall i \in [1,10],\\, \text{tardiness}\_i = max(0,\text{plane}\_i - \text{LT}\_{i,1})$$ + + When the plane is on time or early, its tardiness is equal to 0. + Otherwise, a positive value is computed that corresponds to the + difference between the real landing time and the target landing + time. + +- the separation time required between two planes has to be satisfied: + + $$\forall i,j \in [1,10]^2,\\, i\ne j, (\text{plane}\_{i} + \text{ST}\_{i,j} \le \text{plane}\_{j}) \oplus (\text{plane}\_{j} + \text{ST}\_{j,i} \le \text{plane}\_{i})$$ + + If plane *i* lands before plane *j* then plane *j* lands at least + `ST[i][j]` after plane *i*. Otherwise, plane *j* lands first and plane + *i* waits at least `ST[j][i]` after it to land. The two conditions + cannot hold together because of the XOR logical operator. In this + case a simple inequality constraint fits the need. + +- the deviation cost has then to be maintained: + + $$tot\_{dev} = \sum_{i = 1}^{10} \text{PC}\_{i,0} \cdot \text{earliness}\_i + \text{PC}\_{i,1} \cdot \text{tardiness}\_i$$ + +Objective +--------- + +The objective is to find a solution that minimizes tot\_dev. \ No newline at end of file diff --git a/content/en/tutos/Aircraft Landing Problem/_index.md b/content/en/tutos/Aircraft Landing Problem/_index.md new file mode 100644 index 0000000..321db72 --- /dev/null +++ b/content/en/tutos/Aircraft Landing Problem/_index.md @@ -0,0 +1,10 @@ +--- +title: "Airplane Landing Problem" +date: 2020-02-03T13:41:10+01:00 +type: docs +weight: 40 +description: > + Become an air traffic controller. +--- + +You can consider this tutorial as a modeling exercise. To do so, just read the description and then compare your mathematical model with the one proposed. In the same way, you can start by coding your own model in Choco-solver before reading the proposed one. \ No newline at end of file diff --git a/content/en/tutos/Constraints.md b/content/en/tutos/Constraints.md new file mode 100644 index 0000000..c3cdc9a --- /dev/null +++ b/content/en/tutos/Constraints.md @@ -0,0 +1,540 @@ +--- +title: "Designing a constraint" +date: 2020-02-05 +type: docs +math: "true" +weight: 70 +description: > + Steps to follow to create your own constraint. +--- + + +In this part, we are going to see how to create a constraint to be used +by Choco-solver. The work will be based on the sum constraint, more +specifically: $\sum_{i = 1}^{n} x_i \leq b$ where +$x_i = [\underline{x_i},\overline{x_i}]$ are distinct variables and +where $b$ is a constant. + +[Bounds Consistency Techniques for Long Linear +Constraint](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.8.8962) +by W.Harvey and J.Schimpf described in details how such a constraint is +implemented and will serve as a basis of this tutorials. + +{{% alert title="Important" color="secondary"%}} +The implementation presented here can be improved in many ways but +that is not the goal this tutorial to discuss improvements but to show + what is important to know when creating a constraint. +{{%/alert%}} + + +The first filtering algorithm they depicted in the article is roughly +the following: + +- First, compute $F = b - \sum_{i = 1}^{n} \underline{x_i}$ +- then, update variables domain, + $\forall i \in [1,n], x_i \leq F + \underline{x_i}$ + +Note that if $F < 0$ the constraint is unsatisfiable. + +A first implementation +---------------------- + +When one needs to declare its own constraint, actually, he needs to +create a propagator. Indeed, in Choco-solver, a constraint is a container which +is composed of propagators, and each propagator can +eliminate values from domain variables. So the first step will be to +create a java class that extends `Propagator`. The generic +parameter `` indicates that the propagator only manages integer +variable. Set it to BoolVar, SetVar or Variable are possible +alternatives. + +Once the class is created, a constructor is needed plus two methods : + +- `public void propagate(int evtmask) throws ContradictionException` + where the filtering algorithm will be applied, +- `public ESat isEntailed()` where the entailment/satisfaction of the + propagator is checked. + +We now describe how these two methods can be implemented, plus an +optional yet important method and the constructor parametrization. + +### Entailment + +For debugging purpose or to enable constraint reification, a method +named `isEntailed()` has to be implemented. The former is mainly used when +implementing the constraint to make sure that found solutions respect +the constraint specifications. The latter is called to valuate the +boolean variable attached to a propagator when it is reified. The method +returns `ESat.TRUE`, `ESat.FALSE` or `ESat.UNDEFINED` when respectively with +respect to the current domain of the variables, the propagator can +always be satisfied however they are instantiated, the propagator can +never be satisfied and nothing can be deduced. + +For example, consider the constraint $c = (x_1 + x_2 \leq 10)$ and the +three following states: + +- $x_1 = [1,2], x_2 = [1,2]$ : the method returns `ESat.TRUE` since all + combinations satisfy c, +- $x_1 = [22,23], x_2 = [10,12]$ : the method returns `ESat.FALSE` since + no combination satisfies c and +- $x_1 = [1,10], x_2 = [1,10]$ : the method returns `ESat.UNDEFINED` + since some combinations satisfy $c$, other don't. + +{{%alert title="Info" color="primary"%}}> +When an instance of a propagator is created, an array of its variables +is automatically created and named vars. The order of elements of +'vars' shouldn't be modified: each variable knows its position in each +of its propagators, modifying a position is only made by the solver +itself. +{{%/alert%}} + +The entailment method can be implemented as is: + +```java +Override +public ESat isEntailed() { + int sumUB = 0, sumLB = 0; + for (int i = 0; i < vars.length; i++) { + sumLB += vars[i].getLB(); + sumUB += vars[i].getUB(); + } + if (sumUB <= b) { + return ESat.TRUE; + } + if (sumLB > b) { + return ESat.FALSE; + } + return ESat.UNDEFINED; +} +``` + +### Filtering algorithm + +A propagator's first objective is to remove, from its variables domain, +values that cannot belong to any solutions. This is the role of the +propagate(int m) method. This method bases its deductions on the current +domain of the variables and can update their domain on the fly. The +expected state of this method exit is called a 'fix-point'. + +{{% alert title="Info" color="primary" %}} +A local fix-point (wrt to a propagator) is reached when no more deductions can be done by a propagator on its variables. +A global fix point (*wrt* to a model) is reached when no more deductions can be done by any propagator on all variables. +{{%/alert%}} + +Indeed, a propagator 'p' is not notified of its modifications but only +those triggered by other propagators which modified at least one +variable of 'p'. Each time one, at least, of its variable is modified, +the satisfaction of a propagator need to check along with some +filtering, if any, based on earlier modification. + +Applying filtering rules can lead to a contradiction. In that case, the +solver resumes after the filtering algorithm is stopped and manages to +undo domain modification. Since restoring previous states is managed by +the solver, it can safely be ignored when creating a propagator. + +In the case of the sum constraint, $F$ is computed first, then fast check +of $F$ is made to check obvious unsatisfaction and eventually a loop is +operated over the variables to make sure that each upper bound is +correct wrt to $F$. A simple loop is enough since $F$ is computed reading +$\overline{x_i}$ and writing $\underline{x_i}$. + +Note that the method can throw an exception. An exception denotes that a +failure is detected and the execution has to be stopped. In our case, if +$F < 0$ an exception should be thrown. In other cases, the methods that +modify the variables domain can thrown such an exception too, when for +example, the domain becomes empty. + +The filtering method can be implemented as is: + +```java +@Override +public void propagate(int evtmask) throws ContradictionException { + int sumLB = 0; + for (int i = 0; i < vars.length; i++) { + sumLB += vars[i].getLB(); + } + int F = b - sumLB; + if (F < 0) { + fails(); + } + for (int i = 0; i < vars.length; i++) { + int lb = vars[i].getLB(); + int ub = vars[i].getUB(); + if (ub - lb > F) { + vars[i].updateUpperBound(F + lb, this); + } + } +} +``` + +The parameter of the method is ignored for now. On line 9, since the +condition of unsatisfaction is met, a `ContradictionException` is thrown +by calling `fails()`. On line 16, the $i^{th}$ variable upper bound is +updated. If the new value is greater or equal to than the current upper +bound of the variable, nothing happens. If not, the variable is +modified. If the new upper bound is lesser than the current lower bound, +a `ContradictionException` is thrown automatically. Otherwise, the old +upper bound is stored (for future restoration), the new upper bound is +set and the propagators' list of the variable is iterated to inform each +of them (except the one that triggers the event) that the variable +domain has changed which can question their local fix-point. + + +{{% alert title="Important" color="secondary" %}} +An `IntVar` can be modified in many ways: instantiation, upper bound + modification, lower bound modification or value removal(s). These + modifications can be achieved calling : + `instantiateTo(...)`,`updateUpperBound(...)`, `updateLowerBound(...)`, `removeValue(...)`, + `removeValues(...)`, ... +{{%/alert%}} + +{{%alert title="Some events can be promoted" color="primary"%}} + +For instance, when the new upper bound of a variable becomes equal +to its current lower bound, the upper bound modification is +promoted to an instantiation. The same goes with the new lower +bound being equal to the current upper bound. Or when a value +removal affects one bound, it is promoted to a bound modification +(which in turn can be promoted to instantiation). + +When the term 'value removal' is used it qualifies a hole in the +middle of a variable domain, otherwise, due to promotion, the most +accurate term is used. +{{%/alert%}} + +### Propagation conditions (optional) + +When a variables is modified, the type of *event* the modification +corresponds is declared. For example when the upper bound of a variables +is decreased, the event indicates `DEC_UPP`. + +Not all types of event is relevant for all propagators and each of them +can give its filtering conditions. By default, a propagator is informed +of all type of modifications. + +In our case, nothing can be done on value removal nor on upper bound +modification. Thus, the following method can be override (note that is +optional but leads to better performances): + +```java +@Override +public int getPropagationConditions(int vIdx) { + return IntEventType.combine(IntEventType.INSTANTIATE, IntEventType.INCLOW); +} +``` + +Note that this method is called statically on each of its variables +(denoted by `vIdx`) when posting the constraint to the model. Some +propagators can thus declare distinct propagation conditions for each +variable. + +### Constructor + +Finally, any propagator should extends Propagator which is an abstract +class and a call to super is expected as first instruction of the +constructor. + +`Propagator` abstract class provides three constructors but we will only +depict one, the most important: +`Propagator(V[] vars, PropagatorPriority priority, boolean reactToFineEvt)`. + +The first argument is the list of variables, here an array of `IntVar`. +The list of all variables the propagator can react on should be passed +here. Consider that, with few exceptions, all variables of the +propagator are expected. + +The second parameter considers the filtering algorithm arity or +complexity. There are seven ordered levels of priority, the three first +ones (arity levels) are `UNARY`, `BINARY` and `TERNARY`. The three following +ones (complexity levels) are `LINEAR`, `QUADRATIC`, `CUBIC`. Actually a +`TERNARY` priority propagator is expected to run faster than a `QUADRATIC` +priority one. So, considering the complexity instead of the arity may be +more relevant when the filtering algorithm is very costly even if the +propagator relies on only three variables. + +The third parameter indicates if the propagator is able to react on fine +events. This parameter will be presented in more details later on. + +In our case, the input parameters are the array of IntVar 'x', the +priority is based on the complexity which is linear in the number of +variables and false. In addition, the constant 'b' needs to be stored +too. + +``` java +/** + * Constructor of the specific sum propagator : x1 + x2 + ... + xn <= b + * @param x array of integer variables + * @param b a constant + */ +public MyPropagator(IntVar[] x, int b) { + super(x, PropagatorPriority.LINEAR, false); + this.b = b; +} +``` + +### MyPropagator + +A basic yet sound propagator which ensures that the sum of all variables +is less than or equal to a constant is declared below. + +```java +public class MyPropagator extends Propagator { + + /** + * The constant the sum cannot be greater than + */ + final int b; + + /** + * Constructor of the specific sum propagator : x1 + x2 + ... + xn <= b + * @param x array of integer variables + * @param b a constant + */ + public MyPropagator(IntVar[] x, int b) { + super(x, PropagatorPriority.LINEAR, false); + this.b = b; + } + + @Override + public int getPropagationConditions(int vIdx) { + return IntEventType.combine(IntEventType.INSTANTIATE, IntEventType.INCLOW); + } + + @Override + public void propagate(int evtmask) throws ContradictionException { + int sumLB = 0; + for (IntVar var : vars) { + sumLB += var.getLB(); + } + int F = b - sumLB; + if (F < 0) { + fails(); + } + for (IntVar var : vars) { + int lb = var.getLB(); + int ub = var.getUB(); + if (ub - lb > F) { + var.updateUpperBound(F + lb, this); + } + } + } + + @Override + public ESat isEntailed() { + int sumUB = 0, sumLB = 0; + for (IntVar var : vars) { + sumLB += var.getLB(); + sumUB += var.getUB(); + } + if (sumUB <= b) { + return ESat.TRUE; + } + if (sumLB > b) { + return ESat.FALSE; + } + return ESat.UNDEFINED; + } +} +``` + +This first implementation outlines key concepts a propagator required. +The entailment method should not ignored since it is helpful (even +essential) to check the correctness of the implementation. The optional +one which describes the propagation conditions can sometimes reduce the +number of times a propagator is called without deducing new information +(domain modifications or failure). + +A more complex version +---------------------- + +Based on [Bounds Consistency Techniques for Long Linear +Constraint](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.8.8962), +the first version can be improved in some ways. + +We will consider first to desactivate the propagator when some +conditions are satisfied, then we will show how backtrackable structures +can be used and finally how a propagator can react to fine events. + +### Reduce to silence + +An interesting feature available by default is the capacity to set +passive a propagator that is entailed (i.e., is always true). Indeed, if +all variables domain are in such state that any combinations satisfy the +constraint, the propagator can be ignored in the propagation loop since +it will not filter values nor fail. + +In our case, this happens when the sum of the upper bounds is equal to +or less than 'b'. If so, the propagator can safely be set to a passivate +state in which it will not be informed of any new modifications +occurring **in the current search sub-tree** (i.e., the propagator will +be reactivated automatically on backtrack). + +The filtering method can be modified like that: + +```java +@Override +public void propagate(int evtmask) throws ContradictionException { + int sumLB = 0; + for (int i = 0; i < vars.length; i++) { + sumLB += vars[i].getLB(); + } + int F = b - sumLB; + if (F < 0) { + fails(); + } + int sumUB = 0; + for (int i = 0; i < vars.length; i++) { + int lb = vars[i].getLB(); + int ub = vars[i].getUB(); + if (ub - lb > F) { + vars[i].updateUpperBound(F + lb, this); + } + sumUB += vars[i].getUB(); + } + int E = sumUB - b; + if (E <= 0) { + this.setPassive(); + } +} +``` + +Line 18, a counter is updated with the sharpest upper bound of each +variables. Line 21-23, if the condition is satisfied, the propagator is +entailed and set to a passive state. + +{{%alert title="Info" color="primary"%}} +We could also consider updating the propagation conditions to +integrate upper bound modifications. Doing so, when one variable upper +bound is modified, the entailment condition could be checked earlier. +{{%/alert%}} + +### Incrementally updating $F$ + +One may have noted that F is always computed as first step of +`propagate(int evtmask)` method. On cases where few bounds are updated, +there could be a benefit to incrementally compute $F$. + +To compute $F$ in an incremental way, three steps are needed: +1. creating a *backtrackable* `int` to record $F$ but also variables' lower bound +2. initializing it on `propagate(int evtmask)` first call +3. anytime a variable is being modified, maintaining $F$ + +First, a `IStateInt` object and an `IStateInt` array are declared as class +variables. In the propagator's constructor, through the `Model`, the +objects are initialized: + +```java +/** + * The constant the sum cannot be greater than + */ +final int b; + +/** + * object to store F in an incremental way. + * Corresponds to a backtrackable int. + */ +final IStateInt F; + +/** + * array to store variables' previous lower bound. + * each cell is a backtrackable int. + */ +final IStateInt[] prev_lbs; + +/** + * Constructor of the specific sum propagator : x1 + x2 + ... + xn <= b + * @param x array of integer variables + * @param b a constant + */ +public MyPropagator(IntVar[] x, int b) { + super(x, PropagatorPriority.LINEAR, false); + this.b = b; + this.F = this.model.getEnvironment().makeInt(0); + this.prev_lbs = new IStateInt[x.length]; + for(int i = 0 ; i < x.length; i++){ + prev_lbs[i] = this.model.getEnvironment().makeInt(0); + } +} +``` + +$F$ is created with value 0; its correct value will be set on the first call +to `propagate(int evtmask)` method. Same goes with prev\_ubs. Any +backtrackable primitive or operation is created thanks to the +*environment* attached to the model. This ensures the integrity of the +structure when backtracks occur. + +The role of prev\_ubs is to store the value of each variable lower +bound. Then, anytime a variable lower bound is modified, its value can +be retrieved and substracted from the current value to update $F$. + +Second, $F$ is initialized in the first call to `propagate(int evtmask)` +method. This is where the value of evtmask is helpful. It can take 2 +distinct values: one is dedicated to a full propagation, the other to a +custom propagation. A full propagation is run on the initial propagation +call, when each propagator is awaken by the solver. Then, if the +propagator was declared not reacting to fine events (last parameter of +the super constructor), full propagation is always run. On the other +hand, if the propagator reacts to fine events, which will be the case +for now, the initial propagation is kept full but then the main entry +point of the filtering algorithm will be +`propagate(int vIdx, int evtmask)` method (with **two** arguments). This +method reacts to fine events, that means all variables modifications +will be given as input thanks to the variable's index in vars (`vIdx`) and +the event mask which is can be a combination of event types, like in +propagation conditions. + +Most of the time, this method is decomposed into a fast but naive +filtering algorithm and a delayed call to a custom, presumably not fast, +filtering algorithm. But it can be made of no filtering at all (that's +the case here) or no delayed call to custom filtering algorithm. + +In our case, we will only incrementally maintain $F$ and then delegate the +filtering to the custom propagation. + +```java +private void prepare(){ + int sumLB = 0; + for(int i = 0 ; i < vars.length; i++){ + sumLB += vars[i].getLB(); + // set the current lower bound in 'prev_lbs' + prev_lbs[i].set(vars[i].getLB()); + } + // set the value of F + F.set(b - sumLB); +} + +@Override +public void propagate(int vIdx, int mask) throws ContradictionException { + // 1. get the current lower bound of the modified variable + int lb = vars[vIdx].getLB(); + // 2. update F with the difference between old and new lower bound + F.add(lb - prev_lbs[vIdx].get()); + // 3. set the new lower bound + prev_lbs[vIdx].set(lb); + // 4. delegate the filtering later on + forcePropagate(PropagatorEventType.CUSTOM_PROPAGATION); +} + +@Override +public void propagate(int evtmask) throws ContradictionException { + if(PropagatorEventType.isFullPropagation(evtmask)){ + // First call to the filtering algorithm, F is not up-to-date + // so prepare initialize its value and 'prev_lbs' + prepare(); + } + if (F.get() < 0) { + fails(); + } + for (IntVar var : vars) { + int lb = var.getLB(); + int ub = var.getUB(); + if (ub - lb > F.get()) { + var.updateUpperBound(F.get() + lb, this); + } + } +} +``` + +A call to `forcePropagate(int evtmask)` will call `propagate(int evtmask)` +only when all fine events are received. This ensures that F is set to +the correct value before filtering forbidden values. diff --git a/content/en/tutos/First example/First model.md b/content/en/tutos/First example/First model.md index 4f67492..6e95487 100644 --- a/content/en/tutos/First example/First model.md +++ b/content/en/tutos/First example/First model.md @@ -2,6 +2,7 @@ title: "First Model" date: 2020-01-31T11:47:58+01:00 type: docs +math: "true" weight: 11 description: > Let's start with the famous 8-queen problem. @@ -68,7 +69,7 @@ solution. The values a variable can take is defined by its domain. Here, in a solution, there will be exactly one queen per row (and per column). So, a modelling trick is to fix the row a queen can go to and only question on their column. Thus, there will be *n* queens (one per -row), each of them to be assigned to one column, among $[1,n]$. +row), each of them to be assigned to one column, among $\[1,n\]$. Lines 3 and 5 managed to create variables and their domain. diff --git a/content/en/tutos/Golomb Ruler/Code.md b/content/en/tutos/Golomb Ruler/Code.md new file mode 100644 index 0000000..c2266db --- /dev/null +++ b/content/en/tutos/Golomb Ruler/Code.md @@ -0,0 +1,134 @@ +--- +title: "Code" +date: 2020-02-05T16:01:13+01:00 +type: docs +math: "true" +weight: 63 +description: > + A bunch of code. +--- + +A model +------- + +```java +int m = 10; +// A new model instance +Model model = new Model("Golomb ruler"); + +// VARIABLES +// set of marks that should be put on the ruler +IntVar[] ticks = model.intVarArray("a", m, 0, 999, false); +// set of distances between two distinct marks +IntVar[] diffs = model.intVarArray("d", (m * (m - 1)) / 2, 0, 999, false); + +// CONSTRAINTS +// the first mark is set to 0 +model.arithm(ticks[0], "=", 0).post(); + +for (int i = 0, k = 0 ; i < m - 1; i++) { + // // the mark variables are ordered + model.arithm(ticks[i + 1], ">", ticks[i]).post(); + for (int j = i + 1; j < m; j++, k++) { + // declare the distance constraint between two distinct marks + model.scalar(new IntVar[]{ticks[j], ticks[i]}, new int[]{1, -1}, "=", diffs[k]).post(); + // redundant constraints on bounds of diffs[k] + model.arithm(diffs[k], ">=", (j - i) * (j - i + 1) / 2).post(); + model.arithm(diffs[k], "<=", ticks[m - 1], "-", ((m - 1 - j + i) * (m - j + i)) / 2).post(); + } +} +// all distances must be distinct +model.allDifferent(diffs, "BC").post(); +//symmetry-breaking constraints +model.arithm(diffs[0], "<", diffs[diffs.length - 1]).post(); +``` + +A search strategy +----------------- + +A simple but efficient strategy to guide the search is to select the +mark variable in lexicographical order and to instantiate each of them +to its lower bound. + +```java +Solver solver = model.getSolver(); +solver.setSearch(Search.inputOrderLBSearch(ticks)); +``` + +The resolution objective +------------------------ + +The objective is to minimize the last mark. + +```java +// Find a solution that minimizes the last mark +solver.findOptimalSolution(ticks[m - 1], false); +``` + +This method attempts to find the optimal solution. + +The entire code +--------------- + +```java +int m = 10; +// A new model instance +Model model = new Model("Golomb ruler"); + +// VARIABLES +// set of marks that should be put on the ruler +IntVar[] ticks = ticks = model.intVarArray("a", m, 0, 999, false); +// set of distances between two distinct marks +IntVar[] diffs = model.intVarArray("d", (m * (m - 1)) / 2, 0, 999, false); + +// CONSTRAINTS +// the first mark is set to 0 +model.arithm(ticks[0], "=", 0).post(); + +for (int i = 0, k = 0 ; i < m - 1; i++) { + // // the mark variables are ordered + model.arithm(ticks[i + 1], ">", ticks[i]).post(); + for (int j = i + 1; j < m; j++, k++) { + // declare the distance constraint between two distinct marks + model.scalar(new IntVar[]{ticks[j], ticks[i]}, new int[]{1, -1}, "=", diffs[k]).post(); + // redundant constraints on bounds of diffs[k] + model.arithm(diffs[k], ">=", (j - i) * (j - i + 1) / 2).post(); + model.arithm(diffs[k], "<=", ticks[m - 1], "-", ((m - 1 - j + i) * (m - j + i)) / 2).post(); + } +} +// all distances must be distinct +model.allDifferent(diffs, "BC").post(); +//symmetry-breaking constraints +model.arithm(diffs[0], "<", diffs[diffs.length - 1]).post(); + +Solver solver = model.getSolver(); +solver.setSearch(Search.inputOrderLBSearch(ticks)); +// show resolution statistics +solver.showShortStatistics(); +// Find a solution that minimizes the last mark +solver.findOptimalSolution(ticks[m - 1], false); +``` + +The trace of the execution is roughly: + +``` +Model[Golomb ruler], 1 Solutions, MINIMIZE a[9] = 80, Resolution time 0,017s, 10 Nodes (593,7 n/s), 0 Backtracks, 0 Fails, 0 Restarts +Model[Golomb ruler], 2 Solutions, MINIMIZE a[9] = 75, Resolution time 0,026s, 18 Nodes (696,8 n/s), 14 Backtracks, 7 Fails, 0 Restarts +Model[Golomb ruler], 3 Solutions, MINIMIZE a[9] = 73, Resolution time 0,032s, 30 Nodes (949,9 n/s), 36 Backtracks, 17 Fails, 0 Restarts +Model[Golomb ruler], 4 Solutions, MINIMIZE a[9] = 72, Resolution time 0,040s, 53 Nodes (1 324,0 n/s), 80 Backtracks, 40 Fails, 0 Restarts +Model[Golomb ruler], 5 Solutions, MINIMIZE a[9] = 70, Resolution time 0,054s, 95 Nodes (1 773,2 n/s), 162 Backtracks, 79 Fails, 0 Restarts +Model[Golomb ruler], 6 Solutions, MINIMIZE a[9] = 68, Resolution time 0,065s, 161 Nodes (2 487,9 n/s), 292 Backtracks, 144 Fails, 0 Restarts +Model[Golomb ruler], 7 Solutions, MINIMIZE a[9] = 66, Resolution time 0,082s, 288 Nodes (3 529,9 n/s), 546 Backtracks, 269 Fails, 0 Restarts +Model[Golomb ruler], 8 Solutions, MINIMIZE a[9] = 62, Resolution time 0,092s, 374 Nodes (4 075,8 n/s), 712 Backtracks, 353 Fails, 0 Restarts +Model[Golomb ruler], 9 Solutions, MINIMIZE a[9] = 60, Resolution time 0,210s, 1354 Nodes (6 435,1 n/s), 2670 Backtracks, 1331 Fails, 0 Restarts +Model[Golomb ruler], 10 Solutions, MINIMIZE a[9] = 55, Resolution time 0,531s, 7997 Nodes (15 050,6 n/s), 15951 Backtracks, 7972 Fails, 0 Restarts +Model[Golomb ruler], 10 Solutions, MINIMIZE a[9] = 55, Resolution time 0,940s, 15981 Nodes (16 999,3 n/s), 31943 Backtracks, 15962 Fails, 0 Restarts +``` + +Things to remember +------------------ + +- adding redundant constraints is about reinforcing the propagation + and attempting to detect earlier impossible combinations +- adding symmetry-breaking constraint avoid finding new solutions that + are symmetric to previously found ones. \ No newline at end of file diff --git a/content/en/tutos/Golomb Ruler/Description.md b/content/en/tutos/Golomb Ruler/Description.md new file mode 100644 index 0000000..284f506 --- /dev/null +++ b/content/en/tutos/Golomb Ruler/Description.md @@ -0,0 +1,25 @@ +--- +title: "Description" +date: 2020-02-05T16:01:05+01:00 +type: docs +weight: 61 +description: > + A description of the problem to model and solve. +--- + + +[Wikipedia](https://en.wikipedia.org/wiki/Golomb_ruler) told us that: + +> A **Golomb ruler** is a set of marks at integer positions along an +> imaginary ruler such that no two pairs of marks are the same distance +> apart. The number of marks on the ruler is its order, and the largest +> distance between two of its marks is its length. The objective is to +> find optimal (minimum length) or near optimal rulers. Translation and +> reflection of a Golomb ruler are considered trivial, so the smallest +> mark is customarily put at 0 and the next mark at the smaller of its +> two possible values. + +Input data +---------- + +Only the order *m* is given as input data. diff --git a/content/en/tutos/Golomb Ruler/Math.md b/content/en/tutos/Golomb Ruler/Math.md new file mode 100644 index 0000000..7046533 --- /dev/null +++ b/content/en/tutos/Golomb Ruler/Math.md @@ -0,0 +1,100 @@ +--- +title: "Math" +date: 2020-02-05T16:01:10+01:00 +type: docs +math: "true" +weight: 62 +description: > + A mathematical model of the problem. +--- + +Variables +--------- + +- An integer variable $\text{tick}\_i$ per mark *i* indicates its + position. + + $$\forall i \in [1,m], \text{tick}\_i = [\\![0,9999]\\!]$$ + +- An integer variable $\text{diff}\_{i,j}$ per couple of marks $i,j$ + ($i < j$) indicates distance between the two marks. + + $$\forall i,j \in [1,m], i < j, \text{diff}\_{i,j} = [\\![0,9999]\\!]$$ + +Constraints +----------- + +- Maks are ordered : + + $$\forall i \in [2,m], \text{tick}\_{i-1} < \text{tick}\_i$$ + +- Distance between two distinct marks : + + $$\forall i,j \in [1,m], i < j, \text{diff}\_{i,j} = \text{tick}\_j - \text{tick}\_i$$ + +- No two pairs of marks are the same distance apart + + $$\forall i,j \in [1,m], i \ne j, \text{tick}\_{i} \ne \text{tick}\_{j}$$ + + We saw this type of constraint before, it is an *alldifferent* + constraint. + +- The first mark can be set to `0`: + $$\text{tick}\_0 = 0$$ + +Those variables and constraints are sufficient to define the problem. +However, this initial model can be improved by adding *redundant +constraints* and by *breaking symmetries*. + +### Redundant constraints + +These types of constraints are not required to find solutions, they are +implied by the other constraints of the model. Thus, their role is to +bring more filtering and to possibly detect infeasible combinations +earlier in the search. + +The following reasoning is based on the fact that: + +$$\forall i,j \in [1,m], i < j, \text{diff}\_{i,j} = \text{diff}\_{i,i+1} \ldots \text{diff}\_{j-1,j}$$ + +Because all distances must be different, we can estimate the minimal sum +of distances as a sum of *j-i* different positive numbers. + +$$\forall i,j \in [1,m], i < j, \text{diff}\_{i,j} \geq \frac\{(j-i) * (j-i+1)}{2}$$ + +Moreover, remember that + +$$\text{diff}\_{1,m} = \text{tick}\_{m} - \text{tick}\_1$$ + +and + +$$\text{diff}\_{1,m} = \text{diff}\_{1,2} \ldots \text{diff}\_{i,j} \ldots\text{diff}\_{m-1,m}$$ + +Thus, since $\text{tick}\_1$ is equal to 0, we deduce that: + +$$\text{tick}\_{m} = \text{diff}\_{1,2} \ldots \text{diff}\_{i,j} \ldots\text{diff}\_{m-1,m}$$ + +There are *m-1-j+i* different numbers so the upper for +$\text{diff}\_{i,j}$ can be defined as: + +$$\forall i,j \in [1,m], i < j, \text{diff}\_{i,j} \leq \text{tick}\_m - \frac{(m - 1 - j + i) * (m - j + i)}{2}$$ + +To go into details, please read [Barták, "Effective modeling with +constraints"](https://www.researchgate.net/publication/221644589_Effective_Modeling_with_Constraints). + +### Symmetry-breaking constraints + +These types of constraints aims at breaking symmetries and reducing the +search space size. Indeed, they avoid finding new solutions that are +symmetric to previously found ones. + +In a Golomb ruler, an easy way to break symmetries is to define an order +between the first and the last distances: + +$$\text{diff}\_{0,1} < \text{diff}\_{m-1,m}$$ + +Objective +--------- + +The objective is to find a solution that minimizes the position of the +last mark $\text{tick}_m$. \ No newline at end of file diff --git a/content/en/tutos/Golomb Ruler/_index.md b/content/en/tutos/Golomb Ruler/_index.md new file mode 100644 index 0000000..bec3a9c --- /dev/null +++ b/content/en/tutos/Golomb Ruler/_index.md @@ -0,0 +1,10 @@ +--- +title: "Golomb Ruler" +date: 2020-02-05T16:00:58+01:00 +type: docs +weight: 60 +description: > + An imaginary ruler. +--- + +You can consider this tutorial as a modeling exercise. To do so, just read the description and then compare your mathematical model with the one proposed. In the same way, you can start by coding your own model in Choco-solver before reading the proposed one. diff --git a/content/en/tutos/Nonogram/Code.md b/content/en/tutos/Nonogram/Code.md new file mode 100644 index 0000000..b0a0e7b --- /dev/null +++ b/content/en/tutos/Nonogram/Code.md @@ -0,0 +1,211 @@ +--- +title: "Code" +date: 2020-02-05T11:22:13+01:00 +type: docs +math: "true" +weight: 53 +description: > + A bunch of code. +--- + +Building DFAs +------------- + +Before describing the model, which is very compact, we will see how +Deterministic Finite Automaton (DFA) can be build. + +We will focus on a single sequence: {1, 2, 3}. + +### Regexp way + +The regular expression that encodes the sequence is +`"0*10+1{2}0+1{3}0*"`: + +- `0*` the word can start with unbounded number of 0 (`*` means zero or +more times) +- `10+` the first block of 1 is followed by at least one 0 (`+` +means one or more times) +- `1{2}0+` deals with the second block of 2 (`a{n}` means `a` +occurs exactly `n` times) which is followed by at least one 0 +- `1{3}0*` the third -- and last -- block of size 3 is followed by zero or more 0. +Indeed, this the last block of the sequence, so there cannot be other 1 +after but 0s are optional. + +Starting and ending 0s are optional but it has to be defined in the +regexp, otherwise some valid words may be skipped. + +{{% alert title="Caution" color="primary"%}} +In Choco-solver, DFAs only accept integer as character. +`0*a+` is not a valid grammar, there is no conversion `Character` (java term) to `Integer`. But, numbers are allowed, not only digits. +Indeed, some variables can take value greater than 9. +In that case, numbers are declared using the specific characters +`<` and `>`. +For example: `"0*<11><22>0*"` will accept words like `00112200` or `1122` but no `0120`. +{{%/alert%}} + + +```java +private void dfa(BoolVar[] cells, int[] rest, Model model) { + StringBuilder regexp = new StringBuilder("0*"); + int m = rest.length; + for (int i = 0; i < m; i++) { + regexp.append('1').append('{').append(rest[i]).append('}'); + regexp.append('0'); + regexp.append(i == m - 1 ? '*' : '+'); + } + IAutomaton auto = new FiniteAutomaton(regexp.toString()); + model.regular(cells, auto).post(); +} +``` + +### Constructive way + +The constructive way requires to declare all states of the automaton and +links together with transitions. A transition corresponds to a character +in the word, and a state is *between* two characters of the word. + +So there is a need of an initial state from which (through an outgoing +transition) the first character of the word will be provided. And at +least one final state to which (through an ingoing transition) the last +character of the word will be provided. + +We note $s_i$ the initial state. The first character can either be a 0 +or a 1, there will be two transitions outgoing from $s_i$. Then, +transition from $s_i$ producing 0 will go to $i_0$ (first transition). +And transition from $s_i$ producing 1 will go to $i_1$ (second +transition). $i_0$ points to itself providing 0 (third transition). +Outgoing transition from $i_1$ goes to $i_2$ and produces 0 (fourth +transition). Two transitions outgoes from $i_2$: one goes to itself +(fifth transition, producing 0), one goes to $i_3$ (sixth transition, +producing 1). $i_3$ goes to $i_4$ (seventh transition) and produce 1. +$i_4$ goes to $i_5$ (eighth transition) and produce 0. And so on. + +{{% pageinfo color="primary"%}} + +![DFA for {1,2} sequence.](/images/tutos/dfa.png) + +_Graph illustrating the DFA for the sequence {1, 2}. Generated with [Graphviz](http://www.graphviz.org/)._ +{{%/pageinfo%}} + + + + +And here the code for building such a DFA for any sequence: + +```java +private void dfa2(BoolVar[] cells, int[] seq, Model model) { + FiniteAutomaton auto = new FiniteAutomaton(); + int si = auto.addState(); + auto.setInitialState(si); // declare it as initial state + int i0 = auto.addState(); + auto.addTransition(si, i0, 0); // first transition + auto.addTransition(i0, i0, 0); // second transition + int from = i0; + int m = seq.length; + for (int i = 0; i < m; i++) { + int ii = auto.addState(); + // word can start with '1' + if(i == 0){ + auto.addTransition(si, ii, 1); + } + auto.addTransition(from, ii, 1); + from = ii; + for(int j = 1; j < seq[i]; j++){ + int jj = auto.addState(); + auto.addTransition(from, jj, 1); + from = jj; + } + int ii0 = auto.addState(); + auto.addTransition(from, ii0, 0); + auto.addTransition(ii0, ii0, 0); + // the word can end with '1' or '0' + if(i == m - 1){ + auto.setFinal(from, ii0); + } + from = ii0; + } + model.regular(cells, auto).post(); +} +``` + +{{%alert title="Info" color="primary"%}} +Any regexp can be transformed into a DFA and conversely. +But, most of the time the constructive way is more convenient. +{{%/alert%}} + +The entire code +--------------- + +```java +// number of columns +int N = 15; +// number of rows +int M = 15; +// sequence of shaded blocks +int[][][] BLOCKS = + new int[][][]{{ + {2}, + {4, 2}, + {1, 1, 4}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 2, 2, 1}, + {1, 3, 1}, + {2, 1}, + {1, 1, 1, 2}, + {2, 1, 1, 1}, + {1, 2}, + {1, 2, 1}, + }, { + {3}, + {3}, + {10}, + {2}, + {2}, + {8, 2}, + {2}, + {1, 2, 1}, + {2, 1}, + {7}, + {2}, + {2}, + {10}, + {3}, + {2}}}; + +Model model = new Model("Nonogram"); +// Variables declaration +BoolVar[][] cells = model.boolVarMatrix("c", N, M); +// Constraint declaration +// one regular per row +for (int i = 0; i < M; i++) { + dfa(cells[i], BLOCKS[0][i], model); +} +for (int j = 0; j < N; j++) { + dfa(ArrayUtils.getColumn(cells, j), BLOCKS[1][j], model); +} +if(model.getSolver().solve()){ + for (int i = 0; i < cells.length; i++) { + System.out.printf("\t"); + for (int j = 0; j < cells[i].length; j++) { + System.out.printf(cells[i][j].getValue() == 1 ? "#" : " "); + } + System.out.printf("\n"); + } +} +``` + +Things to remember +------------------ + +- Regular constraint constructs valid fix-sized words on the basis of + a vocabulary and a grammar. +- A deterministic finite automaton can either be build with a regular + expression or step-by-step. +- Regular constraints are very useful when patterns occur in + solutions. For example, when dealing with shifts on a personnal + scheduling problem: for example: "a nurse doesn't do a late night + shift followed by a day shift the next day". \ No newline at end of file diff --git a/content/en/tutos/Nonogram/Description.md b/content/en/tutos/Nonogram/Description.md new file mode 100644 index 0000000..5e09d52 --- /dev/null +++ b/content/en/tutos/Nonogram/Description.md @@ -0,0 +1,63 @@ +--- +title: "Description" +date: 2020-02-05T11:22:05+01:00 +type: docs +weight: 51 +description: > + A description of the problem to model and solve. +--- + +Nonograms are a popular puzzles, which goes by different names in +different countries. + +Models have to shade in squares in a grid so that blocks of consecutive +shaded squares satisfy constraints given for each row and column. + +Constraints typically indicate the sequence of shaded blocks (e.g. 3,1,2 +means that there is a block of 3, then a gap of unspecified size, a +block of length 1, another gap, and then a block of length 2). + +See [Nonogram](http://www.csplib.org/Problems/prob012/) for more +details. + +Input data +---------- + +We consider here the following input (in java): + +```java +// sequence of shaded blocks +int[][][] BLOCKS = + new int[][][]{{ + {2}, + {4, 2}, + {1, 1, 4}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 1, 1, 1}, + {1, 2, 2, 1}, + {1, 3, 1}, + {2, 1}, + {1, 1, 1, 2}, + {2, 1, 1, 1}, + {1, 2}, + {1, 2, 1}, + }, { + {3}, + {3}, + {10}, + {2}, + {2}, + {8, 2}, + {2}, + {1, 2, 1}, + {2, 1}, + {7}, + {2}, + {2}, + {10}, + {3}, + {2}}}; +``` diff --git a/content/en/tutos/Nonogram/Math.md b/content/en/tutos/Nonogram/Math.md new file mode 100644 index 0000000..2a9de7e --- /dev/null +++ b/content/en/tutos/Nonogram/Math.md @@ -0,0 +1,44 @@ +--- +title: "Math" +date: 2020-02-05T11:22:10+01:00 +type: docs +math: "true" +weight: 52 +description: > + A mathematical model of the problem. +--- + +Variables +--------- + +- A boolean variable $\text{cell}\_{i,j}$ per cells of the grid, set to + true when the cell is shaded, false oterhwise. + + $$\forall i,j \in [1,15]^2, \text{cell}\_{i,j} = \\{0,1\\}$$ + +And that's enough ! + +Constraints +----------- + +Any column and row should respect its sequence in `BLOCKS`. To do so, +each sequence of `BLOCKS` is turned into a Deterministic Finite +Automaton (DFA). There is a gap between two consecutive blocks, before +the first block and after the last block of a sequence there can be a +gap. The length of each block is injected in the DFA. Any gap is encoded +with a '0's and any block by '1's. + +The constraint needed here is named *regular*. It takes a finite +sequence variables and a DFA as input. With the help of a *vocabulary* +(available values, in our case 0 and 1) and a *grammar* (the allowed +sequence), the constraint constructs only valid *words* of a given size +(the variables length). It ensures a very good level of filtering (AC), +but the major difficulty relies on building the DFA. + +There are two ways to create it: based on a regular expression or +describing all states and transitions. + +Objective +--------- + +The objective is to find a solution that satisfies each constraint. \ No newline at end of file diff --git a/content/en/tutos/Nonogram/_index.md b/content/en/tutos/Nonogram/_index.md new file mode 100644 index 0000000..0243f60 --- /dev/null +++ b/content/en/tutos/Nonogram/_index.md @@ -0,0 +1,10 @@ +--- +title: "Nonogram" +date: 2020-02-05T11:21:54+01:00 +type: docs +weight: 50 +description: > + A picture logic puzzle. +--- + +You can consider this tutorial as a modeling exercise. To do so, just read the description and then compare your mathematical model with the one proposed. In the same way, you can start by coding your own model in Choco-solver before reading the proposed one. \ No newline at end of file diff --git a/content/en/tutos/Verbal arithmetic/_index.md b/content/en/tutos/Verbal arithmetic/_index.md index e3d1e63..1cce003 100644 --- a/content/en/tutos/Verbal arithmetic/_index.md +++ b/content/en/tutos/Verbal arithmetic/_index.md @@ -4,6 +4,7 @@ date: 2020-01-31T14:24:15+01:00 type: docs weight: 20 description: > - A little arithmetic problem. + A mathematical game. --- +You can consider this tutorial as a modeling exercise. To do so, just read the description and then compare your mathematical model with the one proposed. In the same way, you can start by coding your own model in Choco-solver before reading the proposed one. diff --git a/content/en/tutos/Warehouse Location Problem/Code.md b/content/en/tutos/Warehouse Location Problem/Code.md new file mode 100644 index 0000000..2c572e6 --- /dev/null +++ b/content/en/tutos/Warehouse Location Problem/Code.md @@ -0,0 +1,278 @@ +--- +title: "Code" +date: 2020-02-03T13:35:26+01:00 +type: docs +math: "true" +weight: 33 +description: > + A bunch of code. +--- + +A model +------- + +```java +// load parameters +// ... +// A new model instance +Model model = new Model("WarehouseLocation"); + +// VARIABLES +// a warehouse is either open or closed +BoolVar[] open = model.boolVarArray("o", W); +// which warehouse supplies a store +IntVar[] supplier = model.intVarArray("supplier", S, 1, W, false); +// supplying cost per store +IntVar[] cost = model.intVarArray("cost", S, 1, 96, true); +// Total of all costs +IntVar tot_cost = model.intVar("C", 0, 99999, true); + +// CONSTRAINTS +for (int j = 0; j < S; j++) { + // a warehouse is 'open', if it supplies to a store + model.element(model.intVar(1), open, supplier[j], 1).post(); + // Compute 'cost' for each store + model.element(cost[j], P[j], supplier[j], 1).post(); +} +for (int i = 0; i < W; i++) { + // additional variable 'occ' is created on the fly + // its domain includes the constraint on capacity + IntVar occ = model.intVar("occur_" + i, 0, K[i], true); + // for-loop starts at 0, warehouse index starts at 1 + // => we count occurrences of (i+1) in 'supplier' + model.count(i+1, supplier, occ).post(); + // redundant link between 'occ' and 'open' for better propagation + occ.ge(open[i]).post(); +} +// Prepare the constraint that maintains 'tot_cost' +int[] coeffs = new int[W + S]; +Arrays.fill(coeffs, 0, W, C); +Arrays.fill(coeffs, W, W + S, 1); +// then post it +model.scalar(ArrayUtils.append(open, cost), coeffs, "=", tot_cost).post(); +``` + +The last parameter of the element constraints (line 19 and 21) indicates +an offset. It enables to adapt the index range wrt to the domain of the +variable: here supplier variables lower bound is 1. But, open array +index starts at 0 and an offset is needed to match supplier with open +array. In other words, first *element* constraint states that +$open[supplier[s] - o] = 1$ where $o$ is set to 1. + +A search strategy +----------------- + +Since the problem is hard to solve, defining an adapted strategy is a +key to success. Among all declared variables, the ones that holds the +problem are 'open' and 'supplier': deciding on these variables has a +great effect on the size of the search space reduction. They are named +after that effect as *decision variables*. + +A good strategy for that problem is to select, among decisions +variables, the one with the smallest domain first. If two variables or +more have the smallest domain size, *ties* are broken randomly. Then, +the value in the middle of the domain of the selected variable is +assigned to it, with a floor rounding policy (the closest value greater +or equal to the middle value is returned). + +```java +Solver solver = model.getSolver(); +solver.setSearch(Search.intVarSearch( + new VariableSelectorWithTies<>( + new FirstFail(model), + new Smallest()), + new IntDomainMiddle(false), + ArrayUtils.append(supplier, cost, open)) +); +``` + +The resolution objective +------------------------ + +The objective is to minimize 'tot\_cost'. + +```java +// Find a solution that minimizes 'tot_cost' +Solution best = solver.findOptimalSolution(tot_cost, false); +``` + +This method attempts to find the optimal solution. + +{{% alert title="Hint" color="primary" %}} +Finding an optimal solution goes like this: anytime a solution is +found, a *cut* is posted on the objective variable to forbid worst or +same value solutions to be found. When a cut is so strong that no +better solution is found, the last one is the optimal one (if we +consider that no search limits was defined). The cut process is +entirely managed by the solver. +{{%/alert%}} + +Alternatively, the search loop can be unfold. + +```java +model.setObjective(false, tot_cost); +while(solver.solve()){ + // do something on solution +} +``` + +The objective variable and criteria should be declared, but there is no +need to post the cut manually, the solver manages this. When the unfold +search process is used, one can modify the way the cut is handled: + +```java +// Walking cut: allow same value solutions +solver.getObjectiveManager().setCutComputer(obj -> obj); +model.setObjective(false, tot_cost); +while(solver.solve()){ + // do something on solution +} +``` + +Unfold search process allows you to execute code on solution easily. + +One can add a limit to the resolution process. For example, a 10 +second-limit can be defined like this: + +```java +solver.limitTime("10s"); +// then run the resolution +Solution best = solver.findOptimalSolution(tot_cost, false); +``` + +The search should be configured **before** being called. There can be +multiple limitations, in that case, the first reached stops the search. + +Pretty solution output +---------------------- + +We can define a function that prints any solutions in a pretty way. + +```java +private void prettyPrint(Model model, IntVar[] open, int W, IntVar[] supplier, int S, IntVar tot_cost) { + StringBuilder st = new StringBuilder(); + st.append("Solution #").append(model.getSolver().getSolutionCount()).append("\n"); + for (int i = 0; i < W; i++) { + if (open[i].getValue() > 0) { + st.append(String.format("\tWarehouse %d supplies customers : ", (i + 1))); + for (int j = 0; j < S; j++) { + if (supplier[j].getValue() == (i + 1)) { + st.append(String.format("%d ", (j + 1))); + } + } + st.append("\n"); + } + } + st.append("\tTotal C: ").append(tot_cost.getValue()); + System.out.println(st.toString()); +} +``` + +Calling this method is made easy with the unfold resolution instruction. + +The entire code +--------------- + +```java +// load parameters +// number of warehouses +int W = 5; +// number of stores +int S = 10; +// maintenance cost +int C = 30; +// capacity of each warehouse +int[] K = new int[]{1, 4, 2, 1, 3}; +// matrix of supply costs, store x warehouse +int[][] P = new int[][]{ + {20, 24, 11, 25, 30}, + {28, 27, 82, 83, 74}, + {74, 97, 71, 96, 70}, + {2, 55, 73, 69, 61}, + {46, 96, 59, 83, 4}, + {42, 22, 29, 67, 59}, + {1, 5, 73, 59, 56}, + {10, 73, 13, 43, 96}, + {93, 35, 63, 85, 46}, + {47, 65, 55, 71, 95}}; + +// A new model instance +Model model = new Model("WarehouseLocation"); + +// VARIABLES +// a warehouse is either open or closed +BoolVar[] open = model.boolVarArray("o", W); +// which warehouse supplies a store +IntVar[] supplier = model.intVarArray("supplier", S, 1, W, false); +// supplying cost per store +IntVar[] cost = model.intVarArray("cost", S, 1, 96, true); +// Total of all costs +IntVar tot_cost = model.intVar("tot_cost", 0, 99999, true); + +// CONSTRAINTS +for (int j = 0; j < S; j++) { + // a warehouse is 'open', if it supplies to a store + model.element(model.intVar(1), open, supplier[j], 1).post(); + // Compute 'cost' for each store + model.element(cost[j], P[j], supplier[j], 1).post(); +} +for (int i = 0; i < W; i++) { + // additional variable 'occ' is created on the fly + // its domain includes the constraint on capacity + IntVar occ = model.intVar("occur_" + i, 0, K[i], true); + // for-loop starts at 0, warehouse index starts at 1 + // => we count occurrences of (i+1) in 'supplier' + model.count(i+1, supplier, occ).post(); + // redundant link between 'occ' and 'open' for better propagation + occ.ge(open[i]).post(); +} +// Prepare the constraint that maintains 'tot_cost' +int[] coeffs = new int[W + S]; +Arrays.fill(coeffs, 0, W, C); +Arrays.fill(coeffs, W, W + S, 1); +// then post it +model.scalar(ArrayUtils.append(open, cost), coeffs, "=", tot_cost).post(); + +model.setObjective(Model.MINIMIZE, tot_cost); +Solver solver = model.getSolver(); +solver.setSearch(Search.intVarSearch( + new VariableSelectorWithTies<>( + new FirstFail(model), + new Smallest()), + new IntDomainMiddle(false), + ArrayUtils.append(supplier, cost, open)) +); +solver.showShortStatistics(); +while(solver.solve()){ + prettyPrint(model, open, W, supplier, S, tot_cost); +} +``` + +The best solution found is: + +``` +Solution #23 + Warehouse 1 supplies customers : 4 + Warehouse 2 supplies customers : 2 6 7 9 + Warehouse 3 supplies customers : 8 10 + Warehouse 5 supplies customers : 1 3 5 + Total C: 383 +Model[Model-0], 23 Solutions, Minimize tot_cost = 383, Resolution time 0,069s, 76 Nodes (1 098,9 n/s), 93 Backtracks, 26 Fails, 0 Restarts +``` + +Things to remember +------------------ + +- The *element* constraint can be very helpful, one can have more + details on it on the [Global Constraint + Catalog](http://sofdem.github.io/gccat/gccat/Celement.html). +- The *count* constraint is also part of the must-have constraints + ([Global Constraint + Catalog](http://sofdem.github.io/gccat/gccat/Ccount.html)). +- Besides pre-defined search strategies, one can also constructed a + specific one. Most of the time, it is worth the time spent on it. +- The resolution process can be unfold and limited. It allows + interacting with solution state without building default solution + object. +- Cut process is managed by the solver, but it can be modified when + using the unfold resolution process. \ No newline at end of file diff --git a/content/en/tutos/Warehouse Location Problem/Description.md b/content/en/tutos/Warehouse Location Problem/Description.md new file mode 100644 index 0000000..b2122fb --- /dev/null +++ b/content/en/tutos/Warehouse Location Problem/Description.md @@ -0,0 +1,52 @@ +--- +title: "Description" +date: 2020-02-03T13:26:51+01:00 +type: docs +weight: 31 +description: > + A description of the problem to model and solve. +--- + +In the Warehouse Location problem (WLP), a company considers opening +warehouses at some candidate locations in order to supply its existing +stores. + +Each possible warehouse has the same maintenance cost, and a capacity +designating the maximum number of stores that it can supply. + +Each store must be supplied by exactly one open warehouse. The supply +cost to a store depends on the warehouse. + +The objective is to determine which warehouses to open, and which of +these warehouses should supply the various stores, such that the sum of +the maintenance and supply costs is minimized. + +See [this page](http://csplib.org/Problems/prob034/) for more details. + +Input data +---------- + +We consider here the following input (in java): + +```java +// number of warehouses +int W = 5; +// number of stores +int S = 10; +// maintenance cost +int C = 30; +// capacity of each warehouse +int[] K = new int[]{1, 4, 2, 1, 3}; +// matrix of supply costs, store x warehouse +int[][] P = new int[][]{ + {20, 24, 11, 25, 30}, + {28, 27, 82, 83, 74}, + {74, 97, 71, 96, 70}, + {2, 55, 73, 69, 61}, + {46, 96, 59, 83, 4}, + {42, 22, 29, 67, 59}, + {1, 5, 73, 59, 56}, + {10, 73, 13, 43, 96}, + {93, 35, 63, 85, 46}, + {47, 65, 55, 71, 95}}; +``` \ No newline at end of file diff --git a/content/en/tutos/Warehouse Location Problem/Math.md b/content/en/tutos/Warehouse Location Problem/Math.md new file mode 100644 index 0000000..70d476d --- /dev/null +++ b/content/en/tutos/Warehouse Location Problem/Math.md @@ -0,0 +1,74 @@ +--- +title: "Math" +date: 2020-02-03T13:27:42+01:00 +type: docs +math: "true" +weight: 32 +description: > + A mathematical model of the problem. +--- + +Variables +--------- + +- A boolean variable $\text{open}\_i$ per warehouse *i* is needed, set to `true` if the corresponding warehouse is open, `false` otherwise. + + $$\forall i \in [1,5],\\, \text{open}\_i = \\{0,1\\}$$ + +- An integer variable $\text{supplier}\_j$ per store *j* is needed, it + indicates which warehouse supplies it. + + $$\forall j \in [1,10],\\, \text{supplier}\_j = [\\![1,5]\\!]$$ + +- An integer variable $\text{cost}\_j$ per store *j* is needed too, it + stores the cost of being supplied by a warehouse (the range is + deduced from the matrix P). + + $$\forall j \in [1,10],\\, \text{cost}\_j = [\\![1, 96]\\!]$$ + +- An integer variable $tot_cost$ totals all costs: + + $$tot\_{cost} = [\\![1, {+\infty})$$ + +Constraints +----------- + +- if a warehouse *i* supplies a store *j*, then, the warehouse is + open: + + $$\forall j \in [1,10], \text{open}\_{\text{supplier}\_j} = 1$$ + + Here $\text{supplier}\_j$ defines the index the array $\text{open}$ + to be valuated to 1. This is a encoded with an element constraint. + +- if a warehouse *i* supplies a store *j*, it is related to a specific + cost: + + $$\forall j \in [1,10], P_{j,\text{supplier}\_{j}} = \text{cost}\_j$$ + + Here again, an element constraint is used to bind the supplier and + the supply cost matrix to the cost of supplying a store. + +- the maximum number of stores a warehouse *i* can supply is limited + to $K_i$: + + $$\forall i \in [1,5], \sum_{j = 1}^{10} (\text{supplier}\_j == i) = \text{occ}\_i$$ + $$\forall i \in [1,5], \text{occ}\_i \leq K_i$$ + $$\forall i \in [1,5], \text{occ}\_i \geq \text{open}\_i$$ + + The first constraint counts the number of occurrences of the value + *i* in the array supplier and stores the result $\text{occ}\_i$ + variable. This variable is then constrained to be less than or equal + to $K_i$, to ensure the capacity is satisfied, but also to be + greater or equal to $\text{open}\_i$ to better propagation. + +- the assignment cost has then to be maintained, including fixed costs + and supplying costs: + + $$tot\_{cost} = \sum_{i = 1}^{5} 30 \cdot \text{open}\_i + \sum_{j = 1}^{10} \text{cost}\_j$$ + +Objective +--------- + +The objective is not to simply find a solution but one that minimizes +$tot\_{cost}$. \ No newline at end of file diff --git a/content/en/tutos/Warehouse Location Problem/_index.md b/content/en/tutos/Warehouse Location Problem/_index.md new file mode 100644 index 0000000..014e833 --- /dev/null +++ b/content/en/tutos/Warehouse Location Problem/_index.md @@ -0,0 +1,10 @@ +--- +title: "Warehouse Location Problem" +date: 2020-02-03T13:25:31+01:00 +type: docs +weight: 30 +description: > + Where to build warehouses and which warehouse supplies which customer. +--- + +You can consider this tutorial as a modeling exercise. To do so, just read the description and then compare your mathematical model with the one proposed. In the same way, you can start by coding your own model in Choco-solver before reading the proposed one. \ No newline at end of file diff --git a/layouts/partials/head.html b/layouts/partials/head.html index 782282b..08c3cb4 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -35,8 +35,8 @@