Reorganise background sections
This commit is contained in:
parent
e662ff1510
commit
5f09ec6d02
@ -221,7 +221,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
\newglossaryentry{operator}{
|
\newglossaryentry{operator}{
|
||||||
name={operators},
|
name={operator},
|
||||||
description={},
|
description={},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,14 +76,16 @@ the model.
|
|||||||
|
|
||||||
\end{example}
|
\end{example}
|
||||||
|
|
||||||
In the remainder of this chapter we will first, in \cref{sec:back-minizinc}
|
In the remainder of this chapter we will first, in \cref{sec:back-minizinc} we
|
||||||
introduce \minizinc\ as the leading \cml\ used within this thesis.
|
introduce \minizinc\ as the leading \cml\ used within this thesis. In
|
||||||
\cref{sec:back-mzn-interpreter} explains the process that the current \minizinc\
|
\cref{sec:back-solving} we discuss how \gls{cp} can be used to solve a
|
||||||
interpreter uses to translate a \minizinc\ model into a solver-level constraint
|
constraint model. We also briefly discuss other solving techniques and the
|
||||||
model. Then, \cref{sec:back-other-languages} introduces alternative \cmls\ and
|
problem format these techniques expect. \Cref{sec:back-other-languages}
|
||||||
compares their functionality to \minizinc{}. Finally, \cref{sec:back-term} and
|
introduces alternative \cmls\ and compares their functionality to \minizinc{}.
|
||||||
\cref{sec:back-clp} survey the closely related fields of \gls{trs} and
|
Then,\cref{sec:back-term} survey the some closely related technologies in the
|
||||||
\gls{clp}.
|
field of \glspl{trs}. Finally, \cref{sec:back-mzn-interpreter} explores the
|
||||||
|
process that the current \minizinc\ interpreter uses to translate a \minizinc\
|
||||||
|
instance into a solver-level constraint model.
|
||||||
|
|
||||||
\section{\glsentrytext{minizinc}}%
|
\section{\glsentrytext{minizinc}}%
|
||||||
\label{sec:back-minizinc}
|
\label{sec:back-minizinc}
|
||||||
@ -109,9 +111,9 @@ library of constraints allow users to easily model complex problems.
|
|||||||
The model starts with the declaration of the \glspl{parameter}.
|
The model starts with the declaration of the \glspl{parameter}.
|
||||||
\Lref{line:back:knap:toys} declares an enumerated type that represents all
|
\Lref{line:back:knap:toys} declares an enumerated type that represents all
|
||||||
possible toys, \(T\) in the mathematical model in the example.
|
possible toys, \(T\) in the mathematical model in the example.
|
||||||
\Lref{line:back:knap:joy,line:back:knap:space} declare arrays mapping from
|
\Lrefrange{line:back:knap:joy}{line:back:knap:space} declare arrays mapping
|
||||||
toys to integer values, these represent the functional mappings \(joy\) and
|
from toys to integer values, these represent the functional mappings \(joy\)
|
||||||
\(space\). Finally, \lref{line:back:knap:left} declares an integer
|
and \(space\). Finally, \lref{line:back:knap:left} declares an integer
|
||||||
\gls{parameter} to represent the car capacity as an equivalent to \(C\).
|
\gls{parameter} to represent the car capacity as an equivalent to \(C\).
|
||||||
|
|
||||||
The model then declares its \glspl{variable}. \Lref{line:back:knap:sel}
|
The model then declares its \glspl{variable}. \Lref{line:back:knap:sel}
|
||||||
@ -330,6 +332,7 @@ The choice between different expressions can often be expressed using a
|
|||||||
\gls{conditional} expression, sometimes better known as an ``if-then-else''
|
\gls{conditional} expression, sometimes better known as an ``if-then-else''
|
||||||
expressions. You could, for example, force that the absolute value of
|
expressions. You could, for example, force that the absolute value of
|
||||||
\mzninline{a} is bigger than \mzninline{b} using the constraint
|
\mzninline{a} is bigger than \mzninline{b} using the constraint
|
||||||
|
|
||||||
\begin{mzn}
|
\begin{mzn}
|
||||||
constraint if b >= 0 then a > b else b < a endif;
|
constraint if b >= 0 then a > b else b < a endif;
|
||||||
\end{mzn}
|
\end{mzn}
|
||||||
@ -489,17 +492,29 @@ Some expressions in the \cmls\ do not always have a well-defined result.
|
|||||||
Examples of such expressions in \minizinc\ are:
|
Examples of such expressions in \minizinc\ are:
|
||||||
|
|
||||||
\begin{itemize}
|
\begin{itemize}
|
||||||
\item Division (or modulus) when the divisor is zero: \\ \mzninline{x div 0 =
|
\item Division (or modulus) when the divisor is zero:
|
||||||
@??@}
|
|
||||||
|
|
||||||
\item Array access when the index is outside the given index set: \\
|
\begin{mzn}
|
||||||
\mzninline{array1d(1..3, [1,2,3])[0] = @??@}
|
x div 0 = @??@
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
\item Finding the minimum or maximum or an empty set: \\ \mzninline{min({})
|
\item Array access when the index is outside the given index set:
|
||||||
=@??@}
|
|
||||||
|
|
||||||
\item Computing the square root of a negative value: \\ \mzninline{sqrt(-1) =
|
\begin{mzn}
|
||||||
@??@}
|
array1d(1..3, [1,2,3])[0] = @??@
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
\item Finding the minimum or maximum or an empty set:
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
min({}) = @??@
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
\item Computing the square root of a negative value:
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
sqrt(-1) = @??@
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
\end{itemize}
|
\end{itemize}
|
||||||
|
|
||||||
@ -589,331 +604,6 @@ input types and the basic method of solving the given problem.
|
|||||||
\subsection{Hybrid Technologies}%
|
\subsection{Hybrid Technologies}%
|
||||||
\label{subsec:back-hybrid}
|
\label{subsec:back-hybrid}
|
||||||
|
|
||||||
|
|
||||||
\section{Compiling \glsentrytext{minizinc}}%
|
|
||||||
\label{sec:back-mzn-interpreter}
|
|
||||||
|
|
||||||
\jip{This section is the only one here that is not really literature review.
|
|
||||||
Maybe this should just be a separate chapter. It is ``new'' in the sense that
|
|
||||||
is the first real description of some parts of the compiler, but it is
|
|
||||||
relatively short.}
|
|
||||||
|
|
||||||
Traditionally the compilation process is split into three sequential parts: the
|
|
||||||
\emph{frontend}, the \emph{middle-end}, and the \emph{backend}. It is the job of
|
|
||||||
the frontend to parse the user input, report on any errors or inconsistencies in
|
|
||||||
the input, and transform it into an internal representation. The middle-end
|
|
||||||
performs the main translation in a target-independent fashion. It converts the
|
|
||||||
internal representation at the level of the compiler frontend to another
|
|
||||||
internal representation as close to the level required by the compilation
|
|
||||||
targets. The final transformation to the format required by the compilation
|
|
||||||
target are performed by the backend. When a compiler is separated into these few
|
|
||||||
steps, then adding support for new language or compilation target only require
|
|
||||||
the addition of a frontend or backend respectively.
|
|
||||||
|
|
||||||
The \minizinc\ compilation process categorised in the same three categories, as
|
|
||||||
shown in \cref{fig:back-mzn-comp}. In the frontend, a \minizinc\ model is first
|
|
||||||
parsed together with its data into an \gls{ast}. The process will then analyse
|
|
||||||
the \gls{ast} to discover the types of all expressions used in the instance. If
|
|
||||||
an inconsistency is discovered, then an error is reported to the user. Finally,
|
|
||||||
the frontend will also preprocess the \gls{ast}. This process is used to rewrite
|
|
||||||
expressions into a common form for the middle-end, \eg\ remove the ``syntactic''
|
|
||||||
sugar. For instance, replacing the usage of enumerated types by normal integers.
|
|
||||||
|
|
||||||
\begin{figure}
|
|
||||||
\centering
|
|
||||||
\includegraphics[width=\linewidth]{assets/img/back_compilation_structure}
|
|
||||||
\caption{\label{fig:back-mzn-comp} The compilation structure of the \minizinc\
|
|
||||||
compiler.}
|
|
||||||
\end{figure}
|
|
||||||
|
|
||||||
The middle-end contains the most important two processes: the flattening and the
|
|
||||||
optimisation. During the flattening process the high-level (\minizinc{})
|
|
||||||
constraint model is rewritten into a solver level (\flatzinc{}) constraint
|
|
||||||
model. It could be noted that the flattening step depends on the compilation
|
|
||||||
target to define its solver level constraints. Even though the information
|
|
||||||
required for this step is target dependent, we consider it part of the
|
|
||||||
middle-end as the mechanism is the same for all compilation targets. A full
|
|
||||||
description of this process will follow in \cref{subsec:back-flattening}. Once a
|
|
||||||
solver level constraint model is constructed, the \minizinc\ compiler will try
|
|
||||||
to optimise this model: shrink domains of variables, remove constraints that are
|
|
||||||
proven to hold, and remove variables that have become unused. These optimisation
|
|
||||||
techniques are discussed in \cref{subsec:back-fzn-optimisation}.
|
|
||||||
|
|
||||||
The backend will convert the internal solver level constraint model into a
|
|
||||||
format that can be used by the targeted \gls{solver}. Given the formatted
|
|
||||||
artefact, a solver process, controlled by the backend, can then be started.
|
|
||||||
Whenever the solver process produces a solution, the backend will reconstruct
|
|
||||||
the solution to the specification of the original \minizinc{} model.
|
|
||||||
|
|
||||||
In this section we will discuss the flattening and optimisation process as
|
|
||||||
employed by the 2.5.5 version of \minizinc{} \autocite{minizinc-2021-minizinc}.
|
|
||||||
|
|
||||||
\subsection{Flattening}%
|
|
||||||
\label{subsec:back-flattening}
|
|
||||||
|
|
||||||
The goal of the flattening process is to arrive at a ``flat'' constraint model:
|
|
||||||
it only contains constraints that consist of a singular call instruction, all
|
|
||||||
arguments to calls are \gls{parameter} literals or \gls{variable} identifiers,
|
|
||||||
and the call itself is a constraint primitive for the target \gls{solver}.
|
|
||||||
|
|
||||||
To arrive at a flat model, the flattening process will transverse the
|
|
||||||
declarations, \glspl{constraint}, and the solver goal and flatten any expression
|
|
||||||
contained in these items. The flattening of an expression is a recursive
|
|
||||||
process. \Gls{parameter} literals and \gls{variable} identifiers are already
|
|
||||||
flat. For any other kind of expression, its arguments are first flattened. If
|
|
||||||
the expression itself is a constraint primitive, then it is ready
|
|
||||||
|
|
||||||
\jip{This should say something about introducing relational reified constraints.}
|
|
||||||
|
|
||||||
\paragraph{Common Sub-expression Elimination}
|
|
||||||
|
|
||||||
Because the evaluation of a \minizinc\ expression cannot have any side-effects,
|
|
||||||
In some cases, it is even possible to not generate definitions in the first
|
|
||||||
place through the use of \gls{cse}. This simplification is a well understood
|
|
||||||
technique that originates from compiler optimisation \autocite{cocke-1970-cse}
|
|
||||||
and has proven to be very effective in discrete optimisation
|
|
||||||
\autocite{marinov-2005-sat-optimisations, araya-2008-cse-numcsp}, including
|
|
||||||
during the evaluation of \cmls\ \autocite{rendl-2009-enhanced-tailoring}.
|
|
||||||
|
|
||||||
For instance, in the constraint\\
|
|
||||||
\begin{mzn}
|
|
||||||
constraint (abs(x)*2 >= 20) \/ (abs(x)+5 >= 15);
|
|
||||||
\end{mzn}
|
|
||||||
|
|
||||||
the expression \mzninline{abs(x)} is occurs twice. There is however no need to
|
|
||||||
create two separate \glspl{variable} (and defining \glspl{constraint}) to
|
|
||||||
represent the absolute value of \mzninline{x}. The same \gls{variable} can be
|
|
||||||
used to represent the \mzninline{abs(x)} in both sides of the disjunction.
|
|
||||||
|
|
||||||
Seeing that the same expression occurs multiple times is not always easy. Some
|
|
||||||
expressions only become syntactically equal during evaluation, as in the
|
|
||||||
following example.
|
|
||||||
|
|
||||||
\begin{example}
|
|
||||||
Consider the fragment:
|
|
||||||
|
|
||||||
\begin{mzn}
|
|
||||||
function var float: pythagoras(var float: a, var float: b) =
|
|
||||||
let {
|
|
||||||
var float: x = pow(a, 2);
|
|
||||||
var float: y = pow(b, 2);
|
|
||||||
} in sqrt(x + y);
|
|
||||||
constraint pythagoras(i, i) >= 5;
|
|
||||||
\end{mzn}
|
|
||||||
|
|
||||||
Although the expressions \mzninline{pow(a, 2)} and \mzninline{pow(b, 2)} are
|
|
||||||
not syntactically equal, the function call \mzninline{pythagoras(i,i)} using
|
|
||||||
the same variable for \mzninline{a} and \mzninline{b} makes them equivalent.
|
|
||||||
\end{example}
|
|
||||||
|
|
||||||
A straightforward approach to ensure that the same instantiation of a function
|
|
||||||
To ensure that syntactically equal expressions are only evaluated once the
|
|
||||||
\minizinc\ compiler through the use of memorisation. After the flattening of an
|
|
||||||
expression, the instantiated expression and its result are stored in a lookup
|
|
||||||
table, the \gls{cse} table. Then before any consequent expression is flattened
|
|
||||||
the \gls{cse} table is consulted. If an equivalent expression is found, then the
|
|
||||||
accompanying result is used; otherwise, the evaluation proceeds as normal.
|
|
||||||
|
|
||||||
In our example, the evaluation of \mzninline{pythagoras(i, i)} would proceed as
|
|
||||||
normal to evaluate \mzninline{x = pow(i, 2)}. However, the expression defining
|
|
||||||
\mzninline{y}, \mzninline{pow(i, 2)}, will be found in the \gls{cse} table and
|
|
||||||
replaced by the earlier stored result: \mzninline{y = x}.
|
|
||||||
|
|
||||||
\gls{cse} also has an important interaction with the occurence of reified
|
|
||||||
constraints. \Glspl{reification} of a \gls{constraint} are often defined in the
|
|
||||||
library in terms of complicated decompositions into simpler constraints, or
|
|
||||||
require specialised algorithms in the target solvers. In either case, it can be
|
|
||||||
very beneficial for the efficiency solving process if we can detect that a
|
|
||||||
reified constraint is in fact not required.
|
|
||||||
|
|
||||||
If a constraint is present in the root context, it means that it must hold
|
|
||||||
globally. If the same constraint is used in a reified context, it can therefore
|
|
||||||
be replaced with the constant \mzninline{true}, avoiding the need for
|
|
||||||
reification (or in fact any evaluation).
|
|
||||||
|
|
||||||
We can harness \gls{cse} to store the evaluation context when a constraint is
|
|
||||||
added, and detect when the same constraint is used in both contexts. Whenever a
|
|
||||||
lookup in the \gls{cse} table is successful, action can be taken depending on
|
|
||||||
both the current and stored evaluation context. If the stored expression was in
|
|
||||||
root context, then the constant \mzninline{true} can be used, independent of the
|
|
||||||
current context. Otherwise, if the stored expression was in reified context and
|
|
||||||
the current context is reified, then the stored result variable can be used.
|
|
||||||
Finally, if the stored expression was in reified context and the current context
|
|
||||||
is root context, then the previous result can be replaced by the constant
|
|
||||||
\mzninline{true} and the evaluation will proceed as normal with the root context
|
|
||||||
constraint.
|
|
||||||
|
|
||||||
\begin{example}
|
|
||||||
Consider the fragment:
|
|
||||||
|
|
||||||
\begin{mzn}
|
|
||||||
function var bool: p(var int: x, var int: y) = q(x) /\ r(y);
|
|
||||||
constraint b0 <-> q(x);
|
|
||||||
constraint b1 <-> t(x,y);
|
|
||||||
constraint b1 <-> p(x,y);
|
|
||||||
\end{mzn}
|
|
||||||
|
|
||||||
If we process the top-level constraints in order we create a reified call to
|
|
||||||
\mzninline{q(x)} for the original call. Suppose processing the second
|
|
||||||
constraint we discover \mzninline{t(x,y)} is \mzninline{true}, fixing
|
|
||||||
\mzninline{b1}. When we then process \mzninline{q(x)} in instantiation of the
|
|
||||||
call \mzninline{p(x,y)}, we find it is the root context. So \gls{cse} needs to
|
|
||||||
set \mzninline{b0} to \mzninline{true}.
|
|
||||||
\end{example}
|
|
||||||
|
|
||||||
\paragraph{Adjusting domains}
|
|
||||||
|
|
||||||
As discussed in \cref{subsec:back-cp}, the \glspl{domain} of \glspl{variable}
|
|
||||||
can sometimes be directly changed because of the addition of a \gls{constraint}.
|
|
||||||
Similarly, depending on the \glspl{domain} of \glspl{variable} some constraints
|
|
||||||
can be proven \mzninline{true} or \mzninline{false}.
|
|
||||||
|
|
||||||
This principle also applies during the flattening of a \minizinc\ model. It is
|
|
||||||
generally a good idea to detect cases where we can directly change the
|
|
||||||
\gls{domain} of a \gls{variable}. Sometimes this might mean that the constraint
|
|
||||||
does not need to be added at all and that constricting the domain is enough.
|
|
||||||
Tight domains can also allow us to avoid the creation of reified constraints
|
|
||||||
when the truth-value of a reified constraints can be determined from the
|
|
||||||
\glspl{domain} of variables.
|
|
||||||
|
|
||||||
\begin{example}%
|
|
||||||
\label{ex:back-adj-dom}
|
|
||||||
Consider the following \minizinc\ model:
|
|
||||||
|
|
||||||
\begin{mzn}
|
|
||||||
var 1..10: a;
|
|
||||||
var 1..5: b;
|
|
||||||
|
|
||||||
constraint a < b;
|
|
||||||
constraint (a > 5) -> (a + b > 12);
|
|
||||||
\end{mzn}
|
|
||||||
|
|
||||||
Given the \glspl{domain} specified in the model, the second constraint is
|
|
||||||
flattened using to reified \glspl{constraint} for each side of the
|
|
||||||
implication.
|
|
||||||
|
|
||||||
If we however consider the first \gls{constraint}, then we deduce that
|
|
||||||
\mzninline{a} must always take a value that is 4 or lower. When the compiler
|
|
||||||
adjust the domain of \mzninline{a} while flattening the first
|
|
||||||
\gls{constraint}, then the second \gls{constraint} can be simplified. The
|
|
||||||
expression \mzninline{a > 5} is known to be \mzninline{false}, which means
|
|
||||||
that the constraint can be simplified to \mzninline{true}.
|
|
||||||
\end{example}
|
|
||||||
|
|
||||||
During flattening, the \minizinc\ compiler will actively remove values from the
|
|
||||||
\gls{domain} when it encounters constraints that trivially reduces it. For
|
|
||||||
example, constraints with a single comparison expression between a
|
|
||||||
\gls{variable} and a \gls{parameter} (\eg\ \mzninline{x != 5}), constraint with
|
|
||||||
a single comparison between two \glspl{variable} (\eg\ \mzninline{x >= y}),
|
|
||||||
constraints that directly change the domain (\eg\ \mzninline{x in 3..5}). It,
|
|
||||||
however, will not perform more complex \gls{propagation}, like the
|
|
||||||
\gls{propagation} of \glspl{global}.
|
|
||||||
|
|
||||||
\paragraph{Constraint Aggregation}
|
|
||||||
|
|
||||||
Complex \minizinc\ expression can sometimes result in the creation of many new
|
|
||||||
variables that represent intermediate results. This is in particular true for
|
|
||||||
linear and boolean equations that are generally written using \minizinc\
|
|
||||||
operators. For example the evaluation of the linear constraint \mzninline{x +
|
|
||||||
2*y <= z} could result in the following \flatzinc:
|
|
||||||
|
|
||||||
\begin{nzn}
|
|
||||||
var int: x;
|
|
||||||
var int: y;
|
|
||||||
var int: z;
|
|
||||||
var int: i1;
|
|
||||||
var int: i2;
|
|
||||||
constraint int_times(y, 2, i1);
|
|
||||||
constraint int_plus(x, i1, i2);
|
|
||||||
constraint int_le(i2, z);
|
|
||||||
\end{nzn}
|
|
||||||
|
|
||||||
This \flatzinc\ model is correct, but, at least for pure \gls{cp} solvers, the
|
|
||||||
existence of the intermediate variables is likely to have a negative impact on
|
|
||||||
the \gls{solver}'s performance. These \glspl{solver} would likely perform better
|
|
||||||
had they received the equivalent linear constraint
|
|
||||||
|
|
||||||
\begin{mzn}
|
|
||||||
constraint int_lin_le([1,2,-1], [x,y,z], 0)
|
|
||||||
\end{mzn}
|
|
||||||
|
|
||||||
directly. Since many solvers support linear constraints, it is often an
|
|
||||||
additional burden to have intermediate values that have to be given a value in
|
|
||||||
the solution.
|
|
||||||
|
|
||||||
This can be resolved using the \gls{aggregation} of constraints. When we
|
|
||||||
aggregate constraints we collect multiple \minizinc\ expressions, that would
|
|
||||||
each have been separately translated, and combine them into a singular structure
|
|
||||||
that eliminates the need for intermediate \glspl{variable}. For example, the
|
|
||||||
arithmetic definitions can be combined into linear constraints, Boolean logic
|
|
||||||
can be combined into clauses, and counting constraints can be combined into
|
|
||||||
global cardinality constraints.
|
|
||||||
|
|
||||||
The \minizinc\ compiler aggregates expressions whenever possible. When the
|
|
||||||
\minizinc\ compiler reaches an expression that could potentially be part of an
|
|
||||||
aggregated constraint, the compiler will not flatten the expression. The
|
|
||||||
compiler will instead perform a search of its sub-expression to collect all other
|
|
||||||
expressions to form an aggregated constraint. The flattening process continues
|
|
||||||
by flattening this aggregated constraint, which might still contain unflattened
|
|
||||||
arguments.
|
|
||||||
|
|
||||||
\paragraph{Delayed Rewriting}
|
|
||||||
|
|
||||||
Adjusting the \glspl{domain} of variables during flattening means that the
|
|
||||||
system becomes non-confluent, and some orders of execution may produce
|
|
||||||
``better'', \ie\ more compact or more efficient, \flatzinc{}.
|
|
||||||
|
|
||||||
\begin{example}
|
|
||||||
The following example is similar to code found in the \minizinc\ libraries of
|
|
||||||
\gls{mip} solvers.
|
|
||||||
|
|
||||||
\begin{mzn}
|
|
||||||
function var int: lq_zero_if_b(var int: x, var bool: b) =
|
|
||||||
x <= ub(x)*(1-b);
|
|
||||||
\end{mzn}
|
|
||||||
|
|
||||||
This predicate expresses the constraint \mzninline{b -> x<=0}, using a
|
|
||||||
well-known method called ``big-M transformation''. The expression
|
|
||||||
\mzninline{ub(x)} returns a valid upper bound for \mzninline{x}, \ie\ a fixed
|
|
||||||
value known to be greater than or equal to \mzninline{x}. This could be the
|
|
||||||
initial upper bound \mzninline{x} was declared with, or the current value in
|
|
||||||
the corresponding \nanozinc\ \mzninline{mkvar} call. If \mzninline{b} takes
|
|
||||||
the value 0, the expression \mzninline{ub(x)*(1-b)} is equal to
|
|
||||||
\mzninline{ub(x)}, and the constraint \mzninline{x <= ub(x)} holds trivially.
|
|
||||||
If \mzninline{b} takes the value 1, \mzninline{ub(x)*(1-b)} is equal to 0,
|
|
||||||
enforcing the constraint \mzninline{x <= 0}.
|
|
||||||
\end{example}
|
|
||||||
|
|
||||||
For \gls{mip} solvers, it is quite important to enforce tight bounds in order to
|
|
||||||
improve efficiency and sometimes even numerical stability. It would therefore be
|
|
||||||
useful to rewrite the \mzninline{lq_zero_if_b} predicate only after the
|
|
||||||
\glspl{domain} of the involved variables has been reduced as much as possible,
|
|
||||||
in order to take advantage of the tightest possible bounds. On the other hand,
|
|
||||||
evaluating a predicate may also \emph{impose} new bounds on variables, so it is
|
|
||||||
not always clear which order of evaluation is best.
|
|
||||||
|
|
||||||
The same problem occurs with \glspl{reification} that are produced during
|
|
||||||
flattening. Other constraints could fix the domain of the reified \gls{variable}
|
|
||||||
and make the \gls{reification} unnecessary. Instead the constraint (or its
|
|
||||||
negation) can be flattened in root context. This could avoid the use of a big
|
|
||||||
decomposition or an expensive propagator.
|
|
||||||
|
|
||||||
To tackle this problem, the \minizinc\ compiler employs \gls{del-rew}. When a
|
|
||||||
linear \gls{constraint} is aggregated or a relational \gls{reification}
|
|
||||||
\gls{constraint} is introduced it is not directly flattened. Instead these
|
|
||||||
constraints are appended to the end of the current \gls{ast}. All other
|
|
||||||
constraints currently still unflattened, that could change the relevant
|
|
||||||
\glspl{domain}, will be flattened first.
|
|
||||||
|
|
||||||
Note that this heuristic does not guarantee that \glspl{variable} have their
|
|
||||||
tightest possible \gls{domain}. One delayed \gls{constraint} can still influence
|
|
||||||
the \glspl{domain} of \glspl{variable} used by other delayed \glspl{constraint}.
|
|
||||||
|
|
||||||
\subsection{Optimisation}%
|
|
||||||
\label{subsec:back-fzn-optimisation}
|
|
||||||
|
|
||||||
The optimisation process of the \minizinc\ compiler
|
|
||||||
|
|
||||||
\section{Other Constraint Modelling Languages}%
|
\section{Other Constraint Modelling Languages}%
|
||||||
\label{sec:back-other-languages}
|
\label{sec:back-other-languages}
|
||||||
|
|
||||||
@ -1251,3 +941,327 @@ constraint store to its state before the last decision was made).
|
|||||||
\subsection{ACD Term Rewriting}%
|
\subsection{ACD Term Rewriting}%
|
||||||
\label{subsec:back-acd}
|
\label{subsec:back-acd}
|
||||||
|
|
||||||
|
|
||||||
|
\section{Compiling \glsentrytext{minizinc}}%
|
||||||
|
\label{sec:back-mzn-interpreter}
|
||||||
|
|
||||||
|
\jip{This section is the only one here that is not really literature review.
|
||||||
|
Maybe this should just be a separate chapter. It is ``new'' in the sense that
|
||||||
|
is the first real description of some parts of the compiler, but it is
|
||||||
|
relatively short.}
|
||||||
|
|
||||||
|
Traditionally the compilation process is split into three sequential parts: the
|
||||||
|
\emph{frontend}, the \emph{middle-end}, and the \emph{backend}. It is the job of
|
||||||
|
the frontend to parse the user input, report on any errors or inconsistencies in
|
||||||
|
the input, and transform it into an internal representation. The middle-end
|
||||||
|
performs the main translation in a target-independent fashion. It converts the
|
||||||
|
internal representation at the level of the compiler frontend to another
|
||||||
|
internal representation as close to the level required by the compilation
|
||||||
|
targets. The final transformation to the format required by the compilation
|
||||||
|
target are performed by the backend. When a compiler is separated into these few
|
||||||
|
steps, then adding support for new language or compilation target only require
|
||||||
|
the addition of a frontend or backend respectively.
|
||||||
|
|
||||||
|
The \minizinc\ compilation process categorised in the same three categories, as
|
||||||
|
shown in \cref{fig:back-mzn-comp}. In the frontend, a \minizinc\ model is first
|
||||||
|
parsed together with its data into an \gls{ast}. The process will then analyse
|
||||||
|
the \gls{ast} to discover the types of all expressions used in the instance. If
|
||||||
|
an inconsistency is discovered, then an error is reported to the user. Finally,
|
||||||
|
the frontend will also preprocess the \gls{ast}. This process is used to rewrite
|
||||||
|
expressions into a common form for the middle-end, \eg\ remove the ``syntactic''
|
||||||
|
sugar. For instance, replacing the usage of enumerated types by normal integers.
|
||||||
|
|
||||||
|
\begin{figure}
|
||||||
|
\centering
|
||||||
|
\includegraphics[width=\linewidth]{assets/img/back_compilation_structure}
|
||||||
|
\caption{\label{fig:back-mzn-comp} The compilation structure of the \minizinc\
|
||||||
|
compiler.}
|
||||||
|
\end{figure}
|
||||||
|
|
||||||
|
The middle-end contains the most important two processes: the flattening and the
|
||||||
|
optimisation. During the flattening process the high-level (\minizinc{})
|
||||||
|
constraint model is rewritten into a solver level (\flatzinc{}) constraint
|
||||||
|
model. It could be noted that the flattening step depends on the compilation
|
||||||
|
target to define its solver level constraints. Even though the information
|
||||||
|
required for this step is target dependent, we consider it part of the
|
||||||
|
middle-end as the mechanism is the same for all compilation targets. A full
|
||||||
|
description of this process will follow in \cref{subsec:back-flattening}. Once a
|
||||||
|
solver level constraint model is constructed, the \minizinc\ compiler will try
|
||||||
|
to optimise this model: shrink domains of variables, remove constraints that are
|
||||||
|
proven to hold, and remove variables that have become unused. These optimisation
|
||||||
|
techniques are discussed in \cref{subsec:back-fzn-optimisation}.
|
||||||
|
|
||||||
|
The backend will convert the internal solver level constraint model into a
|
||||||
|
format that can be used by the targeted \gls{solver}. Given the formatted
|
||||||
|
artefact, a solver process, controlled by the backend, can then be started.
|
||||||
|
Whenever the solver process produces a solution, the backend will reconstruct
|
||||||
|
the solution to the specification of the original \minizinc{} model.
|
||||||
|
|
||||||
|
In this section we will discuss the flattening and optimisation process as
|
||||||
|
employed by the 2.5.5 version of \minizinc{} \autocite{minizinc-2021-minizinc}.
|
||||||
|
|
||||||
|
\subsection{Flattening}%
|
||||||
|
\label{subsec:back-flattening}
|
||||||
|
|
||||||
|
The goal of the flattening process is to arrive at a ``flat'' constraint model:
|
||||||
|
it only contains constraints that consist of a singular call instruction, all
|
||||||
|
arguments to calls are \gls{parameter} literals or \gls{variable} identifiers,
|
||||||
|
and the call itself is a constraint primitive for the target \gls{solver}.
|
||||||
|
|
||||||
|
To arrive at a flat model, the flattening process will transverse the
|
||||||
|
declarations, \glspl{constraint}, and the solver goal and flatten any expression
|
||||||
|
contained in these items. The flattening of an expression is a recursive
|
||||||
|
process. \Gls{parameter} literals and \gls{variable} identifiers are already
|
||||||
|
flat. For any other kind of expression, its arguments are first flattened. If
|
||||||
|
the expression itself is a constraint primitive, then it is ready
|
||||||
|
|
||||||
|
\jip{This should say something about introducing relational reified constraints.}
|
||||||
|
|
||||||
|
\paragraph{Common Sub-expression Elimination}
|
||||||
|
|
||||||
|
Because the evaluation of a \minizinc\ expression cannot have any side-effects,
|
||||||
|
In some cases, it is even possible to not generate definitions in the first
|
||||||
|
place through the use of \gls{cse}. This simplification is a well understood
|
||||||
|
technique that originates from compiler optimisation \autocite{cocke-1970-cse}
|
||||||
|
and has proven to be very effective in discrete optimisation
|
||||||
|
\autocite{marinov-2005-sat-optimisations, araya-2008-cse-numcsp}, including
|
||||||
|
during the evaluation of \cmls\ \autocite{rendl-2009-enhanced-tailoring}.
|
||||||
|
|
||||||
|
For instance, in the constraint\\
|
||||||
|
\begin{mzn}
|
||||||
|
constraint (abs(x)*2 >= 20) \/ (abs(x)+5 >= 15);
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
the expression \mzninline{abs(x)} is occurs twice. There is however no need to
|
||||||
|
create two separate \glspl{variable} (and defining \glspl{constraint}) to
|
||||||
|
represent the absolute value of \mzninline{x}. The same \gls{variable} can be
|
||||||
|
used to represent the \mzninline{abs(x)} in both sides of the disjunction.
|
||||||
|
|
||||||
|
Seeing that the same expression occurs multiple times is not always easy. Some
|
||||||
|
expressions only become syntactically equal during evaluation, as in the
|
||||||
|
following example.
|
||||||
|
|
||||||
|
\begin{example}
|
||||||
|
Consider the fragment:
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
function var float: pythagoras(var float: a, var float: b) =
|
||||||
|
let {
|
||||||
|
var float: x = pow(a, 2);
|
||||||
|
var float: y = pow(b, 2);
|
||||||
|
} in sqrt(x + y);
|
||||||
|
constraint pythagoras(i, i) >= 5;
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
Although the expressions \mzninline{pow(a, 2)} and \mzninline{pow(b, 2)} are
|
||||||
|
not syntactically equal, the function call \mzninline{pythagoras(i,i)} using
|
||||||
|
the same variable for \mzninline{a} and \mzninline{b} makes them equivalent.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
A straightforward approach to ensure that the same instantiation of a function
|
||||||
|
To ensure that syntactically equal expressions are only evaluated once the
|
||||||
|
\minizinc\ compiler through the use of memorisation. After the flattening of an
|
||||||
|
expression, the instantiated expression and its result are stored in a lookup
|
||||||
|
table, the \gls{cse} table. Then before any consequent expression is flattened
|
||||||
|
the \gls{cse} table is consulted. If an equivalent expression is found, then the
|
||||||
|
accompanying result is used; otherwise, the evaluation proceeds as normal.
|
||||||
|
|
||||||
|
In our example, the evaluation of \mzninline{pythagoras(i, i)} would proceed as
|
||||||
|
normal to evaluate \mzninline{x = pow(i, 2)}. However, the expression defining
|
||||||
|
\mzninline{y}, \mzninline{pow(i, 2)}, will be found in the \gls{cse} table and
|
||||||
|
replaced by the earlier stored result: \mzninline{y = x}.
|
||||||
|
|
||||||
|
\gls{cse} also has an important interaction with the occurence of reified
|
||||||
|
constraints. \Glspl{reification} of a \gls{constraint} are often defined in the
|
||||||
|
library in terms of complicated decompositions into simpler constraints, or
|
||||||
|
require specialised algorithms in the target solvers. In either case, it can be
|
||||||
|
very beneficial for the efficiency solving process if we can detect that a
|
||||||
|
reified constraint is in fact not required.
|
||||||
|
|
||||||
|
If a constraint is present in the root context, it means that it must hold
|
||||||
|
globally. If the same constraint is used in a reified context, it can therefore
|
||||||
|
be replaced with the constant \mzninline{true}, avoiding the need for
|
||||||
|
reification (or in fact any evaluation).
|
||||||
|
|
||||||
|
We can harness \gls{cse} to store the evaluation context when a constraint is
|
||||||
|
added, and detect when the same constraint is used in both contexts. Whenever a
|
||||||
|
lookup in the \gls{cse} table is successful, action can be taken depending on
|
||||||
|
both the current and stored evaluation context. If the stored expression was in
|
||||||
|
root context, then the constant \mzninline{true} can be used, independent of the
|
||||||
|
current context. Otherwise, if the stored expression was in reified context and
|
||||||
|
the current context is reified, then the stored result variable can be used.
|
||||||
|
Finally, if the stored expression was in reified context and the current context
|
||||||
|
is root context, then the previous result can be replaced by the constant
|
||||||
|
\mzninline{true} and the evaluation will proceed as normal with the root context
|
||||||
|
constraint.
|
||||||
|
|
||||||
|
\begin{example}
|
||||||
|
Consider the fragment:
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
function var bool: p(var int: x, var int: y) = q(x) /\ r(y);
|
||||||
|
constraint b0 <-> q(x);
|
||||||
|
constraint b1 <-> t(x,y);
|
||||||
|
constraint b1 <-> p(x,y);
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
If we process the top-level constraints in order we create a reified call to
|
||||||
|
\mzninline{q(x)} for the original call. Suppose processing the second
|
||||||
|
constraint we discover \mzninline{t(x,y)} is \mzninline{true}, fixing
|
||||||
|
\mzninline{b1}. When we then process \mzninline{q(x)} in instantiation of the
|
||||||
|
call \mzninline{p(x,y)}, we find it is the root context. So \gls{cse} needs to
|
||||||
|
set \mzninline{b0} to \mzninline{true}.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
\paragraph{Adjusting domains}
|
||||||
|
|
||||||
|
As discussed in \cref{subsec:back-cp}, the \glspl{domain} of \glspl{variable}
|
||||||
|
can sometimes be directly changed because of the addition of a \gls{constraint}.
|
||||||
|
Similarly, depending on the \glspl{domain} of \glspl{variable} some constraints
|
||||||
|
can be proven \mzninline{true} or \mzninline{false}.
|
||||||
|
|
||||||
|
This principle also applies during the flattening of a \minizinc\ model. It is
|
||||||
|
generally a good idea to detect cases where we can directly change the
|
||||||
|
\gls{domain} of a \gls{variable}. Sometimes this might mean that the constraint
|
||||||
|
does not need to be added at all and that constricting the domain is enough.
|
||||||
|
Tight domains can also allow us to avoid the creation of reified constraints
|
||||||
|
when the truth-value of a reified constraints can be determined from the
|
||||||
|
\glspl{domain} of variables.
|
||||||
|
|
||||||
|
\begin{example}%
|
||||||
|
\label{ex:back-adj-dom}
|
||||||
|
Consider the following \minizinc\ model:
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
var 1..10: a;
|
||||||
|
var 1..5: b;
|
||||||
|
|
||||||
|
constraint a < b;
|
||||||
|
constraint (a > 5) -> (a + b > 12);
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
Given the \glspl{domain} specified in the model, the second constraint is
|
||||||
|
flattened using to reified \glspl{constraint} for each side of the
|
||||||
|
implication.
|
||||||
|
|
||||||
|
If we however consider the first \gls{constraint}, then we deduce that
|
||||||
|
\mzninline{a} must always take a value that is 4 or lower. When the compiler
|
||||||
|
adjust the domain of \mzninline{a} while flattening the first
|
||||||
|
\gls{constraint}, then the second \gls{constraint} can be simplified. The
|
||||||
|
expression \mzninline{a > 5} is known to be \mzninline{false}, which means
|
||||||
|
that the constraint can be simplified to \mzninline{true}.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
During flattening, the \minizinc\ compiler will actively remove values from the
|
||||||
|
\gls{domain} when it encounters constraints that trivially reduces it. For
|
||||||
|
example, constraints with a single comparison expression between a
|
||||||
|
\gls{variable} and a \gls{parameter} (\eg\ \mzninline{x != 5}), constraint with
|
||||||
|
a single comparison between two \glspl{variable} (\eg\ \mzninline{x >= y}),
|
||||||
|
constraints that directly change the domain (\eg\ \mzninline{x in 3..5}). It,
|
||||||
|
however, will not perform more complex \gls{propagation}, like the
|
||||||
|
\gls{propagation} of \glspl{global}.
|
||||||
|
|
||||||
|
\paragraph{Constraint Aggregation}
|
||||||
|
|
||||||
|
Complex \minizinc\ expression can sometimes result in the creation of many new
|
||||||
|
variables that represent intermediate results. This is in particular true for
|
||||||
|
linear and boolean equations that are generally written using \minizinc\
|
||||||
|
operators. For example the evaluation of the linear constraint \mzninline{x +
|
||||||
|
2*y <= z} could result in the following \flatzinc:
|
||||||
|
|
||||||
|
\begin{nzn}
|
||||||
|
var int: x;
|
||||||
|
var int: y;
|
||||||
|
var int: z;
|
||||||
|
var int: i1;
|
||||||
|
var int: i2;
|
||||||
|
constraint int_times(y, 2, i1);
|
||||||
|
constraint int_plus(x, i1, i2);
|
||||||
|
constraint int_le(i2, z);
|
||||||
|
\end{nzn}
|
||||||
|
|
||||||
|
This \flatzinc\ model is correct, but, at least for pure \gls{cp} solvers, the
|
||||||
|
existence of the intermediate variables is likely to have a negative impact on
|
||||||
|
the \gls{solver}'s performance. These \glspl{solver} would likely perform better
|
||||||
|
had they received the equivalent linear constraint
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
constraint int_lin_le([1,2,-1], [x,y,z], 0)
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
directly. Since many solvers support linear constraints, it is often an
|
||||||
|
additional burden to have intermediate values that have to be given a value in
|
||||||
|
the solution.
|
||||||
|
|
||||||
|
This can be resolved using the \gls{aggregation} of constraints. When we
|
||||||
|
aggregate constraints we collect multiple \minizinc\ expressions, that would
|
||||||
|
each have been separately translated, and combine them into a singular structure
|
||||||
|
that eliminates the need for intermediate \glspl{variable}. For example, the
|
||||||
|
arithmetic definitions can be combined into linear constraints, Boolean logic
|
||||||
|
can be combined into clauses, and counting constraints can be combined into
|
||||||
|
global cardinality constraints.
|
||||||
|
|
||||||
|
The \minizinc\ compiler aggregates expressions whenever possible. When the
|
||||||
|
\minizinc\ compiler reaches an expression that could potentially be part of an
|
||||||
|
aggregated constraint, the compiler will not flatten the expression. The
|
||||||
|
compiler will instead perform a search of its sub-expression to collect all other
|
||||||
|
expressions to form an aggregated constraint. The flattening process continues
|
||||||
|
by flattening this aggregated constraint, which might still contain unflattened
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
\paragraph{Delayed Rewriting}
|
||||||
|
|
||||||
|
Adjusting the \glspl{domain} of variables during flattening means that the
|
||||||
|
system becomes non-confluent, and some orders of execution may produce
|
||||||
|
``better'', \ie\ more compact or more efficient, \flatzinc{}.
|
||||||
|
|
||||||
|
\begin{example}
|
||||||
|
The following example is similar to code found in the \minizinc\ libraries of
|
||||||
|
\gls{mip} solvers.
|
||||||
|
|
||||||
|
\begin{mzn}
|
||||||
|
function var int: lq_zero_if_b(var int: x, var bool: b) =
|
||||||
|
x <= ub(x)*(1-b);
|
||||||
|
\end{mzn}
|
||||||
|
|
||||||
|
This predicate expresses the constraint \mzninline{b -> x<=0}, using a
|
||||||
|
well-known method called ``big-M transformation''. The expression
|
||||||
|
\mzninline{ub(x)} returns a valid upper bound for \mzninline{x}, \ie\ a fixed
|
||||||
|
value known to be greater than or equal to \mzninline{x}. This could be the
|
||||||
|
initial upper bound \mzninline{x} was declared with, or the current value in
|
||||||
|
the corresponding \nanozinc\ \mzninline{mkvar} call. If \mzninline{b} takes
|
||||||
|
the value 0, the expression \mzninline{ub(x)*(1-b)} is equal to
|
||||||
|
\mzninline{ub(x)}, and the constraint \mzninline{x <= ub(x)} holds trivially.
|
||||||
|
If \mzninline{b} takes the value 1, \mzninline{ub(x)*(1-b)} is equal to 0,
|
||||||
|
enforcing the constraint \mzninline{x <= 0}.
|
||||||
|
\end{example}
|
||||||
|
|
||||||
|
For \gls{mip} solvers, it is quite important to enforce tight bounds in order to
|
||||||
|
improve efficiency and sometimes even numerical stability. It would therefore be
|
||||||
|
useful to rewrite the \mzninline{lq_zero_if_b} predicate only after the
|
||||||
|
\glspl{domain} of the involved variables has been reduced as much as possible,
|
||||||
|
in order to take advantage of the tightest possible bounds. On the other hand,
|
||||||
|
evaluating a predicate may also \emph{impose} new bounds on variables, so it is
|
||||||
|
not always clear which order of evaluation is best.
|
||||||
|
|
||||||
|
The same problem occurs with \glspl{reification} that are produced during
|
||||||
|
flattening. Other constraints could fix the domain of the reified \gls{variable}
|
||||||
|
and make the \gls{reification} unnecessary. Instead the constraint (or its
|
||||||
|
negation) can be flattened in root context. This could avoid the use of a big
|
||||||
|
decomposition or an expensive propagator.
|
||||||
|
|
||||||
|
To tackle this problem, the \minizinc\ compiler employs \gls{del-rew}. When a
|
||||||
|
linear \gls{constraint} is aggregated or a relational \gls{reification}
|
||||||
|
\gls{constraint} is introduced it is not directly flattened. Instead these
|
||||||
|
constraints are appended to the end of the current \gls{ast}. All other
|
||||||
|
constraints currently still unflattened, that could change the relevant
|
||||||
|
\glspl{domain}, will be flattened first.
|
||||||
|
|
||||||
|
Note that this heuristic does not guarantee that \glspl{variable} have their
|
||||||
|
tightest possible \gls{domain}. One delayed \gls{constraint} can still influence
|
||||||
|
the \glspl{domain} of \glspl{variable} used by other delayed \glspl{constraint}.
|
||||||
|
|
||||||
|
\subsection{Optimisation}%
|
||||||
|
\label{subsec:back-fzn-optimisation}
|
||||||
|
|
||||||
|
The optimisation process of the \minizinc\ compiler
|
||||||
|
Reference in New Issue
Block a user