742 lines
50 KiB
TeX
742 lines
50 KiB
TeX
%************************************************
|
|
\chapter{Incremental Processing}\label{ch:incremental}
|
|
%************************************************
|
|
|
|
\input{chapters/5_incremental_preamble}
|
|
|
|
\section{Modelling of Restart-Based Meta-Search}\label{sec:6-modelling}
|
|
|
|
This section introduces a \minizinc{} extension that enables modellers to define \gls{meta-search} algorithms in \cmls{}.
|
|
This extension is based on the construct introduced in \minisearch\ \autocite{rendl-2015-minisearch}, as summarised below.
|
|
|
|
\subsection{Meta-Search in \glsentrytext{minisearch}}\label{sec:6-minisearch}
|
|
|
|
% Most \gls{lns} literature discusses neighbourhoods in terms of ``destroying'' part of
|
|
% a solution that is later repaired. However, from a declarative modelling point
|
|
% of view, it is more natural to see neighbourhoods as adding new constraints and
|
|
% variables that need to be applied to the base model, \eg\ forcing variables to
|
|
% take the same value as in the previous solution.
|
|
|
|
\minisearch{} introduced a \minizinc{} extension that enables modellers to express meta-searches inside a \minizinc\ model.
|
|
A meta-search in \minisearch\ typically solves a given \minizinc\ model, performs some calculations on the solution, adds new constraints and then solves again.
|
|
|
|
Most \gls{meta-search} definitions in \minisearch\ consist of two parts.
|
|
The first part is a declarative definition of any restriction to the search space that the \gls{meta-search} algorithm can apply, called a \gls{neighbourhood}.
|
|
In \minisearch\ these definitions can make use of the function: \mzninline{function int: sol(var int: x)}, which returns the value that variable \mzninline{x} was assigned to in the previous solution (similar functions are defined for Boolean, float and set variables).
|
|
This allows the \gls{neighbourhood} to be defined in terms of the previous solution.
|
|
In addition, a neighbourhood predicate will typically make use of the random number generators available in the \minizinc\ standard library.
|
|
\Cref{lst:6-lns-minisearch-pred} shows a simple random neighbourhood.
|
|
For each decision variable \mzninline{x[i]}, it draws a random number from a uniform distribution and, if it exceeds threshold \mzninline{destr_rate}, posts constraints forcing \mzninline{x[i]} to take the same value as in the previous solution.
|
|
For example, \mzninline{uniform_neighbourhood(x, 0.2)} would result in each variable in the array \mzninline{x} having a 20\% chance of being unconstrained, and an 80\% chance of being assigned to the value it had in the previous solution.
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_lns_minisearch_pred.mzn}
|
|
\caption{\label{lst:6-lns-minisearch-pred} A simple random \gls{lns} predicate implemented in \minisearch{}}
|
|
\end{listing}
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_lns_minisearch.mzn}
|
|
\caption{\label{lst:6-lns-minisearch} A simple \gls{lns} \gls{meta-search} implemented in \minisearch{}}
|
|
\end{listing}
|
|
|
|
The second part of a \minisearch\ \gls{meta-search} is the \gls{meta-search} algorithm itself.
|
|
\Cref{lst:6-lns-minisearch} shows a basic \minisearch\ implementation of a basic \gls{lns} algorithm, called \mzninline{lns}.
|
|
It performs a fixed number of iterations, each invoking the neighbourhood predicate \mzninline{uniform_neighbourhood} in a fresh scope (so that the constraints only affect the current loop iteration).
|
|
It then searches for a solution (\mzninline{minimize_bab}) with a given timeout, and if the search does return a new solution, it commits to that solution (so that it becomes available to the \mzninline{sol} function in subsequent iterations).
|
|
The \mzninline{lns} function also posts the constraint \mzninline{obj < sol(obj)}, ensuring the objective value in the next iteration is strictly better than that of the current solution.
|
|
|
|
Although \minisearch\ enables the modeller to express \glspl{neighbourhood} in a declarative way, the definition of the \gls{meta-search} algorithms is rather unintuitive and difficult to debug, leading to unwieldy code for defining even simple restarting strategies.
|
|
Furthermore, the \minisearch\ implementation requires either a close integration of the backend solver into the \minisearch\ system, or it drives the solver through the regular text-file based \flatzinc\ interface, leading to a significant communication overhead.
|
|
|
|
To address these two issues, we propose to keep modelling neighbourhoods as predicates, but define \gls{meta-search} algorithms from an imperative perspective.
|
|
|
|
We define a few additional \minizinc\ built-in annotations and functions that (a) allow us to express important aspects of the meta-search in a more convenient way, and (b) enable a simple compilation scheme that requires no additional communication with and only small, simple extensions of the backend solver.
|
|
|
|
% The approach we follow here is therefore to \textbf{extend \flatzinc}, such that
|
|
% the definition of neighbourhoods can be communicated to the solver together with
|
|
% the problem instance. This maintains the loose coupling of \minizinc\ and
|
|
% solver, while avoiding the costly communication and cold-starting of the
|
|
% black-box approach.
|
|
|
|
\subsection{Restart Annotation}
|
|
|
|
Instead of the complex \minisearch\ definitions, we propose to add support for \glspl{meta-search} that are purely based on the notion of \glspl{restart}.
|
|
A \gls{restart} happens when a solver abandons its current search efforts, returns to the root node of the search tree, and begins a new exploration.
|
|
Many \gls{cp} solvers already provide support for controlling their restarting behaviour, \eg\ they can periodically restart after a certain number of nodes, or restart for every solution.
|
|
Typically, solvers also support posting additional constraints upon restarting (\eg\ Comet \autocite{michel-2005-comet}) that are only valid for the particular \gls{restart} (\ie\ they are ``retracted'' for the next \gls{restart}).
|
|
|
|
In its simplest form, we can therefore implement \gls{lns} by specifying a neighbourhood predicate, and annotating the \mzninline{solve} item to indicate the predicate should be invoked upon each restart:
|
|
|
|
\mzninline{solve ::on_restart(my_neighbourhood) minimize cost;}
|
|
|
|
Note that \minizinc\ currently does not support passing functions or predicates as arguments.
|
|
Calling the predicate, as in \mzninline{::on_restart(my_neighbourhood())}, would not have the correct semantics, since the predicate needs to be called for \emph{each} restart.
|
|
As a workaround, we currently pass the name of the predicate to be called for each restart as a string (see the definition of the new \mzninline{on_restart} annotation in \cref{lst:6-restart-ann}).
|
|
|
|
The second component of our \gls{lns} definition is the \emph{restarting strategy}, defining how much effort the solver should put into each neighbourhood (\ie\ restart), and when to stop the overall search.
|
|
|
|
We propose adding new search annotations to the \minizinc\ language to control this behaviour (see \cref{lst:6-restart-ann}).
|
|
The \mzninline{restart_on_solution} annotation tells the solver to restart immediately for each solution, rather than looking for the best one in each restart, while \mzninline{restart_without_objective} tells it not to add branch-and-bound constraints on the objective.
|
|
The other \mzninline{restart_X} annotations define different strategies for restarting the search when no solution is found.
|
|
The \mzninline{timeout} annotation gives an overall time limit for the search, whereas \mzninline{restart_limit} stops the search after a fixed number of restarts.
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_restart_ann.mzn}
|
|
\caption{\label{lst:6-restart-ann} New annotations to control the restarting
|
|
behaviour}
|
|
\end{listing}
|
|
|
|
\subsection{Advanced Meta-Search}
|
|
|
|
Although using just a restart annotations by themselves allows us to run the basic \gls{lns} algorithm, more advanced \gls{meta-search} algorithms will require more than reapplying the same \gls{neighbourhood} time after time.
|
|
It is, for example, often beneficial to use several \gls{neighbourhood} definitions for a problem.
|
|
Different \glspl{neighbourhood} may be able to improve different aspects of a solution, at different phases of the search.
|
|
Adaptive \gls{lns} \autocite{ropke-2006-adaptive, pisinger-2007-heuristic}, which keeps track of the \glspl{neighbourhood} that led to improvements and favours them for future iterations, is the prime example for this approach.
|
|
A simpler scheme may apply several \glspl{neighbourhood} in a round-robin fashion.
|
|
|
|
In \minisearch\, these adaptive or round-robin approaches can be implemented using \emph{state variables}, which support destructive update (overwriting the value they store).
|
|
In this way, the \minisearch\ strategy can store values to be used in later iterations.
|
|
We use the \emph{solver state} instead, \ie\ normal decision variables, and define two simple built-in functions to access the solver state \emph{of the previous restart}.
|
|
This approach is sufficient for expressing many \gls{meta-search} algorithms, and its implementation is much simpler.
|
|
|
|
\paragraph{State access and initialisation}
|
|
|
|
The state access functions are defined in \cref{lst:6-state-access}.
|
|
Function \mzninline{status} returns the status of the previous restart, namely: \mzninline{START} (there has been no restart yet); \mzninline{UNSAT} (the restart failed); \mzninline{SAT} (the restart found a solution); \mzninline{OPT} (the restart found and proved an optimal solution); and \mzninline{UNKNOWN} (the restart did not fail or find a solution).
|
|
Function \mzninline{last_val} (which, like \mzninline{sol}, has versions for all basic variable types) allows modellers to access the last value assigned to a variable (the value is undefined if \mzninline{status()=START}).
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_state_access.mzn}
|
|
\caption{\label{lst:6-state-access} Functions for accessing previous solver
|
|
states}
|
|
\end{listing}
|
|
|
|
In order to be able to initialise the variables used for state access, we reinterpret \mzninline{on_restart} so that the predicate is also called for the initial search (\ie\ before the first ``real'' restart) with the same semantics, that is, any constraint posted by the predicate will be retracted for the next restart.
|
|
|
|
\paragraph{Parametric neighbourhood selection predicates}
|
|
|
|
We define standard neighbourhood selection strategies as predicates that are parametric over the neighbourhoods they should apply.
|
|
For example, since \mzninline{on_restart} now also includes the initial search, we can define a strategy \mzninline{basic_lns} that applies a neighbourhood only if the current status is not \mzninline{START}:
|
|
|
|
\begin{mzn}
|
|
predicate basic_lns(var bool: nbh) = (status()!=START -> nbh);
|
|
\end{mzn}
|
|
|
|
In order to use this predicate with the \mzninline{on_restart} annotation, we cannot simply pass
|
|
\begin{mzn}
|
|
basic_lns(uniform_neighbourhood(x, 0.2))
|
|
\end{mzn}
|
|
\noindent{}Calling \mzninline{uniform_neighbourhood} like that would result in a \emph{single} evaluation of the predicate, since \minizinc\ employs a call-by-value evaluation strategy.
|
|
Furthermore, the \mzninline{on_restart} annotation only accepts the name of a nullary predicate.
|
|
Therefore, users have to define their overall strategy in a new predicate.
|
|
\Cref{lst:6-basic-complete} shows a complete example of a basic \gls{lns} model.
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_basic_complete.mzn}
|
|
\caption{\label{lst:6-basic-complete} Complete \gls{lns} example}
|
|
\end{listing}
|
|
|
|
We can also define round-robin and adaptive strategies using these primitives.
|
|
\Cref{lst:6-round-robin} defines a round-robin \gls{lns} meta-heuristic, which cycles through a list of \mzninline{N} neighbourhoods \mzninline{nbhs}.
|
|
To do this, it uses the decision variable \mzninline{select}.
|
|
In the initialisation phase (\mzninline{status()=START}), \mzninline{select} is set to \mzninline{-1}, which means none of the neighbourhoods is activated.
|
|
In any following restart, \mzninline{select} is incremented modulo \mzninline{N}, by accessing the last value assigned in a previous restart:
|
|
\begin{mzn}
|
|
last_val(select)
|
|
\end{mzn}
|
|
\noindent{}This will activate a different neighbourhood for each restart (\lref{line:6:roundrobin:post}).
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_round_robin.mzn}
|
|
\caption{\label{lst:6-round-robin} A predicate providing the round-robin
|
|
meta-heuristic}
|
|
\end{listing}
|
|
|
|
%\paragraph{Adaptive \gls{lns}}
|
|
|
|
For adaptive \gls{lns}, a simple strategy is to change the size of the neighbourhood depending on whether the previous size was successful or not.
|
|
\Cref{lst:6-adaptive} shows an adaptive version of the \mzninline{uniform_neighbourhood} that increases the number of free variables when the previous restart failed, and decreases it when it succeeded, within the bounds \([0.6,0.95]\).
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_adaptive.mzn}
|
|
\caption{\label{lst:6-adaptive} A simple adaptive neighbourhood}
|
|
\end{listing}
|
|
|
|
\subsection{Optimisation strategies}
|
|
|
|
The \gls{lns} strategies we have seen so far rely on the default behaviour of \minizinc\ solvers to use a branch-and-bound method for optimisation: when a new solution is found, the solver adds a constraint to the remainder of the search to only accept better solutions, as defined by the objective function in the \mzninline{minimize} or \mzninline{maximize} clause of the \mzninline{solve} item.
|
|
When combined with restarts and \gls{lns}, this is equivalent to a simple hill-climbing meta-heuristic.
|
|
|
|
We can use the constructs introduced above to implement alternative meta-heuristics such as simulated annealing.
|
|
We use \mzninline{restart_without_objective}, in particular, to tell the solver not to add the branch-and-bound constraint on restart.
|
|
It will still use the declared objective to decide whether a new solution is globally the best one seen so far, and only output those (to maintain the convention of \minizinc\ solvers that the last solution printed at any point in time is the currently best known one).
|
|
|
|
With \mzninline{restart_without_objective}, the restart predicate is now responsible for constraining the objective function.
|
|
Note that a simple hill-climbing (for minimisation) can still be defined easily in this context as:
|
|
|
|
\begin{mzn}
|
|
predicate hill_climbing() = status() != START -> _objective < sol(_objective);
|
|
\end{mzn}
|
|
|
|
It takes advantage of the fact that the declared objective function is available through the built-in variable \mzninline{_objective}.
|
|
A more interesting example is a simulated annealing strategy.
|
|
When using this strategy, the solutions that the solver finds are no longer required to steadily improve in quality.
|
|
Instead, we ask the solver to find a solution that is a significant improvement over the previous solution.
|
|
Over time, we decrease the amount by which we require the solution needs to improve until we are just looking for any improvements.
|
|
This \gls{meta-search} can help improve the qualities of solutions quickly and thereby reaching the optimal solution quicker.
|
|
This strategy is also easy to express using our restart-based modelling:
|
|
|
|
\begin{mzn}
|
|
predicate simulated_annealing(float: init_temp, float: cooling_rate) =
|
|
let {
|
|
var float: temp;
|
|
} in if status() = START then
|
|
temp = init_temp
|
|
else
|
|
temp = last_val(temp) * (1 - cooling_rate) % cool down
|
|
/\ _objective < sol(_objective) - ceil(log(uniform(0.0, 1.0)) * temp)
|
|
endif;
|
|
\end{mzn}
|
|
|
|
Using the same methods it is also possible to describe optimisation strategies with multiple objectives.
|
|
An example of such a strategy is lexicographic search.
|
|
Lexicographic search can be employed when there is a strict order between the importance of different variables.
|
|
It required that, once a solution is found, each subsequent solution must either improve the first objective, or have the same value for the first objective and improve the second objective, or have the same value for the first two objectives and improve the third objective, and so on.
|
|
We can model this strategy restarts as such:
|
|
|
|
\begin{mzn}
|
|
predicate lex_minimize(array[int] of var int: o) =
|
|
let {
|
|
var min(index_set(o))..max(index_set(o))+1: stage;
|
|
} in if status() = START then
|
|
stage = min(index_set(o))
|
|
elseif status() = UNSAT then
|
|
stage = lastval(stage) + 1
|
|
else /* status() = SAT */
|
|
stage = lastval(stage)
|
|
/\ o[stage] < sol(o[stage])
|
|
endif
|
|
/\ forall(i in min(index_set(o))..stage-1) (
|
|
o[i] = sol(o[i])
|
|
)
|
|
/\ if stage > max(index_set(o)) then
|
|
complete()
|
|
endif;
|
|
\end{mzn}
|
|
|
|
The lexicographic objective changes the objective at each stage in the evaluation.
|
|
Initially the stage is 1. Otherwise, is we have an unsatisfiable result, then the last stage has been completed (proved optimal).
|
|
We increase the stage by one if we have stages to go otherwise we finish.
|
|
Otherwise, if the last all was SAT we maintain the same stage, and store the objective value (for this stage) in the \mzninline{best} array.
|
|
For normal computation we fix all the earlier stage variables to their best value.
|
|
If we are not in the first run for a stage we add the branch and bound cut to try to find better solutions.
|
|
Finally, we set the objective to be the objective for the current stage.
|
|
|
|
There is not always a clear order of importance for different objectives in a problem.
|
|
In these cases we instead look for a number of diverse solutions and allow the user to pick the most acceptable options.
|
|
The following fragment shows a \gls{meta-search} for the Pareto optimality of a pair of objectives:
|
|
|
|
\begin{mzn}
|
|
predicate pareto_optimal(var int: obj1, var int: obj2) =
|
|
let {
|
|
int: ms = 1000; % max solutions
|
|
var 0..ms: nsol; % number of solutions
|
|
set of int: SOL = 1..ms;
|
|
array[SOL] of var lb(obj1)..ub(obj1): s1;
|
|
array[SOL] of var lb(obj2)..ub(obj2): s2;
|
|
} in if status() = START then
|
|
nsol = 0
|
|
elseif status() = UNSAT then
|
|
complete() % we are finished!
|
|
elseif
|
|
nsol = sol(nsol) + 1 /\
|
|
s1[nsol] = sol(obj1) /\
|
|
s2[nsol] = sol(obj2)
|
|
endif
|
|
/\ for(i in 1..nsol) (
|
|
obj1 < lastval(s1[i]) \/ obj2 < lastval(s2[i])
|
|
);
|
|
\end{mzn}
|
|
|
|
In this implementation we keep track of the number of solutions found so far using \mzninline{nsol}.
|
|
There is a maximum number we can handle (\mzninline{ms}).
|
|
At the start the number of solutions is 0. If we find no solutions, then we finish the entire search.
|
|
Otherwise, we record that we have one more solution.
|
|
We store the solution values in \mzninline{s1} and \mzninline{s2} arrays.
|
|
Before each restart we add constraints removing Pareto dominated solutions, based on each previous solution.
|
|
|
|
\section{Compilation of Meta-Search Specifications}\label{sec:6-solver-extension}
|
|
|
|
The neighbourhoods defined in the previous section can be executed with \minisearch\ by adding support for the \mzninline{status} and \mzninline{last_val} built-in functions, and by defining the main restart loop.
|
|
The \minisearch{} evaluator will then call a solver to produce a solution, and evaluate the neighbourhood predicate, incrementally producing new \flatzinc\ to be added to the next round of solving.
|
|
|
|
While this is a viable approach, our goal is to keep the compiler and solver separate, by embedding the entire \gls{lns} specification into the \flatzinc\ that is passed to the solver.
|
|
|
|
This section introduces such a compilation approach.
|
|
It only requires simple modifications of the \minizinc\ compiler, and the compiled \flatzinc\ can be executed by standard \gls{cp} solvers with a small set of simple extensions.
|
|
|
|
\subsection{Compilation overview}
|
|
|
|
The neighbourhood definitions from the previous section have an important property that makes them easy to compile to standard \flatzinc: they are defined in terms of standard \minizinc\ expressions, except for a few new built-in functions.
|
|
When the neighbourhood predicates are evaluated in the \minisearch\ way, the \minisearch\ runtime implements those built-in functions, computing the correct value whenever a predicate is evaluated.
|
|
|
|
Instead, the compilation scheme presented below uses a limited form of \emph{partial evaluation}: parameters known at compile time will be fully evaluated; those only known during the solving, such as the result of a call to any of the new functions (\mzninline{sol}, \mzninline{status}, etc.)
|
|
, are replaced by decision variables.
|
|
This essentially \textbf{turns the new built-in functions into constraints} that have to be supported by the target solver.
|
|
The neighbourhood predicate can then be added as a constraint to the model.
|
|
The evaluation is performed by hijacking the solver's own capabilities: It will automatically perform the evaluation of the new functions by propagating the new constraints.
|
|
|
|
To compile a \gls{lns} specification to standard \flatzinc{}, the \minizinc\ compiler performs four simple steps:
|
|
|
|
\begin{enumerate}
|
|
\item Replace the annotation \mzninline{::on_restart("X")} with a call to predicate \mzninline{X}.
|
|
\item Inside predicate \mzninline{X} and any other predicate called recursively from \mzninline{X}: treat any call to built-in functions \mzninline{sol}, \mzninline{status}, and \mzninline{last_val} as returning a \mzninline{var} instead of a \mzninline{par} value; and rename calls to random functions, e.g., \mzninline{uniform} to \mzninline{uniform_slv}, in order to distinguish them from their standard library versions.
|
|
\item Convert any expression containing a call from step 2 to \mzninline{var} to ensure the functions are compiled as constraints, rather than statically evaluated by the \minizinc\ compiler.
|
|
\item Compile the resulting model using an extension of the \minizinc\ standard library that provides declarations for these built-in functions, as defined below.
|
|
\end{enumerate}
|
|
|
|
These transformations will not change the code of many neighbourhood definitions, since the built-in functions are often used in positions that accept both parameters and variables.
|
|
For example, the \mzninline{uniform_neighbourhood} predicate from \cref{lst:6-lns-minisearch-pred} uses \mzninline{uniform(0.0, 1.0)} in an \mzninline{if} expression, and \mzninline{sol(x[i])} in an equality constraint.
|
|
Both expressions can be translated to \flatzinc\ when the functions return a \mzninline{var}.
|
|
|
|
\subsection{Compiling the new built-ins}
|
|
|
|
We can compile models that contain the new built-ins by extending the \minizinc\ standard library as follows.
|
|
|
|
\paragraph{\mzninline{status}}
|
|
|
|
\Cref{lst:6-status} shows the definition of the \mzninline{status} function.
|
|
It simply replaces the functional form by a predicate \mzninline{status} (declared in \lref{line:6:status}), which constrains its local variable argument \mzninline{stat} to take the status value.
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_status.mzn}
|
|
\caption{\label{lst:6-status} MiniZinc definition of the \mzninline{status} function}
|
|
\end{listing}
|
|
|
|
|
|
\paragraph{\mzninline{sol} and \mzninline{last_val}}
|
|
|
|
Since \mzninline{sol} is overloaded for different variable types and \flatzinc\ does not support overloading, we produce type-specific built-ins for every type of solver variable (\mzninline{int_sol(x, xi)}, \mzninline{bool_sol(x, xi)}, etc.)
|
|
. The resolving of the \mzninline{sol} function into these specific built-ins is done using an overloaded definition like the one shown in~\Cref{lst:6-int-sol} for integer variables.
|
|
If the value of the variable in question becomes known at compile time, we use that value instead.
|
|
Otherwise, we replace the function call with a type specific \mzninline{int_sol} predicate, which is the constraint that will be executed by the solver.
|
|
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_sol_function.mzn}
|
|
\caption{\label{lst:6-int-sol} MiniZinc definition of the \mzninline{sol}
|
|
function for integer variables}
|
|
\end{listing}
|
|
|
|
To improve the compilation of the model further, we use the declared bounds of the argument (\mzninline{lb(x)..ub(x)}) to constrain the variable returned by \mzninline{sol}.
|
|
This bounds information is important for the compiler to be able to generate the most efficient \flatzinc\ code for expressions involving \mzninline{sol}.
|
|
The compilation of \mzninline{last_val} is similar to that for \mzninline{sol}.
|
|
|
|
\paragraph{Random number functions}
|
|
|
|
Calls to the random number functions have been renamed by appending \texttt{\_slv}, so that the compiler does not simply evaluate them statically.
|
|
The definition of these new functions follows the same pattern as for \mzninline{sol}, \mzninline{status}, and \mzninline{last_val}.
|
|
The MiniZinc definition of the \mzninline{uniform_nbh} function is shown in \Cref{lst:6-int-rnd}\footnote{Random number functions need to be marked as \mzninline{::impure} for the compiler not to apply \gls{cse} when they are called multiple times with the same arguments.}.
|
|
Note that the function accepts variable arguments \mzninline{l} and \mzninline{u}, so that it can be used in combination with other functions, such as \mzninline{sol}.
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_uniform_slv.mzn}
|
|
\caption{\label{lst:6-int-rnd} MiniZinc definition of the
|
|
\mzninline{uniform_nbh} function for floats}
|
|
\end{listing}
|
|
|
|
\subsection{Solver support for restart-based built-ins}
|
|
|
|
We will now show the minimal extensions required from a solver to interpret the new \flatzinc\ constraints and, consequently, to execute \gls{lns} definitions expressed in \minizinc{}.
|
|
|
|
First, the solver needs to parse and support the restart annotations of~\cref{lst:6-restart-ann}.
|
|
Many solvers already support all this functionality.
|
|
Second, the solver needs to be able to parse the new constraints \mzninline{status}, and all versions of \mzninline{sol}, \mzninline{last_val}, and random number functions like \mzninline{float_uniform}.
|
|
In addition, for the new constraints the solver needs to:
|
|
|
|
\begin{itemize}
|
|
\item \mzninline{status(s)}: record the status of the previous restart, and fix \mzninline{s} to the recorded status.
|
|
\item \mzninline{sol(x, sx)} (variants): constrain \mzninline{sx} to be equal to the value of \mzninline{x} in the incumbent solution.
|
|
If there is no incumbent solution, it has no effect.
|
|
\item \mzninline{last_val(x, lx)} (variants): constrain \mzninline{lx} to take the last value assigned to \mzninline{x} during search.
|
|
If no value was ever assigned, it has no effect.
|
|
Note that many solvers (in particular \gls{sat} and \gls{lcg} solvers) already track \mzninline{last_val} for their variables for use in search.
|
|
To support \gls{lns} a solver must at least track the \emph{last value} of each of the variables involved in such a constraint.
|
|
This is straightforward by using the \mzninline{last_val} propagator itself.
|
|
It wakes up whenever the first argument is fixed, and updates the last value (a non-backtrackable value).
|
|
\item Random number functions: fix their variable argument to a random number in the appropriate probability distribution.
|
|
\end{itemize}
|
|
|
|
Importantly, these constraints need to be propagated in a way that their effects can be undone for the next restart.
|
|
Typically, this means the solver must not propagate these constraints in the root node of the search.
|
|
|
|
Modifying a solver to support this functionality is straightforward if it already has a mechanism for posting constraints during restarts.
|
|
We have implemented these extensions for both \gls{gecode} (110 new lines of code) and \gls{chuffed} (126 new lines of code).
|
|
|
|
For example, consider the model from \cref{lst:6-basic-complete} again.
|
|
\Cref{lst:6-flat-pred} shows a part of the \flatzinc\ that arises from compiling
|
|
|
|
\begin{mzn}
|
|
basic_lns(uniform_neighbourhood(x, 0.2))}
|
|
\end{mzn}
|
|
|
|
\noindent{}assuming that \mzninline{index_set(x) = 1..n}.
|
|
|
|
\Lrefrange{line:6:status:start}{line:6:status:end} define a Boolean variable \mzninline{b1} that is true if-and-only-if the status is not \mzninline{START}.
|
|
The second block of code (\lrefrange{line:6:x1:start}{line:6:x1:end}) represents the decomposition of the expression
|
|
|
|
\begin{mzn}
|
|
(status() != START /\ uniform(0.0,1.0) > 0.2) -> x[1] = sol(x[1])
|
|
\end{mzn}
|
|
|
|
which is the result of merging the implication from the \mzninline{basic_lns} predicate with the \mzninline{if} expression from \mzninline{uniformNeighbourhood}.
|
|
The code first introduces and constrains a variable for the random number, then adds two Boolean variables: \mzninline{b2} is constrained to be true if-and-only-if the random number is greater than \(0.2\); while \mzninline{b3} is constrained to be the conjunction of the two.
|
|
\lref{line:6:x1} constrains \mzninline{x1} to be the value of \mzninline{x[1]} in the previous solution.
|
|
Finally, the half-reified constraint in \lref{line:6:x1:end} implements
|
|
|
|
\begin{mzn}
|
|
b3 -> x[1] = sol(x[1])
|
|
\end{mzn}
|
|
|
|
We have omitted the similar code generated for \mzninline{x[2]} to \mzninline{x[n]}.
|
|
Note that the \flatzinc\ shown here has been simplified for presentation.
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_basic_complete_transformed.mzn}
|
|
\caption{\label{lst:6-flat-pred} \flatzinc{} that results from compiling \\
|
|
\mzninline{basic_lns(uniformNeighbourhood(x,0.2))}.}
|
|
\end{listing}
|
|
|
|
The first time the solver is invoked, it sets \mzninline{s} to 1 (\mzninline{START}).
|
|
Propagation will fix \mzninline{b1} to \mzninline{false} and \mzninline{b3} to \mzninline{false}.
|
|
Therefore, the implication in \lref{line:6:x1:end} is not activated, leaving \mzninline{x[1]} unconstrained.
|
|
The neighbourhood constraints are effectively switched off.
|
|
|
|
When the solver restarts, all the special propagators are re-executed.
|
|
Now \mzninline{s} is not 1, and \mzninline{b1} will be set to \mzninline{true}.
|
|
The \mzninline{float_random} propagator assigns \mzninline{rnd1} a new random value and, depending on whether it is greater than \mzninline{0.2}, the Boolean variables \mzninline{b2}, and consequently \mzninline{b3} will be assigned.
|
|
If it is \mzninline{true}, the constraint in line \lref{line:6:x1:end} will become active and assign \mzninline{x[1]} to its value in the previous solution.
|
|
|
|
Furthermore, it is not strictly necessary to guard \mzninline{int_uniform} against being invoked before the first solution is found, since the \mzninline{sol} constraints will simply not propagate anything in case no solution has been recorded yet, but we use this simple example to illustrate how these Boolean conditions are compiled and evaluated.
|
|
|
|
\section{An Incremental Interface for Constraint Modelling Languages}%
|
|
\label{sec:6-incremental-compilation}
|
|
|
|
As an alternative approach to run \gls{meta-search} algorithm, we propose the possibility of incremental flattening.
|
|
The execution of any
|
|
|
|
In order to support incremental flattening, the \nanozinc\ interpreter must be able to process \nanozinc\ calls \emph{added} to an existing \nanozinc\ program, as well as to \emph{remove} calls from an existing \nanozinc\ program.
|
|
Adding new calls is straightforward, since \nanozinc\ is already processed call-by-call.
|
|
|
|
Removing a call, however, is not so simple.
|
|
When we remove a call, all effects the call had on the \nanozinc\ program have to be undone, including results of propagation, \gls{cse} and other simplifications.
|
|
|
|
\begin{example}\label{ex:6-incremental}
|
|
Consider the following \minizinc\ fragment:
|
|
|
|
\begin{mzn}
|
|
constraint x < 10;
|
|
constraint y < x;
|
|
\end{mzn}
|
|
|
|
After evaluating the first constraint, the domain of \mzninline{x} is changed to be less than 10\.
|
|
Evaluating the second constraint causes the domain of \mzninline{y} to be less than 9\.
|
|
If we now, however, try to remove the first constraint, it is not just the direct inference on the domain of \mzninline{x} that has to be undone, but also any further effects of those changes --- in this case, the changes to the domain of \mzninline{y}.
|
|
|
|
\end{example}
|
|
|
|
Due to this complex interaction between calls, we only support the removal of calls in reverse chronological order, also known as \textit{backtracking}.
|
|
The common way of implementing backtracking is using a \textit{trail} data structure~\autocite{warren-1983-wam}.
|
|
The trail records all changes to the \nanozinc\ program:
|
|
|
|
\begin{itemize}
|
|
\item The addition or removal of new variables or constraints,
|
|
\item changes made to the domains of variables,
|
|
\item additions to the \gls{cse} table, and
|
|
\item substitutions made due to equality propagation.
|
|
\end{itemize}
|
|
|
|
These changes can be caused by the evaluation of a call, propagation, or \gls{cse}.
|
|
When a call is removed, the corresponding changes can now be undone by reversing any action recorded on the trail up to the point where the call was added.
|
|
|
|
In order to limit the amount of trailing required, the programmer must create explicit \textit{choice points} to which the system state can be reset.
|
|
In particular, this means that if no choice point was created before the initial model was flattened, then this flattening can be performed without any trailing.
|
|
|
|
\begin{example}\label{ex:6-trail}
|
|
Let us look again at the resulting \nanozinc\ code from \cref{ex:rew-absreif}:
|
|
|
|
\todo{Fix example to new syntax}
|
|
% \begin{nzn}
|
|
% c @$\mapsto$@ true @$\sep$@ []
|
|
% x @$\mapsto$@ mkvar(-10..10) @$\sep$@ []
|
|
% y @$\mapsto$@ mkvar(-10..10) @$\sep$@ []
|
|
% true @$\mapsto$@ true @$\sep$@ []
|
|
% \end{nzn}
|
|
|
|
Assume that we added a choice point before posting the constraint \mzninline{c}.
|
|
Then the trail stores the \emph{inverse} of all modifications that were made to the \nanozinc\ as a result of \mzninline{c} (where \(\mapsfrom\) denotes restoring an identifier, and \(\lhd\) \texttt{+}/\texttt{-} respectively denote attaching and detaching constraints):
|
|
|
|
\todo{Fix example to new syntax}
|
|
% \begin{nzn}
|
|
% % Posted c
|
|
% true @$\lhd$@ -[c]
|
|
% % Propagated c = true
|
|
% c @$\mapsfrom$@ mkvar(0,1) @$\sep$@ []
|
|
% true @$\lhd$@ +[c]
|
|
% % Simplified bool_or(b1, true) = true
|
|
% b2 @$\mapsfrom$@ bool_or(b1, c) @$\sep$@ []
|
|
% true @$\lhd$@ +[b2]
|
|
% % b1 became unused...
|
|
% b1 @$\mapsfrom$@ int_gt(t, y) @$\sep$@ []
|
|
% % causing t, then b0 and finally z to become unused
|
|
% t @$\mapsfrom$@ z @$\sep$@ [b0]
|
|
% b0 @$\mapsfrom$@ int_abs(x, z) @$\sep$@ []
|
|
% z @$\mapsfrom$@ mkvar(-infinity,infinity) @$\sep$@ []
|
|
% \end{nzn}
|
|
|
|
To reconstruct the \nanozinc\ program at the choice point, we simply apply the changes recorded in the trail, in reverse order.
|
|
\end{example}
|
|
|
|
\subsection{Incremental Solving}
|
|
|
|
Ideally, the incremental changes made by the interpreter would also be applied incrementally to the solver.
|
|
This requires the solver to support both the dynamic addition and removal of variables and constraints.
|
|
While some solvers can support this functionality, most solvers have limitations.
|
|
The system can therefore support solvers with different levels of an incremental interface:
|
|
|
|
\begin{itemize}
|
|
|
|
\item Using a non-incremental interface, the solver is reinitialised with the updated \nanozinc\ program every time.
|
|
In this case, we still get a performance benefit from the improved flattening time, but not from incremental solving.
|
|
|
|
\item Using a \textit{warm-starting} interface, the solver is reinitialised with the updated program as above, but it is also given a previous solution to initialise some internal data structures.
|
|
In particular for mathematical programming solvers, this can result in dramatic performance gains compared to ``cold-starting'' the solver every time.
|
|
|
|
\item Using a fully incremental interface, the solver is instructed to apply the changes made by the interpreter.
|
|
In this case, the trail data structure is used to compute the set of \nanozinc\ changes since the last choice point.
|
|
|
|
\end{itemize}
|
|
|
|
|
|
\section{Experiments}\label{sec:inc-experiments}
|
|
|
|
We have created a prototype implementation of the architecture presented in the preceding sections.
|
|
It consists of a compiler from \minizinc\ to \microzinc{}, and an incremental \microzinc\ interpreter producing \nanozinc{}.
|
|
The system supports a significant subset of the full \minizinc\ language; notable features that are missing are support for set and float variables, option types, and compilation of model output expressions and annotations.
|
|
We will release our implementation under an open-source license and can make it available to the reviewers upon request.
|
|
|
|
The implementation is not optimised for performance yet, but was created as a faithful implementation of the developed concepts, in order to evaluate their suitability and provide a solid baseline for future improvements.
|
|
In the following we present experimental results on basic flattening performance as well as incremental flattening and solving that demonstrate the efficiency gains that are possible thanks to the new architecture.
|
|
|
|
\subsection{Incremental Flattening and Solving}
|
|
|
|
To demonstrate the advantage that the incremental processing of \minizinc\ offers, we present a runtime evaluation of two meta-heuristics implemented using our prototype interpreter.
|
|
For both meta-heuristics, we evaluate the performance of fully re-evaluating and solving the instance from scratch, compared to the fully incremental evaluation and solving.
|
|
The solving in both tests is performed by the \gls{gecode} \gls{solver}, version 6.1.2, connected using the fully incremental API\@.
|
|
|
|
\paragraph{\glsentrytext{gbac}} The \glsaccesslong{gbac} problem \autocite{chiarandini-2012-gbac} consists of scheduling the courses in a curriculum subject to load limits on the number of courses for each period, prerequisites for courses, and preferences of teaching periods by teaching staff.
|
|
It has been shown~\autocite{dekker-2018-mzn-lns} that Large Neighbourhood Search (\gls{lns}) is a useful meta-heuristic for quickly finding high quality solutions to this problem.
|
|
In \gls{lns}, once an initial (sub-optimal) solution is found, constraints are added to the problem that restrict the search space to a \textit{neighbourhood} of the previous solution.
|
|
After this neighbourhood has been explored, the constraints are removed, and constraints for a different neighbourhood are added.
|
|
This is repeated until a sufficiently high solution quality has been reached.
|
|
|
|
We can model a neighbourhood in \minizinc\ as a predicate that, given the values of the variables in the previous solution, posts constraints to restrict the search.
|
|
The following predicate defines a suitable neighbourhood for the \gls{gbac} problem:
|
|
|
|
\begin{mzn}
|
|
predicate random_allocation(array[int] of int: sol) =
|
|
forall(i in courses) (
|
|
(uniform(0,99) < 80) -> (period_of[i] == sol[i])
|
|
);
|
|
|
|
predicate free_period() =
|
|
let {
|
|
int: period = uniform(periods)
|
|
} in forall(i in courses where sol(period_of[i]) != period) (
|
|
period_of[i] = sol(period_of[i])
|
|
);
|
|
\end{mzn}
|
|
|
|
When this predicate is called with a previous solution \mzninline{sol}, then every \mzninline{period_of} variable has an \(80\%\) chance to be fixed to its value in the previous solution.
|
|
With the remaining \(20\%\), the variable is unconstrained and will be part of the search for a better solution.
|
|
|
|
In a non-incremental architecture, we would re-flatten the original model plus the neighbourhood constraint for each iteration of the \gls{lns}.
|
|
In the incremental \nanozinc\ architecture, we can easily express \gls{lns} as a repeated addition and retraction of the neighbourhood constraints.
|
|
We implemented both approaches using the \nanozinc\ prototype, with the results shown in \Cref{fig:6-gbac}.
|
|
The incremental \nanozinc\ translation shows a 12x speedup compared to re-compiling the model from scratch in each iteration.
|
|
For this particular problem, incrementally instructing the target solver (\gls{gecode}) does not lead to a significant reduction in runtime.
|
|
|
|
\paragraph{Radiation} Our second experiment is based on a problem of planning cancer radiation therapy treatment using multi-leaf collimators \autocite{baatar-2011-radiation}.
|
|
Two characteristics mark the quality of a solution: the amount of time the patient is exposed to radiation, and the number of ``shots'' or different angles the treatment requires.
|
|
However, the first characteristic is considered more important than the second.
|
|
The problem therefore has a lexicographical objective: a solution is better if it requires a strictly shorter exposure time, or the same exposure time but a lower number of ``shots''.
|
|
|
|
\minizinc\ \glspl{solver} do not support lexicographical objectives directly, but we can instead repeatedly solve a model instance and add a constraint to ensure that the lexicographical objective improves.
|
|
When the solver proves that no better solution can be found, the last solution is known to be optimal.
|
|
Given two variables \mzninline{exposure} and \mzninline{shots}, once we have found a solution with \mzninline{exposure=e} and \mzninline{shots=s}, we can add the constraint
|
|
|
|
\begin{mzn}
|
|
constraint exposure < e \/ (exposure = e /\ shots < s)
|
|
\end{mzn}
|
|
|
|
\noindent{}expressing the lexicographic ordering, and continue the search.
|
|
Since each added lexicographic constraint is strictly stronger than the previous one, we never have to retract previous constraints.
|
|
|
|
\begin{figure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_cmp_lex.pdf}
|
|
\caption{\label{subfig:inc-cmp-lex}Radiation}
|
|
\end{subfigure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_cmp_lns.pdf}
|
|
\caption{\label{subfig:inc-cmp-lns}GBAC}
|
|
\end{subfigure}
|
|
\caption{\label{fig:inc-cmp} A comparison of the two new incremental techniques and a recompilation strategy.}
|
|
\end{figure}
|
|
|
|
As shown in \cref{fig:6-radiation}, the incremental processing of the added \mzninline{lex_less} calls is a clear improvement over the re-evaluation of the whole model.
|
|
The translation shows a 13x speedup on average, and even the time spent solving is reduced by 33\%.
|
|
|
|
\subsection{Compiling neighbourhoods}
|
|
|
|
\todo{Decide what to do with these}
|
|
% Table column headings
|
|
\newcommand{\intobj}{\int}
|
|
\newcommand{\minobj}{\min}
|
|
\newcommand{\devobj}{\sigma}
|
|
\newcommand{\nodesec}{n/s}
|
|
\newcommand{\gecodeStd}{\textsf{gecode}}
|
|
\newcommand{\gecodeReplay}{\textsf{gecode-replay}}
|
|
\newcommand{\gecodeMzn}{\textsf{gecode-fzn}}
|
|
\newcommand{\chuffedStd}{\textsf{chuffed}}
|
|
\newcommand{\chuffedMzn}{\textsf{chuffed-fzn}}
|
|
|
|
We will now show that a solver that evaluates the compiled \flatzinc\ \gls{lns} specifications can (a) be effective and (b) incur only a small overhead compared to a dedicated implementation of the neighbourhoods.
|
|
|
|
To measure the overhead, we implemented our new approach in \gls{gecode}~\autocite{gecode-2021-gecode}.
|
|
The resulting solver (\gecodeMzn{} in the tables below) has been instrumented to also output the domains of all model variables after propagating the new special constraints.
|
|
We implemented another extension to \gls{gecode} (\gecodeReplay) that simply reads the stream of variable domains for each restart, essentially replaying the \gls{lns} of \gecodeMzn\ without incurring any overhead for evaluating the neighbourhoods or handling the additional variables and constraints.
|
|
Note that this is a conservative estimate of the overhead: \gecodeReplay\ has to perform \emph{less} work than any real \gls{lns} implementation.
|
|
|
|
In addition, we also present benchmark results for the standard release of \gls{gecode} 6.0 without \gls{lns} (\gecodeStd); as well as \chuffedStd{}, the development version of Chuffed; and \chuffedMzn{}, Chuffed performing \gls{lns} with \flatzinc\ neighbourhoods.
|
|
These experiments illustrate that the \gls{lns} implementations indeed perform well compared to the standard solvers.
|
|
Details of the computational environment and resources used in our experiments are outlined in \cref{ch:benchmarks}.
|
|
{lns} benchmarks are repeated with 10 different random seeds and the average is shown.
|
|
The overall timeout for each run is 120 seconds.
|
|
|
|
We ran experiments for three models from the MiniZinc Challenge~\autocite{stuckey-2010-challenge, stuckey-2014-challenge} (\texttt{gbac}, \texttt{steelmillslab}, and \texttt{rcpsp-wet}).
|
|
The best objective found during the \minizinc\ Challenge is shown for every instance (\emph{best known}).
|
|
|
|
For each solving method we measured the average integral of the model objective after finding the initial solution (\(\intobj\)), the average best objective found (\(\minobj\)), and the standard deviation of the best objective found in percentage (\%), which is shown as the superscript on \(\minobj\) when running \gls{lns}.
|
|
%and the average number of nodes per one second (\nodesec).
|
|
The underlying search strategy used is the fixed search strategy defined in the model.
|
|
For each model we use a round-robin evaluation (\cref{lst:6-round-robin}) of two neighbourhoods: a neighbourhood that destroys \(20\%\) of the main decision variables (\cref{lst:6-lns-minisearch-pred}) and a structured neighbourhood for the model (described below).
|
|
The restart strategy is
|
|
|
|
\begin{mzn}
|
|
::restart_constant(250) ::restart_on_solution
|
|
\end{mzn}
|
|
|
|
\subsubsection{\texttt{gbac}}
|
|
|
|
% GBAC
|
|
\begin{listing}[b]
|
|
\mznfile{assets/listing/6_gbac_neighbourhood.mzn}
|
|
\caption{\label{lst:6-free-period}\texttt{gbac}: neighbourhood freeing all courses in a period.}
|
|
\end{listing}
|
|
|
|
The \gls{gbac} problem comprises courses having a specified number of credits and lasting a certain number of periods, load limits of courses for each period, prerequisites for courses, and preferences of teaching periods for professors.
|
|
A detailed description of the problem is given in~\autocite{chiarandini-2012-gbac}.
|
|
The main decisions are to assign courses to periods, which is done via the variables \mzninline{period_of} in the model.
|
|
\cref{lst:6-free-period} shows the neighbourhood chosen, which randomly picks one period and frees all courses that are assigned to it.
|
|
|
|
\begin{figure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_obj_gecode_gbac.pdf}
|
|
\caption{\label{subfig:inc-obj-gecode-gbac}\gls{gecode}}
|
|
\end{subfigure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_obj_chuffed_gbac.pdf}
|
|
\caption{\label{subfig:inc-obj-chuffed-gbac}\gls{chuffed}}
|
|
\end{subfigure}
|
|
\caption{\label{fig:inc-obj-gbac} \todo{GBAC benchmarks description}}
|
|
\end{figure}
|
|
|
|
The results for \texttt{gbac} in \cref{tab:6-gbac} show that the overhead introduced by \gecodeMzn\ w.r.t.
|
|
\gecodeReplay{} is quite low, and both their results are much better than the baseline \gecodeStd{}.
|
|
Since learning is not very effective for \gls{gbac}, the performance of \chuffedStd\ is inferior to \gls{gecode}.
|
|
However, \gls{lns} again significantly improves over standard Chuffed.
|
|
|
|
\subsubsection{\texttt{steelmillslab}}
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_steelmillslab_neighbourhood.mzn}
|
|
\caption{\label{lst:6-free-bin}\texttt{steelmillslab}: Neighbourhood that frees
|
|
all orders assigned to a selected slab.}
|
|
\end{listing}
|
|
|
|
The Steel Mill Slab design problem consists of cutting slabs into smaller ones, so that all orders are fulfilled while minimising the wastage.
|
|
The steel mill only produces slabs of certain sizes, and orders have both a size and a colour.
|
|
We have to assign orders to slabs, with at most two different colours on each slab.
|
|
The model uses the variables \mzninline{assign} for deciding which order is assigned to which slab.
|
|
\Cref{lst:6-free-bin} shows a structured neighbourhood that randomly selects a slab and frees the orders assigned to it in the incumbent solution.
|
|
These orders can then be freely reassigned to any other slab.
|
|
|
|
\begin{figure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_obj_gecode_steelmillslab.pdf}
|
|
\caption{\label{subfig:inc-obj-gecode-steelmillslab}\gls{gecode}}
|
|
\end{subfigure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_obj_chuffed_steelmillslab.pdf}
|
|
\caption{\label{subfig:inc-obj-chuffed-steelmillslab}\gls{chuffed}}
|
|
\end{subfigure}
|
|
\caption{\label{fig:inc-obj-steelmillslab} \todo{steelmillslab benchmarks description}}
|
|
\end{figure}
|
|
|
|
For this problem a solution with zero wastage is always optimal.
|
|
The use of \gls{lns} makes these instances easy, as all the \gls{lns} approaches find optimal solutions.
|
|
As \cref{tab:6-steelmillslab} shows, \gecodeMzn\ is again slightly slower than \gecodeReplay\ (the integral is slightly larger).
|
|
While \chuffedStd\ significantly outperforms \gecodeStd\ on this problem, once we use \gls{lns}, the learning in \chuffedMzn\ is not advantageous compared to \gecodeMzn\ or \gecodeReplay{}.
|
|
Still, \chuffedMzn\ outperforms \chuffedStd\ by always finding an optimal solution.
|
|
|
|
% RCPSP/wet
|
|
\subsubsection{\texttt{rcpsp-wet}}
|
|
|
|
\begin{listing}
|
|
\mznfile{assets/listing/6_rcpsp_neighbourhood.mzn}
|
|
\caption{\label{lst:6-free-timeslot}\texttt{rcpsp-wet}: Neighbourhood freeing
|
|
all tasks starting in the drawn interval.}
|
|
\end{listing}
|
|
|
|
The Resource-Constrained Project Scheduling problem with Weighted Earliness and Tardiness cost, is a classic scheduling problem in which tasks need to be scheduled subject to precedence constraints and cumulative resource restrictions.
|
|
The objective is to find an optimal schedule that minimises the weighted cost of the earliness and tardiness for tasks that are not completed by their proposed deadline.
|
|
The decision variables in array \mzninline{s} represent the start times of each task in the model.
|
|
\Cref{lst:6-free-timeslot} shows our structured neighbourhood for this model.
|
|
It randomly selects a time interval of one-tenth the length of the planning horizon and frees all tasks starting in that time interval, which allows a reshuffling of these tasks.
|
|
|
|
\begin{figure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_obj_gecode_rcpspw.pdf}
|
|
\caption{\label{subfig:inc-obj-gecode-rcpspw}\gls{gecode}}
|
|
\end{subfigure}
|
|
\begin{subfigure}[b]{0.5\linewidth}
|
|
\includegraphics[width=\columnwidth]{assets/img/inc_obj_chuffed_rcpspw.pdf}
|
|
\caption{\label{subfig:inc-obj-chuffed-rcpspw}\gls{chuffed}}
|
|
\end{subfigure}
|
|
\caption{\label{fig:inc-obj-rcpspw} \todo{RCPSP-Wet benchmarks description}}
|
|
\end{figure}
|
|
|
|
\cref{tab:6-rcpsp-wet} shows that \gecodeReplay\ and \gecodeMzn\ perform almost identically, and substantially better than baseline \gecodeStd\ for these instances.
|
|
The baseline learning solver, \chuffedStd{}, is the best overall on the easy examples, but \gls{lns} makes it much more robust.
|
|
The poor performance of \chuffedMzn\ on the last instance is due to the fixed search, which limits the usefulness of no-good learning.
|
|
|
|
\subsubsection{Summary}
|
|
|
|
The results show that \gls{lns} outperforms the baseline solvers, except for benchmarks where we can quickly find and prove optimality.
|
|
|
|
However, the main result from these experiments is that the overhead introduced by our \flatzinc\ interface, when compared to an optimal \gls{lns} implementation, is relatively small.
|
|
We have additionally calculated the rate of search nodes explored per second and, across all experiments, \gecodeMzn\ achieves around 3\% fewer nodes per second than \gecodeReplay{}.
|
|
This overhead is caused by propagating the additional constraints in \gecodeMzn{}.
|
|
Overall, the experiments demonstrate that the compilation approach is an effective and efficient way of adding \gls{lns} to a modelling language with minimal changes to the solver.
|