Remaining questions after Guido's notes

This commit is contained in:
Jip J. Dekker 2021-07-27 12:56:54 +10:00
parent b63274965f
commit 91d264597c
No known key found for this signature in database
GPG Key ID: 517DF4A00618C9C3
3 changed files with 68 additions and 49 deletions

View File

@ -4,7 +4,7 @@ var int: cost; % objective function
% --- some constraints defining the problem ---
% The user-defined LNS strategy
predicate my_lns() = basic_lns(uniform_neighbourhood(x, 0.2));
predicate my_lns() = basic_lns(uniform_neighbourhood(x, 0.2));@\Vlabel{line:inc:blns:strat}@
% Solve using my_lns, restart every 500 nodes, overall timeout 120 seconds
solve ::on_restart("my_lns") ::restart_constant(500) ::timeout(120)

View File

@ -3,6 +3,8 @@ predicate simulated_annealing(float: init_temp, float: cooling_rate) =
var float: temp;
} in if status() = START then
temp = init_temp
elseif status() = UNSAT then
complete()
else
temp = last_val(temp) * (1 - cooling_rate) % cool down
/\ _objective < sol(_objective) - ceil(log(uniform(0.0, 1.0)) * temp)

View File

@ -168,28 +168,42 @@ Function \mzninline{status} returns the status of the previous restart, namely:
\item[\mzninline{UNKNOWN}] the last \gls{restart} did not find a \gls{sol}, but did not exhaust its \gls{search-space}.
\end{description}
\noindent{}Function \mzninline{last_val} allows modellers to access the last value assigned to a \variable{}.
If \mzninline{status()=SAT \/ status{}=OPT}, then \mzninline{last_val(x)} and \mzninline{sol(x)} return the same value.
However, if \mzninline{status()=UNSAT \/ status{}=UNKNOWN}, but \mzninline{x} was still (at some point) assigned during search, then that value will be returned by \mzninline{last_val(x)}, whereas \mzninline{sol(x)} will still return the value from the last \gls{sol}.
When \mzninline{x} is assigned at the root node of the search, it has the same value until the next restart.
This mechanism allows us to use \variables{} as state variables.
The value is undefined when \mzninline{status()=START}.
Like \mzninline{sol}, it has versions for all basic \variable{} types.
\begin{listing}
\mznfile{assets/listing/inc_state_access.mzn}
\caption{\label{lst:inc-state-access} Functions for accessing previous \solver{} states.}
\end{listing}
The function \mzninline{last_val} allows modellers to access the last value assigned to a \variable{}.
Like \mzninline{sol}, it has versions for all basic \variable{} types.
If \mzninline{status()=SAT \/ status{}=OPT}, then \mzninline{last_val(x)} and \mzninline{sol(x)} return the same value.
However, if \mzninline{status()=UNSAT \/ status{}=UNKNOWN}, but \mzninline{x} was still (at some point) assigned during search, then that value will be returned by \mzninline{last_val(x)}, whereas \mzninline{sol(x)} will still return the value from the last \gls{sol}.
The value is undefined when \mzninline{status()=START} or when \mzninline{x} was not present during the last \gls{restart}.
If a \variable{} \mzninline{x} is assigned immediately after each restart (before any \glspl{search-decision}), then it is guaranteed that \mzninline{last_val(x)} will return this value after the next restart.
This mechanism allows us to use \variables{} similar fashion to \minisearch{}'s state variables.
In order to be able to initialize the \variables{} used for state access, we interpret \mzninline{on_restart} also before the initial search using the same semantics.
As such, the predicate is also called before the first ``real'' \gls{restart}, but any \constraint{} posted by the predicate will be retracted for the next \gls{restart}.
Of particular interest for the behaviour of \mzninline{last_val} and \mzninline{sol} is the manner in which we identify a \variable{}.
We have defined the behaviour of the \mzninline{on_restart} \gls{annotation} as repeatedly calling the function argument on every restart.
This means that if the call introduces and \glspl{avar}, then it will create unique \glspl{avar} for every restart.
It could thus be said that \mzninline{last_val} and \mzninline{sol} cannot be used on these \glspl{avar}.
However, it is often natural to introduce state variables used within our restart strategy, as we shall soon see.
Therefore, we identify each \variable{} using their variable path \autocite{leo-2015-multipass}.
The path of a \variable{} is designed to give a unique identifier to a \variable{} when an \instance{} is rewritten multiple times.
The \variable{} path is mostly dependent on the \parameter{} arguments to the calls and the value of iterators for which the \variable{} is introduced.
As such, this mechanism will between \glspl{restart} correctly identify the \variables{} for each call to the nullary function in the \mzninline{on_restart} \gls{annotation}.
However, it might lead to slightly unexpected behaviour when using \parameter{} function arguments that do not relate to the \mzninline{avar} introduced in the function.
\paragraph{Parametric neighbourhood selection predicates}
We define standard \gls{neighbourhood} selection strategies as predicates that are parametric over the \glspl{neighbourhood} they should apply.
Since there are many well-known ways to makes a selection of a set of \glspl{neighbourhood}, we define standard \gls{neighbourhood} selection strategies as predicates that are parametric over the \glspl{neighbourhood} they should apply.
For example, we can define a strategy \mzninline{basic_lns} that applies a \gls{lns} \gls{neighbourhood}.
Since \mzninline{on_restart} now also includes the initial search, we apply the \gls{neighbourhood} only if the current status is not \mzninline{START}, as shown in the following predicate.
Since \mzninline{on_restart} now also includes the initial search, we apply the \gls{neighbourhood} only if the current status is not \mzninline{START}.
However, we again face the problem that in \minizinc{} we cannot pass functions or predicates as arguments.
We can however define these predicates to take Boolean \variables{}, the result of evaluating a predicate, as their arguments.
Using this technique, the \mzninline{basic_lns} predicate can be defined as follows.
\begin{mzn}
predicate basic_lns(var bool: nbh) =
@ -198,40 +212,39 @@ Since \mzninline{on_restart} now also includes the initial search, we apply the
endif;
\end{mzn}
In order to use this predicate with the \mzninline{on_restart} \gls{annotation}, we cannot simply call a \gls{neighbourhood} predicate as follows.
Still, in order to use this predicate with the \mzninline{on_restart} \gls{annotation}, we cannot simply call a \gls{neighbourhood} predicate as follows.
\begin{mzn}
basic_lns(uniform_neighbourhood(x, 0.2))
\end{mzn}
\noindent{}Calling \mzninline{uniform_neighbourhood} like this would result in a single evaluation of the predicate, since \minizinc{} employs a call-by-value evaluation strategy.
Furthermore, the \mzninline{on_restart} \gls{annotation} only accepts the name of a nullary predicate.
Therefore, users have to define their overall strategy in a new predicate.
\noindent{}Calling \mzninline{uniform_neighbourhood} like this would result in a single evaluation of the predicate.
Rather, the modeller has to define their overall strategy in a new nullary predicate.
This ensures that both the selection strategy and the \glspl{neighbourhood} are evaluated on every restart.
\Cref{lst:inc-basic-complete} shows a complete example of a basic \gls{lns} model.
\Lref{line:inc:blns:strat} shows the predicate defined to capture the overall strategy.
\begin{listing}
\begin{listing}[p]
\mznfile{assets/listing/inc_basic_complete.mzn}
\caption{\label{lst:inc-basic-complete} A complete \gls{lns} example.}
\end{listing}
We can also define round-robin and adaptive strategies using these primitives.
\Cref{lst:inc-round-robin} defines a round-robin \gls{lns} meta-heuristic, which cycles through a list of \mzninline{N} neighbourhoods \mzninline{nbhs}.
\Cref{lst:inc-round-robin} defines a round-robin \gls{lns} selection strategy, which cycles through a list of \mzninline{N} neighbourhoods \mzninline{nbhs}.
To do this, it uses the \variable{} \mzninline{select}.
In the initialization phase (\mzninline{status()=START}), \mzninline{select} is set to \mzninline{-1}, which means none of the \glspl{neighbourhood} is activated.
In any following \gls{restart}, \mzninline{select} is incremented modulo \mzninline{N}, by accessing the last value assigned in a previous \gls{restart}.
This will activate a different \gls{neighbourhood} for each subsequent \gls{restart} (\lref{line:6:roundrobin:post}).
\begin{listing}
\begin{listing}[p]
\mznfile[l]{assets/listing/inc_round_robin.mzn}
\caption{\label{lst:inc-round-robin} A predicate providing the round-robin meta-heuristic.}
\caption{\label{lst:inc-round-robin} A predicate providing the round-robin selection strategy.}
\end{listing}
%\paragraph{Adaptive \gls{lns}}
For adaptive \gls{lns}, a simple strategy is to change the size of the \gls{neighbourhood} depending on whether the previous size was successful or not.
\Cref{lst:inc-adaptive} shows an adaptive version of the \mzninline{uniform_neighbourhood} that increases the number of free \variables{} when the previous \gls{restart} failed, and decreases it when it succeeded, within the range \([0.6,0.95]\).
\begin{listing}
\begin{listing}[p]
\mznfile{assets/listing/inc_adaptive.mzn}
\caption{\label{lst:inc-adaptive}A simple adaptive neighbourhood.}
\end{listing}
@ -239,36 +252,16 @@ For adaptive \gls{lns}, a simple strategy is to change the size of the \gls{neig
\subsection{Optimization strategies}
The \gls{meta-optimization} algorithms we have seen so far rely on the default behaviour of \minizinc{} \solvers{} to use \gls{bnb} for optimization: when a new \gls{sol} is found, the \solver{} adds a \constraint{} to the remainder of the search to only accept better \glspl{sol}, as defined by the \gls{objective} in the \mzninline{minimize} or \mzninline{maximize} clause of the \mzninline{solve} item.
When combined with \glspl{restart} and \gls{lns}, this is equivalent to a simple hill-climbing meta-heuristic.
When combined with \glspl{restart} and \gls{lns}, this is equivalent to a simple hill-climbing search method.
We can use the constructs introduced above to implement alternative meta-heuristics such as simulated annealing.
We can use the constructs introduced above to implement alternative search method such as simulated annealing.
We use the \mzninline{restart_without_objective} \gls{annotation}, in particular, to tell the solver not to add the \gls{bnb} \constraint{} on \gls{restart}.
It will still use the declared \gls{objective} to decide whether a new \gls{sol} is globally the best one seen so far, and only output those.
This maintains the convention of \minizinc{} \solvers{} that the last \gls{sol} printed at any point in time is the currently best known one.
With \mzninline{restart_without_objective}, the \gls{restart} predicate is now responsible for constraining the \gls{objective}.
Note that a simple hill-climbing (for minimization) can still be defined easily in this context as follows.
\begin{mzn}
predicate hill_climbing() = status() != START -> _objective < sol(_objective);
\end{mzn}
It takes advantage of the fact that the declared \gls{objective} is available through the built-in \variable{} \mzninline{_objective}.
A more interesting example is a simulated annealing strategy.
When using this strategy, the sequence of \glspl{sol} that the \solver{} finds are not required to steadily improve in quality.
Instead, we ask the \solver{} to find a \gls{sol} that is a significant improvement over the previous \gls{sol}.
Over time, we decrease the amount by which we require the \gls{sol} needs to improve until we are just looking for any improvements.
This \gls{meta-optimization} can help improve the qualities of \gls{sol} quickly and thereby reaching the \gls{opt-sol} quicker.
\Cref{lst:inc-sim-ann} show how this strategy is also easy to express using \gls{rbmo}.
\begin{listing}
\mznfile{assets/listing/inc_sim_ann.mzn}
\caption{\label{lst:inc-sim-ann}A predicate implementing a simulated annealing search.}
\end{listing}
So far, the algorithms used have been for versions of incomplete search, or we have trusted the \solver{} to know when to stop searching.
However, for the following algorithms the \solver{} will (or should) not be able to determine whether the search is complete.
Instead, we introduce the following function that can be used to signal to the solver that the search is complete.
When using \mzninline{restart_without_objective} the \solver{} is no longer able to decide when the search process is finished.
However, when \gls{rbmo} is used to describe a complete search, we still want to stop the \solver{} when the process is known to be finished.
Therefore, we introduce the following function that can be used to signal to the solver that the search is complete.
\begin{mzn}
function var bool: complete();
@ -277,6 +270,31 @@ Instead, we introduce the following function that can be used to signal to the s
\noindent{}When the \variable{} returned by this function \gls{fixed} to \true{}, then the search is complete.
If any \gls{sol} was found, it is declared an \gls{opt-sol}.
With \mzninline{restart_without_objective}, the \gls{restart} predicate is now responsible for constraining the \gls{objective}.
Note that a simple hill-climbing (for minimization) can still be defined easily in this context as follows.
\begin{mzn}
predicate hill_climbing() =
if status() != START then
_objective < sol(_objective);
elseif status() = UNSAT then
complete();
endif;
\end{mzn}
It takes advantage of the fact that the declared \gls{objective} is available through the built-in \variable{} \mzninline{_objective}.
A more interesting example is a simulated annealing strategy.
When using this strategy, the sequence of \glspl{sol} that the \solver{} finds are not required to steadily improve in quality.
Instead, the \solver{} can produce \glspl{sol} that potentially with a potentially reduced objective value.
Over time, we decrease the amount by which a \gls{sol} might decrease objective value until we are only looking for improvements.
This \gls{meta-optimization} can help the \solver{} to quickly approximate the \gls{opt-sol}.
\Cref{lst:inc-sim-ann} show how this strategy is also easy to express using \gls{rbmo}.
\begin{listing}
\mznfile{assets/listing/inc_sim_ann.mzn}
\caption{\label{lst:inc-sim-ann}A predicate implementing a simulated annealing search.}
\end{listing}
Using the same methods it is also possible to describe optimization strategies with multiple \glspl{objective}.
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 \glspl{objective}.
@ -322,7 +340,6 @@ It should be noted that the \glspl{sol} found by this search will still contain
The \glspl{neighbourhood} 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 \gls{restart} loop.
The \minisearch{} evaluator will then call a \solver{} to produce a \gls{sol}, and evaluate the \gls{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 \gls{rewriting} and solving separate, by embedding the entire \gls{meta-optimization} algorithm into the \gls{slv-mod}.
This section introduces such a \gls{rewriting} approach.