From 91d264597cb2343d91ddc7bf91b1ec83f14df86c Mon Sep 17 00:00:00 2001 From: "Jip J. Dekker" Date: Tue, 27 Jul 2021 12:56:54 +1000 Subject: [PATCH] Remaining questions after Guido's notes --- assets/listing/inc_basic_complete.mzn | 2 +- assets/listing/inc_sim_ann.mzn | 2 + chapters/5_incremental.tex | 113 +++++++++++++++----------- 3 files changed, 68 insertions(+), 49 deletions(-) diff --git a/assets/listing/inc_basic_complete.mzn b/assets/listing/inc_basic_complete.mzn index 4af97be..295f1cb 100644 --- a/assets/listing/inc_basic_complete.mzn +++ b/assets/listing/inc_basic_complete.mzn @@ -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) diff --git a/assets/listing/inc_sim_ann.mzn b/assets/listing/inc_sim_ann.mzn index 0d08a17..d4043ca 100644 --- a/assets/listing/inc_sim_ann.mzn +++ b/assets/listing/inc_sim_ann.mzn @@ -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) diff --git a/chapters/5_incremental.tex b/chapters/5_incremental.tex index 68d9e04..efae9a0 100644 --- a/chapters/5_incremental.tex +++ b/chapters/5_incremental.tex @@ -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.