949 lines
50 KiB
TeX
949 lines
50 KiB
TeX
\chapter{Incremental Processing}\label{ch:incremental}
|
|
%************************************************
|
|
|
|
In previous chapters we explored the compilation of constraint models for a
|
|
\gls{solver} as a definitive linear process, but to solve real-world problems
|
|
\gls{meta-search} algorithms are often used. These methods usually require
|
|
solving almost the same combinatorial problem repeatedly, with only slight
|
|
modifications, thousands of times. Examples of these methods are:
|
|
|
|
\begin{itemize}
|
|
\item Multi-objective search \autocite{jones-2002-multi-objective}. Optimising
|
|
multiple objectives is often not supported directly in solvers. Instead
|
|
it can be solved using a \gls{meta-search} approach: find a solution to
|
|
a (single-objective) problem, then add more constraints to the original
|
|
problem and repeat.
|
|
\item \gls{lns} \autocite{shaw-1998-local-search}. This is a very successful
|
|
\gls{meta-search} algorithm to quickly improve solution quality. After
|
|
finding a (sub-optimal) solution to a problem, constraints are added to
|
|
restrict the search in the \gls{neighbourhood} of that solution. When a
|
|
new solution is found, the constraints are removed, and constraints for
|
|
a new \gls{neighbourhood} are added.
|
|
\item Online Optimisation \autocite{jaillet-2021-online}. These techniques can
|
|
be employed when the problem rapidly changes. A problem instance is
|
|
continuously updated with new data, such as newly available jobs to be
|
|
scheduled or customer requests to be processed.
|
|
\item Diverse Solution Search \autocite{hebrard-2005-diverse}. Here we aim to
|
|
provide a set of solutions that are sufficiently different from each
|
|
other in order to give human decision makers an overview of the solution
|
|
space. Diversity can be achieved by repeatedly solving a problem
|
|
instance with different objectives.
|
|
\item Interactive Optimisation \autocite{belin-2014-interactive}. In some
|
|
scenarios it might be useful to allow a user to directly provide
|
|
feedback on solutions found by the solver. The feedback in the form of
|
|
constraint are added back into the problem, and a new solution is
|
|
generated. Users may also take back some earlier feedback and explore
|
|
different aspects of the problem to arrive at the best solution that
|
|
suits their needs.
|
|
\end{itemize}
|
|
|
|
All of these examples have in common that a problem instance is solved, new
|
|
constraints are added, the resulting instance is solved again, and constraints
|
|
may be removed again.
|
|
|
|
The usage of these methods is not new to \gls{constraint-modelling} and they
|
|
have proven to be very useful \autocite{schrijvers-2013-combinators,
|
|
rendl-2015-minisearch, schiendorfer-2018-minibrass, ek-2020-online,
|
|
ingmar-2020-diverse}. In its most basic form, a simple scripting language is
|
|
sufficient to implement these methods, by repeatedly calling on the
|
|
\gls{constraint-modelling} infrastructure to compile and solve the adjusted
|
|
constraint models. While improvements of the compilation of constraint models,
|
|
such as the ones discussed in previous chapters, can increase the performance of
|
|
these approaches, the overhead of re-compiling an almost identical model may
|
|
still prove prohibitive, warranting direct support from the
|
|
\gls{constraint-modelling} infrastructure. In this chapter we introduces two
|
|
methods to provide this support:
|
|
|
|
\begin{itemize}
|
|
\item Using a minimal extension of existing solvers, we can compile
|
|
\gls{meta-search} algorithms into efficient solver-level specifications
|
|
based on solver restart, avoiding re-compilation all-together.
|
|
\item We can add an interface for adding and removing constraints in the
|
|
\gls{constraint-modelling} infrastructure and avoid re-compilation where
|
|
possible.
|
|
\end{itemize}
|
|
|
|
Although it might sound like the first option is always the best one, it should
|
|
be noted that this option cannot always be used. It might not be possible to
|
|
extend the target \gls{solver} (or it might not be allowed in case of some
|
|
proprietary \glspl{solver}). Furthermore, the modelling of \gls{meta-search}
|
|
algorithms using solver restarts is limited \textbf{TODO: in some way}.
|
|
|
|
The rest of the chapter is organised as follows. \Cref{sec:6-modelling}
|
|
discusses the declarative modelling of \gls{meta-search} algorithms using \cmls.
|
|
\Cref{sec:6-solver-extension} introduces the method to compile these
|
|
\gls{meta-search} specifications into efficient solver-level specifications that
|
|
only require a small extension of existing \glspl{solver}.
|
|
\Cref{sec:6-incremental-compilation} introduces the alternative method that
|
|
extends the \gls{constraint-modelling} infrastructure with an interface to add
|
|
and remove constraints from an existing model while avoiding recompilation.
|
|
\Cref{sec:6-experiments} reports on the experimental results of both approaches.
|
|
Finally, \Cref{sec:6-conclusion} presents the conclusions.
|
|
|
|
|
|
\section{Modelling of 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 might 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}
|
|
\highlightfile{assets/mzn/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}
|
|
\highlightfile{assets/mzn/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.
|
|
|
|
define a small number of 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 \minizinc\ 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}[t]
|
|
\highlightfile{assets/mzn/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 then just 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}[t]
|
|
\highlightfile{assets/mzn/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}:
|
|
|
|
\mzninline{predicate basic_lns(var bool: nbh) = (status()!=START -> nbh);}
|
|
|
|
In order to use this predicate with the \mzninline{on_restart} annotation, we
|
|
cannot simply pass \mzninline{basic_lns(uniformNeighbourhood(x,0.2))}. First of
|
|
all, calling \mzninline{uniformNeighbourhood} 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}[t]
|
|
\highlightfile{assets/mzn/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.
|
|
%\paragraph{Round-robin \gls{lns}}
|
|
\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 (\mzninline{last_val(select)}). This will
|
|
activate a different neighbourhood for each restart
|
|
(\lref{line:6:roundrobin:post}).
|
|
|
|
\begin{listing}[t]
|
|
\highlightfile{assets/mzn/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{uniformNeighbourhood} 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}[t]
|
|
\highlightfile{assets/mzn/6_adaptive.mzn}
|
|
\caption{\label{lst:6-adaptive} A simple adaptive neighbourhood}
|
|
\end{listing}
|
|
|
|
\subsection{Meta-heuristics}
|
|
|
|
The \gls{lns} strategies we have seen so far rely on the default behaviour of
|
|
\minizinc\ solvers to use branch-and-bound 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. In particular, we use
|
|
\mzninline{restart_without_objective} 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 the globally 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:
|
|
|
|
{
|
|
\scriptsize
|
|
\highlightfile{assets/mzn/6_hill_climbing.mzn}
|
|
}
|
|
|
|
It takes advantage of the fact that the declared objective function is available
|
|
through the built-in variable \mzninline{_objective}.
|
|
%
|
|
A simulated annealing strategy is also easy to
|
|
express:
|
|
|
|
\highlightfile{assets/mzn/6_simulated_annealing.mzn}
|
|
|
|
\section{Compilation of Meta-Search}
|
|
\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, with the exception of 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 an \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_nbh}, 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{uniformNeighbourhood} 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}[t]
|
|
\highlightfile{assets/mzn/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}[t]
|
|
\highlightfile{assets/mzn/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{\_nbh}, 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} \autocite{stuckey-2013-functions} if 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}[t]
|
|
\highlightfile{assets/mzn/6_uniform_neighbourhood.mzn}
|
|
\caption{\label{lst:6-int-rnd} MiniZinc definition of the
|
|
\mzninline{uniform_nbh} function for floats}
|
|
\end{listing}
|
|
|
|
\subsection{Solver support for \gls{lns} \glsentrytext{flatzinc}}
|
|
|
|
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
|
|
SAT and 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 Gecode (110 new lines of code) and 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
|
|
\mzninline{basic_lns(uniformNeighbourhood(x, 0.2))}, 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 iff 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
|
|
|
|
\mzninline{(status() != START /\ uniform(0.0,1.0) > 0.2) -> x[1] = sol(x[1])}
|
|
%
|
|
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 iff the random number is greater than 0.2; while
|
|
\mzninline{b3} is constrained to be the conjunction \mzninline{status()!=START
|
|
/\ uniform(0.0,1.0)>0.2}. \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 \mzninline{b3 -> x[1]=sol(x[1])}.
|
|
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}[t]
|
|
\highlightfile{assets/mzn/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 of 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 \mzninline{status()!=START}, 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}
|
|
|
|
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:
|
|
|
|
\highlightfile{assets/mzn/6_incremental.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:absreif}:
|
|
|
|
% \highlightfile{assets/mzn/6_abs_reif_result.mzn}
|
|
|
|
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):
|
|
|
|
% \highlightfile{assets/mzn/6_abs_reif_trail.mzn}
|
|
|
|
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:6-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 Gecode solver, version 6.1.2, connected using the fully incremental API.
|
|
|
|
\paragraph{GBAC}
|
|
The Generalised Balanced Academic Curriculum (GBAC) problem
|
|
\autocite{chiarandini-2012-gbac} is comprised 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 GBAC
|
|
problem:
|
|
|
|
\highlightfile{assets/mzn/6_gbac_neighbourhood.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,
|
|
incrementality in the target solver (Gecode) does not lead to a significant
|
|
reduction in runtime.
|
|
|
|
\begin{figure}
|
|
\centering
|
|
\includegraphics[width=0.5\columnwidth]{assets/img/6_gbac}
|
|
\caption{\label{fig:6-gbac}A run-time performance comparison between incremental
|
|
processing (Incr.) and re-evaluation (Redo) of 5 GBAC \minizinc\ instances
|
|
in the application of \gls{lns} on a 3.4 GHz Quad-Core Intel Core i5 using the
|
|
Gecode 6.1.2 solver. Each run consisted of 2500 iterations of applying
|
|
neighbourhood predicates. Reported times are averages of 10 runs.}
|
|
\end{figure}
|
|
|
|
\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\ solvers 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 \mzninline{exposure < e \/ (exposure = e /\ shots < s)}, 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}
|
|
\centering
|
|
\includegraphics[width=0.5\columnwidth]{assets/img/6_radiation}
|
|
\caption{\label{fig:6-radiation}A run-time performance comparison between
|
|
incremental processing (Incr.) and re-evaluation (Redo) of 9 Radiation
|
|
\minizinc\ instances in the application of Lexicographic objectives on a 3.4
|
|
GHz Quad-Core Intel Core i5 using the Gecode 6.1.2 solver. Each test was run
|
|
to optimality and was conducted 20 times to provide an average.}
|
|
\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
|
|
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 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
|
|
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.\footnote{Our implementations are
|
|
available at
|
|
\texttt{\justify{}https://github.com/Dekker1/\{libminizinc,gecode,chuffed\}} on branches
|
|
containing the keyword \texttt{on\_restart}.} All experiments were run on a
|
|
single core of an Intel Core i5 CPU @ 3.4 GHz with 4 cores and 16 GB RAM running
|
|
MacOS High Sierra. \gls{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
|
|
\mzninline{::restart_constant(250)} \mzninline{::restart_on_solution}.
|
|
|
|
\subsubsection{\texttt{gbac}}
|
|
|
|
% GBAC
|
|
\begin{listing}[b]
|
|
\highlightfile{assets/mzn/6_gbac_neighbourhood.mzn}
|
|
\caption{\label{lst:6-free-period}\texttt{gbac}: neighbourhood freeing all
|
|
courses in a period.}
|
|
\end{listing}
|
|
|
|
The Generalised Balanced Academic Curriculum 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{table}[t]
|
|
\centering
|
|
\input{assets/table/6_gbac}
|
|
\caption{\label{tab:6-gbac}\texttt{gbac} benchmarks}
|
|
\end{table}
|
|
|
|
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 \texttt{gbac}, the performance of \chuffedStd is inferior to
|
|
Gecode. However, \gls{lns} again significantly improves over standard Chuffed.
|
|
|
|
\subsubsection{\texttt{steelmillslab}}
|
|
|
|
\begin{listing}[t]
|
|
\highlightfile{assets/mzn/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{table}[t]
|
|
\centering
|
|
\input{assets/table/6_steelmillslab}
|
|
\caption{\label{tab:6-steelmillslab}\texttt{steelmillslab} benchmarks}
|
|
\end{table}
|
|
|
|
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}[t]
|
|
\highlightfile{assets/mzn/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{table}[b]
|
|
\centering
|
|
\input{assets/table/6_rcpsp-wet}
|
|
\caption{\label{tab:6-rcpsp-wet}\texttt{rcpsp-wet} benchmarks}
|
|
\end{table}
|
|
|
|
\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 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
|
|
nogood 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.
|
|
|
|
\section{Conclusions}
|
|
\label{sec:6-conclusion}
|
|
|