This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.
dekker-phd-thesis/chapters/3_rewriting.tex

892 lines
59 KiB
TeX

%************************************************
\chapter{Rewriting Constraint Modelling Languages}\label{ch:rewriting}
%************************************************
\input{chapters/3_rewriting_preamble}
\section{Architecture Overview}
\label{sec:rew-arch}
\noindent{}\Gls{rewriting} a \minizinc{} instance into a \gls{slv-mod} may often seem like a simple \gls{trs}.
In reality, however, \minizinc{} is quite a complex language, and producing a good \gls{slv-mod} is far from trivial.
Thus, in practice, this rewriting process is a highly complex task that needs to carefully track interactions between \constraints{} and how they affect \variables{}.
To create an efficient \gls{slv-mod} the \gls{rewriting} process uses many simplification techniques such as:
\begin{itemize}
\item continuously updating \variable{} \domains{},
\item \gls{propagation} for known \constraints{},
\item specialized \glspl{decomp} when variables get \gls{fixed},
\item \gls{aggregation},
\item common sub-expression elimination,
\item and removing \variables{} and \constraints{} that are not required any longer.
\end{itemize}
We now present a new architecture for the \gls{rewriting} process that has been designed for the modern day needs of \minizinc{}.
At the core of our \gls{rewriting} process lie formalized rewriting rules.
As such, this architecture represents an abstract machine model for \minizinc{}.
These rules allow us to reason about the system and about the simplifications both to the process and the resulting \gls{slv-mod}.
The process can even be made incremental: in \cref{ch:incremental} we discuss how when making incremental changes to the \minizinc{} \instance{}, unchanged parts of the \instance{} do not need to be rewritten again.
\begin{figure}
\centering
\includegraphics[width=\linewidth]{assets/img/rew_compilation_structure}
\caption{\label{fig:rew-comp} The proposed process for the compilation of \minizinc{} instances.}
\end{figure}
The process of the new architecture is shown in \cref{fig:rew-comp}.
After parsing and type checking, a \minizinc{} model is first compiled into a smaller constraint language called \microzinc{}, independent of the data.
For efficient execution, the \microzinc{} is then transformed into a \gls{byte-code}.
Together, the \gls{byte-code} and a complete \gls{parameter-assignment} form an \instance{} that can be executed by the \microzinc{} \interpreter{}.
During its execution, the \interpreter{} continuously updates a \cmodel{} according to the \microzinc{} definition.
The \cmodel{} in the \interpreter{} is internally represented as a \nanozinc{} program.
\nanozinc{} is a slight extension of the \flatzinc{} format.
The \interpreter{} finishes when it reaches a \gls{slv-mod}.
We have developed a prototype of this toolchain, and present experimental validation that the new tool chain can perform \gls{rewriting} much faster, and produce better models, than the current \minizinc{} \compiler{}.
\section{\glsentrytext{microzinc} and \glsentrytext{nanozinc}}\label{sec:rew-micronano}
This section introduces the two sub-languages of \minizinc{} at the core of the new architecture we have developed.
\microzinc{} is a simple language used to define transformations to be performed by the \interpreter{}.
It is a simplified subset of \minizinc{}.
The transformation are represented in the language through the use of function definitions.
A function of type \mzninline{var bool}, describing a relation, can be defined as a \gls{native} \constraint{}.
Otherwise, a function body must be provided for \gls{rewriting}.
The function body can use a restriction of the \minizinc{} expression language.
An important difference between \minizinc{} and \microzinc{} is that a well-formed \microzinc{} does not contain partial expressions.
Any partiality in the \minizinc{} model must be explicitly expressed using total functions in \microzinc{}.
As such, \constraints{} introduced in \microzinc{} \glspl{let} can be globally enforced.
They are guaranteed to only constrain the \variables{} introduced in the same \gls{let}.
\nanozinc{} is used to represent the current state of the \instance{}.
\nanozinc{} is similar to \flatzinc{} in that it contains only declarations of \variables{} and a flat list of \constraints{}.
However, while all function calls in \flatzinc{} need to be \gls{native}, \nanozinc{} calls can refer to any function implemented in \microzinc{}.
Furthermore, \nanozinc{} allows for \constraints{} to be ``attached'' to a \variable{}, in order to be able to track their lifetime and remove them if the corresponding \variable{} becomes unused.
As a small syntactic difference with \flatzinc{}, \constraints{} and \variable{} declarations in \nanozinc{} can be freely interleaved.
The core of the syntax of \microzinc{} is defined in \cref{fig:rew-uzn-syntax}.
Function declarations without an expression body are used to mark functions that are \gls{native} to the target \solver{}.
We have omitted the definitions of \syntax{<mzn-type-inst>} and \syntax{<ident>}, which can be assumed to be the same as the definition of \syntax{<type-inst>} and \syntax{<ident>} in the \minizinc{} grammar, see \cref{ch:minizinc-grammar}.
While we omit the typing rules here for space reasons, we will assume that in well-typed \microzinc{} programs conditions of expressions are \parameters{}.
This means that the where conditions \syntax{<exp>} in \glspl{comprehension}, the conditions \syntax{<exp>} in \gls{conditional} expressions, and the \syntax{<literal>} index in \gls{array} accesses must have \mzninline{par} type.
In \minizinc{} the use of \variables{} in these positions is allowed, and these expressions are rewritten to function calls.
We will discuss how the same transformation takes place when translating \minizinc{} to \microzinc{}.
\begin{figure}
\begin{grammar}
<model> ::= <pred-decl>*<fun-decl>*
<fun-decl> ::= "function" <type-inst>":" <ident>"(" <typing> ["," <typing>]* ")" "=" <exp>";"
\alt "function" "var" "bool" ":" <ident>"(" <typing> ["," <typing>]* ")"";"
<literal> ::= <constant> | <ident>
<exp> ::= <literal>
\alt <ident>"(" <exp> ["," <exp>]* ")"
\alt "let" "{" <item>* "}" "in" <literal>
\alt "if" <exp> "then" <exp> "else" <exp> "endif"
\alt "[" <exp> "|" <gen-exp> "where" <exp> "]"
\alt <ident>"[" <literal> "]"
<item> ::= <typing> [ "=" <exp> ]";"
\alt "tuple(" <type-inst> ["," <type-inst>]* "):" <ident> = "(" <exp> ["," <exp>]* ")"";"
\alt "(" <typing> ["," <typing>]* ")" "=" <exp>";"
\alt "constraint" <exp>";"
<typing> ::= <type-inst>":" <ident>
<gen-exp> ::= <ident> "in" <exp> ["," <ident> "in" <exp> ]*
\end{grammar}
\caption{\label{fig:rew-uzn-syntax}Syntax of \microzinc{}.}
\end{figure}
A \nanozinc{} program, defined in \cref{fig:rew-nzn-syntax}, is simply a list of \variable{} declarations and \constraints{} in the form of calls.
The syntax ``\texttt{}'' will be used to ``attach'' \constraints{} to \variables{} in order to track dependent \constraints{}.
This will be explained in detail in \cref{sec:rew-nanozinc}.
\begin{figure}
\begin{grammar}
<nzn> ::= <nzn-item>*
<nzn-item> ::= <nzn-def> | <nzn-con>
<nzn-con> ::= "constraint" <ident>"(" <literal> ["," <literal>]* ")"";"
<nzn-var> ::= "var" <nzn-dom> ":" <ident> ";" <nzn-attach>*
<nzn-dom> ::= <constant> ".." <constant> | <constant>
\alt "bool" | "int" | "float" | "set of int"
<nzn-attach> ::= "↳" <nzn-con>
\end{grammar}
\caption{\label{fig:rew-nzn-syntax}Syntax of \nanozinc{}.}
\end{figure}
\subsection{\glsentrytext{minizinc} to \glsentrytext{microzinc}}
The translation from \minizinc{} to \microzinc{} involves the following steps.
\begin{enumerate}
\item Transform all expressions that are valid in \minizinc{} but not in \microzinc{}.
This includes simple cases such as replacing \gls{operator} expressions, like \mzninline{x+y}, by function calls, \mzninline{int_plus(x,y)}, and replacing generators in calls, like \mzninline{sum(x in A)(x)}, by \glspl{comprehension}, \mzninline{sum([x|x in A])}.
As mentioned above, \gls{conditional} expressions where the decision is made by a \variable{} are also not directly supported in \microzinc{}.
These expressions are therefore also replaced by function calls that are possibly decomposed.
An expression \mzninline{if x then A else B endif}, where \mzninline{x} is a \variable{}, is replaced the call \mzninline{if_then_else([x, true], [A, B])}.
A recent study by \textcite{stuckey-2019-conditionals} describes how \mzninline{if_then_else} can be decomposed for different types of \solvers{}.
An expression \mzninline{A[x]}, where \mzninline{x} is a \variable{}, is replaced by the call \mzninline{element(A, x)}.
\mzninline{element} is a well-known \gls{global}.
For many \gls{cp} \solvers{} it is a \gls{native} \constraint{} and for other \solvers{} there have long been accepted \glspl{decomp}.
\item Replace optional \variables{} into non-optional forms.
As shown by \textcite{mears-2014-option}, optional type \variables{} can be represented using a \variable{} of the same non-optional type and a Boolean \variable{}.
The Boolean \variable{}, then, represents if the variable exists or is absent, while the non-optional \variable{} of the same type represents the value of the optional \variable{} if it exists.
In \microzinc{} this can be represented as a tuple of the two \variables{}.
The function definitions for optional types then take both \variables{} into account.
For example, the definition of \mzninline{int_plus(x, y)} for optional types can be translated into the function shown in \cref{lst:rew-opt-plus}.
\begin{listing}
\mznfile{assets/listing/rew_opt_plus.mzn}
\caption{\label{lst:rew-opt-plus} A definition of \mzninline{int_plus} for optional integers in \microzinc{} syntax.}
\end{listing}
\item Lift the partiality in expressions into the surrounding context to implement \minizinc{}'s relational semantics.
In contrast to \minizinc{}, a \microzinc{} expression must be total.
\textcite{stuckey-2013-functions} have extensively examined these problems and describe how to transform any partial function into a total function while maintaining the relational semantics of the original \cmodel{}.
A general approach for describing the partiality in \microzinc{} functions is to make the function return an additional Boolean \variable{}.
This \variable{} indicates whether the result of the function call is defined w.r.t. the function arguments.
The resulting value of the function is then adjusted to return a predefined value when it would normally be undefined.
For example, the \mzninline{element} function in \minizinc{} is declared as follows.
\begin{mzn}
function var int: element(array of int: a, var int: x);
\end{mzn}
\noindent{}It is only defined when \mzninline{x in index_set(a)}.
The function shown in \cref{lst:rew-elem-safe} is a total version of this function that can be used in \microzinc{}\footnote{For brevity's sake, we will still use \minizinc{} \glspl{operator} to write the \microzinc{} definition.}.
\begin{listing}
\mznfile{assets/listing/rew_elem_safe.mzn}
\caption{\label{lst:rew-elem-safe} A total of the \mzninline{element} function in \microzinc{}.}
\end{listing}
Similar to the replacement of optional values, all occurrences of the \mzninline{element} function are replaced by \mzninline{element_safe}.
The usage of its result should be guarded by the returned totality variable in the surrounding Boolean context.
This means that, for example, the expression \mzninline{element(e, v) = 5 \/ b} would be replaced by the following expression.
\begin{mzn}
let {
(var int: res, var bool: total) = element_safe(e, v);
} in (total /\ res = 5) \/ b;
\end{mzn}
\item Resolve subtype based overloading statically.
To prevent the runtime overhead of the dynamic lookup of overloaded function definitions for every call, the function dispatch in \microzinc{} is determined statically.
It is important, however, that the correct version of a function is chosen when it allows for either \variable{} and \parameter{} arguments.
As we will later discuss in more detail, is possible for a \variable{} to be \gls{fixed} to a single value during the \gls{rewriting} process.
At that point it can be treated as a \parameter{}.
To ensure the correct version of the function is used, functions are changed to, at runtime, check if a \variable{} is \gls{fixed} and, if so, dispatch to the \parameter{} version of the function.
If we, for example, have a \minizinc{} function \mzninline{f} that is overloaded on \mzninline{var int} and \mzninline{int}, then the \microzinc{} transformation can be described by the following two functions.
\begin{mzn}
function f_int(int: x) = /* original body */;
function f_varint(var int: x) =
if is_fixed(x) then
f_int(fix(x))
else
/* original body */;
endif;
\end{mzn}
\item Transform all function definitions so that they do not refer to \variables{} and \parameters{} defined outside the scope of the function.
Instead, they are added as extra arguments to the function definition and each call.
\item Move all top-level \variable{} declarations and \constraints{} into a let expression in the body of a new function called \mzninline{main}.
The arguments to this function are the top-level \parameters{} of the \minizinc{} model, and it returns a fresh Boolean \variable{}.
\end{enumerate}
\begin{example}
Consider the \minizinc{} model in \cref{lst:rew-pigeon-mzn}.
It describes a simple \gls{unsat} ``pigeonhole problem'': trying to assign \(n\) pigeons to \(n-1\) holes.
The translation to \microzinc{} turns all expressions into function calls, replaces the special generator syntax in the \mzninline{forall} with an \gls{array} \gls{comprehension}, and creates the \mzninline{main} function.
The result of this translation is shown in \cref{lst:rew-pigeon-uzn}.
\pagebreak{}
For an \instance{} of the \cmodel{}, we can then create a simple \nanozinc{} program that calls \mzninline{main}.
For example for \(n=10\), we would create the following \nanozinc{} program.
\begin{nzn}
constraint main(10);
\end{nzn}
\codeendsexample{}
\end{example}
\begin{listing}
\mznfile{assets/listing/rew_pigeon_mzn.mzn}
\caption{\label{lst:rew-pigeon-mzn} A \minizinc{} model of the pigeonhole problem.}
\end{listing}
\begin{listing}
\mznfile{assets/listing/rew_pigeon_uzn.mzn}
\caption{\label{lst:rew-pigeon-uzn} The \microzinc{} translation of the pigeonhole problem in \cref{lst:rew-pigeon-mzn}.}
\end{listing}
\subsection{Partial Evaluation of \glsentrytext{nanozinc}}\label{sub:rew-partial}
Each call in a \nanozinc{} program is either a call to a \gls{native} \constraint{}, or it has a corresponding \microzinc{} function definition.
The goal of partial evaluation is to rewrite the \nanozinc{} program into a \gls{slv-mod}, i.e., a program where all calls are calls to \gls{native} \constraints{}.
To achieve this, we define the semantics of \microzinc{} as a \gls{rewriting} system that produces \nanozinc{} calls.
Each non-\gls{native} call in a \nanozinc{} program can then be replaced with the result of evaluating the corresponding \microzinc{} function.
A key element of this \gls{rewriting} process, then, is the transformation of functional expressions into relational form.
For this to happen, the \gls{rewriting} process introduces fresh \variables{} to represent intermediate expressions.
\begin{example}\label{ex:rew-abs}
Consider the (reasonably typical) \minizinc{} encoding for the absolute value function presented in \cref{lst:rew-abs}.
When an occurrence of \mzninline{abs} is encountered, the call will be replaced by a \variable{} \mzninline{z}.
But before that can occur, the \variable{} must be introduced, and the \constraint{} \mzninline{int_abs(x, z)} is added to the model, enforcing its value.
\end{example}
\begin{listing}
\mznfile{assets/listing/rew_abs.mzn}
\caption{\label{lst:rew-abs} A \minizinc{} definition for the absolute value function.}
\end{listing}
The function \mzninline{abs(x)} above is a total function: for every value of \mzninline{x}, there is always exactly one valid value of \mzninline{z}.
In this case, the \constraint{} introduced in the \gls{let} may be enforced globally, and multiple occurrences of \mzninline{abs(x)} may be replaced with the same \mzninline{z}.
However, the introduced \mzninline{int_abs} \constraint{} is only needed so long as any other \constraint{} refers to \mzninline{z}.
If \mzninline{z} is unused in the rest of the model, both \mzninline{z} and the \mzninline{int_abs} \constraint{} can be removed.
As we shall see in \cref{sec:rew-simplification}, certain optimizations and simplifications during the evaluation can lead to many expressions becoming unused.
It is therefore important to track which \constraints{} were merely introduced to define results of function calls.
In order to track these dependencies, \nanozinc{} attaches \constraints{} that define a variable to the item that introduces the \variable{}.
\subsection{\glsentrytext{nanozinc}}\label{sec:rew-nanozinc}
A \nanozinc{} program (\cref{fig:rew-nzn-syntax}) consists of a topologically-ordered list of declarations of \variables{} and \constraints{}.
\Variables{} are declared with an identifier, a domain, and are associated with a list of attached \constraints{}.
\Constraints{} can also occur at the top-level of the \nanozinc{} program.
These are said to be the \rootc{}-context \constraints{} of the \cmodel{}, i.e., those that have to hold globally and are not just used to define an \gls{avar}.
Only root-context \constraints{} can effectively constrain the overall problem.
In \microzinc{} these \constraints{} must originate from \constraint{} items in the generated \mzninline{main} function.
\Constraints{} attached to \variables{} originate from function calls, and since all functions are guaranteed to be total, attached \constraints{} can only define the function result.
\begin{example}\label{ex:rew-absnzn}
Consider the following \minizinc{} fragment.
\begin{mzn}
constraint abs(x) > y;
\end{mzn}
\noindent{}If we assume that \mzninline{x} and \mzninline{y} have a \domain{} of \mzninline{-10..10}, then after \gls{rewriting} it would result in the following resulting \nanozinc{} program.
\begin{nzn}
var -10..10: x;
var -10..10: y;
var int: z;
↳ constraint int_abs(x, z);
constraint int_gt(z, y);
\end{nzn}
Evaluating the call \mzninline{abs(x)} introduces a new \variable{} \mzninline{z} (with unrestricted domain) and the \mzninline{int_abs} \constraint{} that is used to define \mzninline{z}.
The top-level \constraint{} \mzninline{abs(x) > y} is rewritten into \mzninline{int_gt(z, y)} and added as a top-level \constraint{}.
\end{example}
\subsection{Rewriting Rules}
The core of the proposed \minizinc{} architecture is an abstract machine that rewrites \nanozinc{} items using the corresponding \microzinc{} function definitions.
We can now formally define the rewriting rules for this abstract machine.
The full set of rules appears in \cref{fig:rew-rewrite-to-nzn-calls,fig:rew-rewrite-to-nzn-let,fig:rew-rewrite-to-nzn-other}.
To simplify presentation, we assume that all rules that introduce new identifiers into the \nanozinc{} program do so in a way that ensures those identifiers are fresh (i.e., not used in the rest of the \nanozinc{} program), by suitable alpha renaming.
We also present our rules using a \emph{call by name} evaluation strategy, i.e., the arguments to function calls are substituted into function body.
As such, they are only evaluated when required for the result of the function.
Although pure \microzinc{} behaves the same under any call evaluation strategy, in implementation a \emph{call by value} strategy can provide more predictable behaviour with debugging functions in the \minizinc{} language.
It would likely be unexpected that functions such as \mzninline{trace} and \mzninline{assert} are only evaluated if and when their values are required (call by name), instead of when the function call is encountered.
\begin{figure*}[p]
\centering
\begin{prooftree}
\hypo{\texttt{function} ~t\texttt{:}~\ptinline{F(\(p_1, \ldots{}, p_k\)) = E;} \in{} \Prog{} \text{~where the~} p_i \text{~are fresh}}
\infer[no rule]1{\Sem{\(\ptinline{E}_{[p_i \mapsto a_i, \forall{} 1 \leq{} i \leq{} k]}\)}{\Prog, \Env} \Rightarrow{} \tuple{v, \Env'}}
\infer1[(Call)]{\Sem{F(\(a_1, \ldots, a_k\))}{\Prog, \Env} \Rightarrow{} \tuple{v, \Env'}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\ptinline{F} \in \text{Builtins}}
\infer1[(CallBuiltin)]{\Sem{F(\(a_1, \ldots, a_k\))}{\Prog, \Env} \Rightarrow{} \tuple{\ensuremath{\mathit{eval}(\ptinline{F}(a_1,\ldots, a_k))}, \Env}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\texttt{function var bool:}~\ptinline{F(\(p_1, \ldots, p_k\));} \in \Prog}
\infer1[(CallNative)]{\Sem{F(\(a_1, \ldots, a_k\))}{\Prog, \Env} \Rightarrow{}
\tuple{\ensuremath{\texttt{constraint}~\ptinline{F}(a_1, \ldots, a_k), \Env}}}
\end{prooftree}
\caption{\label{fig:rew-rewrite-to-nzn-calls} Rewriting rules for partial evaluation of \microzinc{} calls to \nanozinc{}.}
\end{figure*}
The rules in \cref{fig:rew-rewrite-to-nzn-calls} handle function calls.
The first rule, (Call), evaluates a function call expression in the context of a \microzinc{} program \(\Prog{}\) and \nanozinc{} program \(\Env{}\).
The rule evaluates the function body \(\ptinline{E}\) where the formal parameters \(p_{i}\) are substituted by the call arguments \(a_{i}\).\footnote{We use \(E_{[a \mapsto b]}\) as a notation for the expression \(E\) where \(a\) is substituted by \(b\).} The result of this evaluation is the result of the function call.
The (CallBuiltin) rule applies to ``built-in'' functions that can be evaluated directly, such as arithmetic and Boolean operations on \gls{fixed} values.
The rule simply returns the result of evaluating the built-in function on the evaluated parameter values.
The (CallNative) rule applies to calls to \mzninline{var bool} functions, that describe relations, that have been marked as \constraints{} \gls{native} to the \solver{}.
Since these are directly supported by the \solver{}, they simply need to be transferred into the \nanozinc{} program.
\begin{figure*}[p]
\centering
\begin{prooftree}
\hypo{\Sem{\(\mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env, \emptyset} \Rightarrow \tuple{x, \Env'}}
\infer1[(Let)]{\Sem{let \{ \(\mathbf{I}\) \} in \(\mathbf{X}\)}{\Prog, \Env} \Rightarrow{} \tuple{x, \Env'}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(\mathbf{X}\)}{\Prog, \{ t : x \} \cup{} \Env} \Rightarrow \tuple{x, \{ t: x \} \cup{} \Env'}}
\infer1[(Item0)]{\Sem{\(\epsilon{} \mid{} \mathbf{X}\)}{\Prog, \{ t: x \}\cup{}\Env, \Ctx} \Rightarrow{} \tuple{x, \{ t: x \texttt{~↳~} \Ctx{} \} \cup{} \Env'}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(C\)}{\Prog, \Env} \Rightarrow \tuple{c, \Env'}}
\hypo{\Sem{\(\mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env', \{c\}\cup\Ctx} \Rightarrow \tuple{x, \Env''}}
\infer2[(ItemC)]{\Sem{\(\mbox{constraint}~C\texttt{;} \mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env, \Ctx} \Rightarrow{} \tuple{x, \Env''}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(\mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env{} \cup{} \{t : x\}, \Ctx} \Rightarrow \tuple{x, \Env'}}
\infer1[(ItemT)]{\Sem{\(t\texttt{:}~x\texttt{;} \mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env, \Ctx} \Rightarrow{} \tuple{x, \Env'}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(E\)}{\Prog, \Env} \Rightarrow \tuple{v, \Env'}}
\hypo{\Sem{\(\mathbf{I}_{[x \mapsto v]} \mid{} \mathbf{X}_{[x \mapsto v]}\)}{\Prog, \Env', \Ctx} \Rightarrow{} \tuple{x, \Env''}}
\infer2[(ItemTE)]{\Sem{\(t\texttt{:}~x = E\texttt{;} \mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env, \Ctx} \Rightarrow{} \tuple{x, \Env''}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(E_{i}\)}{\Prog,\Env^{i-1}} \Rightarrow \tuple{v_{i}, \Env^{i}}, \forall{} 1 \leq{} i \leq{} k}
\infer[no rule]1{\Sem{\(\mathbf{I}_{[x_{\langle{}i\rangle} \mapsto v_{i}, \forall{} 1 \leq{} i \leq{} k]} \mid{} \mathbf{X}_{[x_{\langle{}i\rangle} \mapsto v_{i}, \forall{} 1 \leq{} i \leq{} k]}\)}{\Prog, \Env^{k}, \Ctx} \Rightarrow{} \tuple{x, \Env'}}
\infer1[(ItemTupC)]{\Sem{\( \texttt{tuple(}t_{1}, \ldots, t_{k}\texttt{):}~x = \left(E_{1}, \ldots, E_{k}\right)\)}{\Prog, \Env^{0}, \Ctx} \Rightarrow{} \tuple{x, \Env'}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(E\)}{\Prog, \Env} \Rightarrow \tuple{\left(v_{\langle{}1\rangle}, \ldots, v_{\langle{}k\rangle}\right), \Env'}}
\infer[no rule]1{\Sem{\(\mathbf{I}_{[x_{i} \mapsto v_{\langle{}i\rangle}, \forall{} 1 \leq{} i \leq{} k]} \mid{} \mathbf{X}_{[x_{i} \mapsto v_{\langle{}i\rangle}, \forall{} 1 \leq{} i \leq{} k]}\)}{\Prog, \Env', \Ctx} \Rightarrow{} \tuple{x, \Env''}}
\infer1[(ItemTupD)]{\Sem{\(\left(t_{1}\texttt{:}~x_{1}, \ldots, t_{k}\texttt{:}~x_{k}\right) = E\texttt{;} \mathbf{I} \mid{} \mathbf{X}\)}{\Prog, \Env, \Ctx} \Rightarrow{} \tuple{x, \Env''}}
\end{prooftree}
\caption{\label{fig:rew-rewrite-to-nzn-let} Rewriting rules for partial evaluation of \microzinc{} \glspl{let} to \nanozinc{}.}
\end{figure*}
The rules in \cref{fig:rew-rewrite-to-nzn-let} consider \glspl{let}.
Starting from the main rule, (Let), the let items \textbf{I} are evaluated one at a time.
\Glspl{constraint} that follow from the evaluation of the let expression, by the (ItemC) rule, are collected in the third component, \(\phi{}\), of the evaluation arguments.
When all inner items have been evaluated, the captured \constraints{} are attached to the literal returned by the body of the let expression \textbf{X} in (Item0).
The (ItemT) rule handles introduction of new \variables{} by adding them to the context.
The (ItemTE) rule handles introduction of new \variables{} with a defining equation by evaluating them in the current context and substituting the name of the new \variable{} by the result of evaluation in the entire scope of the variable.
The rules (ItemTupC) and (ItemTupD) are for the construction and deconstruction of tuple objects.
We use the notation \(x_{\langle{}i\rangle}\) to mark the \(i^{\text{th}}\) member of the tuple \(x\) in our substitution notation.
\begin{figure*}
\centering
\begin{prooftree}
\hypo{x \in \langle \text{ident} \rangle}
\hypo{\{t: x \texttt{~↳~} \phi{} \} \in \Env}
\infer2[(Ident)]{\Sem{\(x\)}{\Prog, \Env} \Rightarrow{} \tuple{x, \Env}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{c \text{~constant}}
\infer1[(Const)]{\Sem{\(c\)}{\Prog, \Env} \Rightarrow{} \tuple{c, \Env}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(x\)}{\Prog, \Env} \Rightarrow \tuple{[x_{n}, \ldots, x_{m}], \Env'}}
\hypo{\Sem{\(i\)}{\Prog, \Env'} \Rightarrow \tuple{v, \Env''}}
\hypo{v \in [n \ldots{} m]}
\infer3[(Access)]{\Sem{\(x\texttt{[}i\texttt{]}\)}{\Prog, \Env} \Rightarrow{} \tuple{x_{v}, \Env''}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(C\)}{\Prog, \Env} \Rightarrow \tuple{\ptinline{true}, \_}}
\infer1[(If\(_T\))]{\Sem{if \(C\) then \(E_t\) else \(E_e\) endif}{\Prog, \Env} \Rightarrow{} \Sem{\(E_t\)}{\Prog, \Env}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(C\)}{\Prog, \Env} \Rightarrow \tuple{\ptinline{false}, \_}}
\infer1[(If\(_F\))]{\Sem{if \(C\) then \(E_t\) else \(E_e\) endif}{\Prog, \Env} \Rightarrow{} \Sem{\(E_e\)}{\Prog, \Env}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{G}{\Prog,\Env} \Rightarrow \tuple{ \ptinline{true}, \_}}
\hypo{\Sem{\(E\)}{\Prog, \Env} \Rightarrow \tuple {v, \Env'}}
\infer2[(WhereT)]{\Sem{\([E ~|~ \mbox{where~} G]\)}{\Prog, \Env} \Rightarrow{} \tuple{[v], \Env'}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{G}{\Prog,\Env} \Rightarrow \tuple{\ptinline{false}, \_}}
\infer1[(WhereF)]{\Sem{\([E ~|~ \mbox{where~} G]\)}{\Prog, \Env} \Rightarrow{} \tuple{[], \Env}}
\end{prooftree} \\
\bigskip
\begin{prooftree}
\hypo{\Sem{\(\mathit{PE}\)}{\Prog,\Env} \Rightarrow \tuple{S, \_}}
\infer[no rule]1{\Sem{\([E ~|~ \mathit{GE} \mbox{~where~} G]\)}{\Prog, \Env_{[x \mapsto{} v]}} \Rightarrow{} \tuple{A_v, \Env_{[x \mapsto{} v]} \cup{} \Env{}_{v}}, \forall{} v \in{} S }
\infer1[(ListG)]{\Sem{\([E~|~ x \mbox{~in~} \mathit{PE}, \mathit{GE} \mbox{~where~} G]\)}{\Prog, \Env} \Rightarrow{}
\tuple{\mbox{concat} [ A_v~|~v \in{} S ], \Env{} \cup{} \bigcup{}_{v \in{} S} \Env{}_{v}}}
\end{prooftree}
\caption{\label{fig:rew-rewrite-to-nzn-other} Rewriting rules for partial evaluation of other \microzinc{} expressions to \nanozinc{}.}
\end{figure*}
Finally, the rules in \cref{fig:rew-rewrite-to-nzn-other} handle the evaluation of \variables{}, constants, \glspl{conditional}, and \glspl{comprehension}.
The (Ident) rule looks up a \variable{} in the environment and returns the \variable{} itself to be used in any \constraints{}.
The (Const) rule simply returns the constant.
The (Acess) rule evaluates the \gls{array} expression \(x\) and the index \(i\).
It then returns the element from the evaluated \gls{array} chosen by the evaluated index.
Note that, in well-defined \microzinc{}, the index \(i\) must evaluate to a value within the index range of the evaluated \gls{array} \(x\).
The (If\(_T\)) and (If\(_F\)) rules evaluate the if condition and then return the result of the then or else branch appropriately.
The (WhereT) rule evaluates the guard of a where \gls{comprehension} and if \true{} returns the resulting expression in a list.
The (WhereF) rule evaluates the guard and when \false{} returns an empty list.
The (ListG) rule evaluates the iteration set \(PE\) to get its value \(S\), which must be \gls{fixed}.
It then evaluates the expression \(E\) with all the different possible values \(v \in S\) of the generator \(x\), collecting the additional \nanozinc{} definitions \(\Env_v\).
It returns the concatenation of the resulting lists with all the additional \nanozinc{} items.
\subsection{Prototype Implementation}
Our prototype implementation of the \microzinc{}/\nanozinc{} framework consists of the following components.
The \gls{compiler} translates \minizinc{} into a \gls{byte-code} encoding of \microzinc{}.
The compiler currently supports a significant subset of the full \minizinc{} language.
The missing features, such as floating point values and complex output statements, require additional engineering effort, but do not require new technology.
The \gls{interpreter} evaluates \microzinc{} \gls{byte-code} and \nanozinc{} programs based on the \gls{rewriting} system introduced in this section.
It can read \parameters{} from a file or via an \glsxtrshort{api}.
The \gls{interpreter} constructs the call to the \mzninline{main} function, and then performs the \gls{rewriting} into \gls{slv-mod} \nanozinc{}.
Memory management is implemented using reference counting, which lends itself well to the simplifications discussed in the next section.
The interpreter is written in around 3.5kLOC of C++ to enable easy embedding into applications and other programming languages.
The framework has the ability to support multiple \solvers{}.
It can pretty-print the \nanozinc{} code to standard \flatzinc{}, so that any solver currently compatible with \minizinc{} can be used.
Additionally, a direct C++ \glsxtrshort{api} can be used to connect solvers directly, in order to enable incremental solving.
We revisit this topic in \cref{ch:incremental}.
The source code for the complete system has been made available under an open-source licence, and it has been included in the experiment resources in \cref{ch:benchmarks}.
\section{NanoZinc Simplification}\label{sec:rew-simplification}
The previous section introduced the basic evaluation model of \nanozinc: each call that has a corresponding \microzinc{} definition can be rewritten into a set of calls by evaluating the \microzinc{} code.
This section looks at further steps that the system can perform in order to produce better, more compact \glspl{slv-mod}.
\subsection{Removing unused decision variables and constraints}
The most basic form of simplification is to identify \variables{} that are not used by any call, and remove them.
This is correct because the semantics of \nanozinc{} requires us to find values for all \variables{} such that all \constraints{} are \gls{satisfied} --- but if a \variable{} does not occur in any \constraint{}, we can pick any value for it.
The model without the unused \variable{} is therefore \gls{eqsat} with the original model.
Consider now the case where a \variable{} in \nanozinc{} is only used in its attached \constraints{}.
The \constraints{} directly succeeding the declaration prepended by \texttt{}.
\begin{example}\label{ex:rew-absreif}
The following is a slight variation on the \minizinc{} fragment in \cref{ex:rew-absnzn}.
\begin{mzn}
constraint abs(x) > y \/ c;
constraint c;
\end{mzn}
The \constraint{} \mzninline{abs(x) > y} is now used in a disjunction.
This means that instead of enforcing the \constraint{} globally, we need to create a \gls{reif}.
Following the rewriting rules, the generated \nanozinc{} code will look very similar to the code generated in \cref{ex:rew-absnzn}, but with an additional \mzninline{bool_or} \constraint{} for the disjunction.
It uses a \variable{} \mzninline{b} that will be introduced for \mzninline{abs(x) > y}.
The \gls{rewriting} of this fragment will result in the following \nanozinc{} program.
\begin{nzn}
var true: c;
var -10..10: x;
var -10..10: y;
var int: z;
↳ constraint int_abs(x, z);
var bool: b;
↳ constraint int_gt_reif(z, y, b);
constraint bool_or(b, c);
\end{nzn}
Evaluating \mzninline{constraint c} has resulted in the domain of \mzninline{c} to be \gls{fixed} to \true{}.
The disjunction now trivially holds, independent of the value of \mzninline{b}.
The \gls{reif} of \mzninline{abs(x) > y} is therefore not required.
However, the \gls{rewriting} has to choose a particular order in which arguments are evaluated, so it is always possible that it already evaluated the left-hand side before ``noticing'' that the disjunction is \gls{satisfied}.
If the system now simplifies the \constraint{} \mzninline{bool_or(b, c)} to true, then the identifier \mzninline{b} will become unused outside its attached \constraints{}.
It was only referenced from the \mzninline{bool_or} call.
Removing \mzninline{b} leads to its defining \constraint{}, \mzninline{int_gt_reif}, being removed.
This in turn means that \mzninline{z} is not referenced anywhere outside its attached \constraints{}.
It can also be removed together with its definition.
The resulting \nanozinc{} program is much more compact.
\begin{nzn}
var true: c;
var -10..10: x;
var -10..10: y;
\end{nzn}
We assume here that \mzninline{c}, \mzninline{x} and \mzninline{y} are used in other parts of the model not shown here and therefore not removed.
Note how it is crucial to exclude the attached \constraints{} when counting the usage of \variables{}.
\mzninline{int_abs(x, z)}, the \constraint{} which merely defines \mzninline{z}, would otherwise require us to keep \mzninline{z} in the \nanozinc{} program, if it were included in the count.
\end{example}
It is important to notice the difference between \constraints{} that appear at the top level and \constraints{} that appear as part of the attached \constraints{} of a \variable{}.
Top-level \constraints{} help define the problem, they are globally enforced and cannot be removed from the problem unless it can be proved that its relations are already enforced.
\Constraints{} attached to a \variable{}, on the other hand, are used to define a \variable{}.
\Constraints{} are generally attached when they stem from a functional relation, like \mzninline{abs(x)} in the example, or from a \gls{reif}, like \mzninline{abs(x) > y} when used in the disjunction with \mzninline{c}.
In this thesis we will follow the same naming standard as \minizinc{}, where a \gls{reif} of a \constraint{} has the same name as the original \constraint{} with \texttt{\_reif} appended.
\begin{example}
The importance of the use of attached \constraints{} when removing unused \variables{} and \constraints{} is demonstrated in the following \minizinc{} fragment.
\begin{mzn}
int: n;
var int: x;
function var int: f(var int: x, int: i) = let {
var int: y;
constraint y = x * i;
} in y;
constraint [f(x,i) | i in 1..n][n] == 10;
\end{mzn}
The \gls{rewriting} of this \minizinc{} model will initially create the \mzninline{n} elements for the \gls{array} \gls{comprehension}.
Each element is evaluated as a new \variable{} \mzninline{y} and a \constraint{} to ensure that \mzninline{y} is the multiplication of \mzninline{x} and \mzninline{i}.
Although \mzninline{n} variables are created, only the last \variable{} is constrained to take the value ten.
All other \variables{} can thus be removed, and the model will stay \gls{eqsat}.
In the \nanozinc{} version of this model, each of the multiplications \constraints{} will be added to the list of attached \constraints{} of its corresponding \mzninline{y} \variable{}.
When, after evaluating the \gls{array} access, all of those \variables{} except the last are detected to be unused, their removal triggers the removal of the multiplication \constraints{}.
For this example the current \minizinc{} compiler, version 2.5.5, will produce a \gls{slv-mod} that contains:
\begin{itemize}
\item the variable \mzninline{x},
\item \mzninline{n-1} variables \mzninline{y},
\item \mzninline{n-1} multiplication \constraints{}.
\end{itemize}
\noindent{}It creates the definitions for \mzninline{y} despite the fact that the \domain{} of the \variable{} \mzninline{x} has already been \gls{fixed} to 1\@.
The \nanozinc{} system can correctly detect that the \mzninline{y} \variables{} and multiplication \constraints{} are not required.
\end{example}
\paragraph{Implementation} The removal of unused identifiers is taken care of by garbage collection in the interpreter.
Since our prototype interpreter uses reference counting, it already accurately tracks the liveness of each identifier.
\subsection{Constraint Propagation}
\Gls{propagation} is the main inference technique used in \gls{cp} \solvers{}, as discussed in \cref{subsec:back-cp}.
At its core, \gls{propagation} simplifies the internal representation of the \gls{slv-mod}: it removes inconsistent values from \variable{} \domains{}, and it may be able to detect that certain \constraints{} are subsumed, which means that they can be removed safely.
Since a \nanozinc{} program is in fact quite similar to the internal representation of \gls{cp} \solver{}, we can use \gls{propagation} to simplify \nanozinc{}.
When using \gls{propagation} for \nanozinc{} simplification, we have to carefully consider its effects.
For instance, given the \constraint{} \mzninline{x > y}, with initial \domains{} \mzninline{x in 1..10, y in 1..10}, \gls{propagation} would result in the \domains{} being tightened to \mzninline{x in 2..10, y in 1..9}.
Note, however, that this may now prevent us from removing \mzninline{x} or \mzninline{y}: even if they later become unused, the tightened \domains{} may impose a \constraint{} on their \variables{}.
When the \domain{} of a \variable{} is tighter than the \gls{bounds} given by its defining expression, the \domain{} are said to be \gls{binding}.
For instance, if \mzninline{x} is defined as \mzninline{abs(z)}, then any restriction on the \domain{} of \mzninline{x} constrains the possible values of \mzninline{z} through the \mzninline{abs} function.
We therefore need to track whether the \domain{} of a \variable{} is the result of external \constraints{}, or is the consequence of its own definition.
A further effect of \gls{propagation} is the replacement of \constraints{} by simpler versions.
A \constraint{} \mzninline{x=y+z}, where the \domain{} of \mzninline{z} has been tightened to the singleton \mzninline{0..0}, can be rewritten to \mzninline{x=y}.
Propagating the effect of one \constraint{} can thus trigger the propagation of other \constraints{}, and \constraint{} solvers typically run all \glspl{propagator} until they reach a \gls{fixpoint}.
The \nanozinc{} \gls{interpreter} applies \glspl{propagator} for the most important \constraints{}, such as linear and Boolean \constraints{}, which can lead to significant reductions in the size of the generated \gls{slv-mod}.
It has been shown previously that applying full \gls{propagation} during compilation can be beneficial \autocite{leo-2015-multipass}.
This works particularly well when the target solver requires complicated reformulations, such as linearization for \gls{mip} \solvers{} \autocite{belov-2016-linearization}.
Returning to the example above, \gls{propagation} (and other model simplifications) can result in equality \constraints{} \mzninline{x=y} being introduced.
For certain types of target \solvers{}, in particular those based on \gls{cp}, equality \constraints{} can lead to exponentially larger search trees if not detected and simplified.
Equalities therefore present a valuable opportunity for model simplification, but also pose some challenges.
Consider the simple case of \mzninline{x=y} where one of the \variables{}, say \mzninline{x}, was a top-level variable introduced by the modeller, and not defined as the result of a function call.
In that case, we can propagate the equality \constraint{} by replacing all occurrences of \mzninline{x} with \mzninline{y}, and remove the definition of \mzninline{x}.
The more complicated case for \mzninline{x=y} is when both \mzninline{x} and \mzninline{y} are the result of function calls.
For example, a \constraint{} such as \mzninline{constraint f(a)=g(b)}.
Assuming functions \mzninline{f} and \mzninline{g} are directly rewritten into relational forms \mzninline{f_rel} and \mzninline{g_rel}, the corresponding \nanozinc{} code looks as follows.
\begin{nzn}
var int: x;
↳ constraint f_rel(a, x);
var int: y;
↳ constraint g_rel(b, y);
constraint int_eq(x, y);
\end{nzn}
In this case, simply removing either \mzninline{x} or \mzninline{y} would not be correct.
This would trigger the removal of the corresponding definition \mzninline{f_rel(a, x)} or \mzninline{g_rel(b, y)}, leaving nothing to be substituted.
Rather, the \interpreter{} needs to pick one of the two definitions and promote it to top-level status.
Let us assume that the \interpreter{} decides to replace \mzninline{y} by \mzninline{x}.
\mzninline{y} is then globally replaced by \mzninline{x}, and its declaration is removed.
The \interpreter{} moves all attached \constraints{} from \mzninline{y} into the list of top-level \constraints{}, and then removes the \mzninline{int_eq} \constraint{}.
The following \nanozinc{} program would be its result.
\begin{nzn}
var int: x;
↳ constraint f_rel(a, x);
constraint g_rel(b, x);
\end{nzn}
The process of equality propagation is similar to unification in logic programming \autocite{warren-1983-wam}.
However, since equations in \minizinc{} always concern scalar variables rather than terms, we can restrict ourselves to this simple substitution scheme and leave the further checking of the resulting equalities to the target \solver{}.
\paragraph{Implementation}
\Gls{rewriting} a function call that has a \microzinc{} definition and \gls{rewriting} a \constraint{} due to \gls{propagation} are very similar.
The \interpreter{} therefore simply interleaves both forms of \gls{rewriting}.
Efficient \gls{propagation} engines ``wake up'' a \gls{propagator} only when one of its \variables{} has received an update (i.e., when its \domain{} has been shrunk).
To enable this, the \interpreter{} needs to keep a data structure linking \variables{} to the \constraints{} where they appear (in addition to the reverse links from calls to the \variables{} in their arguments).
These links do not take part in the reference counting.
Together with its current \domain{}, each \variable{} in the interpreter also contains a Boolean flag.
This flag signifies whether the \domain{} of the \variable{} is \gls{binding}.
It is set when the \domain{} of a \variable{} is tightened by a \constraint{} that is not attached to the \variable{}.
When the flag is set, the \variable{} cannot be removed, even when the reference count is zero.
This \constraint{} may then become subsumed after \gls{propagation} and can then be removed.
Its meaning is now completely captured by the \domains{} of the \variables{}.
However, if any defining \constraint{} can later determine the same, or a strictly tighter, \domain{}, then it is not \gls{binding} any longer.
It is again fully implied by its defining \constraints{}.
The flag can then be unset and the \variable{} can potentially be removed.
\subsection{Delayed Rewriting}
\Gls{propagation} can tighten \domains{} of \variables{} or even fix them to a single value.
When this happens for the arguments of a call, a more specific \gls{decomp} may become available.
As discussed in \cref{subsec:back-delayed-rew}, it is therefore important to evaluate calls when all possible information is available to create efficient \glspl{slv-mod}.
In our \microzinc{}/\nanozinc{} system, we allow functions to be annotated as potential candidates for \gls{del-rew}.
Any annotated \constraint{} is handled by the (CallNative) rule rather than the (Call) rule, which means that it is simply added as a call into the \nanozinc{} code, without evaluating its body.
When \gls{propagation} reaches a \gls{fixpoint}, all \glspl{annotation} are removed from the current \nanozinc{} program, and evaluation resumes with the function bodies.
This crucial optimization enables rewriting in multiple phases.
For example, a \cmodel{} targeted at a \gls{mip} \solver{} is rewritten into Boolean and reified \constraints{}, whose definitions are annotated to be delayed.
The \nanozinc{} program can then be fully simplified by \gls{propagation}, before the Boolean and reified \constraints{} are rewritten into \gls{native} linear \constraints{} suitable for \gls{mip}.
\begin{example}
Let us revisit the example from \cref{subsec:back-delayed-rew}.
The following \minizinc{} fragment calls a predicate that uses the ``Big M method''.
\begin{mzn}
function var int: lq_zero_if_b(var int: x, var bool: b) ::delayed =
x <= ub(x)*(1-b); % b -> x<=0
var 0..1000: x;
var bool: b;
constraint lq_zero_if_b(x, b);
constraint x <= 10;
\end{mzn}
If this fragment is processed without the \gls{del-rew}, then it will result in the following \nanozinc{}.
\begin{nzn}
var 0..100: x;
var bool: b;
constraint int_lin_le([1, 1000], [x,b], 1000);
\end{nzn}
Note, however, that if \mzninline{ub(x)} was known to be 100, then a stronger constraint could have been used.
When using \gls{del-rew}, the interpreter will instead first produce the following \nanozinc{}.
\begin{nzn}
var 0..100: x;
var bool: b;
constraint lq_zero_if_b(x, b);
\end{nzn}
Because the evaluation of \mzninline{lq_zero_if_b} is delayed until after the domain changes of \mzninline{x} it can instead produce \mzninline{int_lin_le([1, 100], [x,b], 100)}.
\end{example}
\subsection{Common Sub-expression Elimination}%
\label{sec:rew-cse}
As shown in \cref{subsec:back-cse}, \gls{cse} is a crucial technique to avoid duplications in a \gls{model}.
In our architecture \gls{cse} is performed on two levels.
The \microzinc{} \interpreter{} performs \gls{cse} through memoization.
It maintains a table that maps a function identifier and the call arguments to its result for each call instruction that is executed.
Before it executes a call instruction, it searches the table for an entry with identical function identifier and call arguments.
Since functions in \microzinc{} are guaranteed to be pure and total, whenever the table search succeeds, the result can be used instead of executing the call instruction.
Earlier in the process, however, the \minizinc{} to \microzinc{} \compiler{} can perform \gls{cse} as it is performed in many other programming languages.
\begin{example}
For instance, let us reconsider the following \minizinc{} fragment from \cref{ex:back-cse}.
\begin{mzn}
(abs(x)*2 >= 20) \/ (abs(x)+5 >= 15)
\end{mzn}
In this case a static \gls{compiler} analysis could detect that the same syntactical expression, \mzninline{abs(x)}, occurs twice.
Since these expressions are guaranteed to have equivalent results, this analysis could then hoist the shared expression into a \gls{let}.
\begin{mzn}
constraint let { var int: y = abs(x) } in
(y*2 >= 20) \/ (y+5 >= 15)
\end{mzn}
\codeendsexample{}
\end{example}
As such, the \compiler{} can enforce sharing of common sub-expressions before evaluation begins.
It is worth noting that, although the \gls{cse} approach based on memoization would also cover the static example above, this method can potentially avoid many dynamic table look-ups.
Since the static analysis is cheap, it is valuable to combine both approaches.
The static approach can be further improved by inlining function calls, since that may expose more calls as being syntactically equal.
\paragraph{Implementation} In the implementation of our \microzinc{} \interpreter{}, \gls{cse} directly interacts with the reference counting mechanism.
It is clear that when an expression is reused by returning it from the \gls{cse} table, this creates a new reference to that expression.
However, the entries in the \gls{cse} table should not keep the corresponding variables alive.
Otherwise, none of the \variables{} would ever become unused.
Therefore, we treat \gls{cse} entries as weak references.
They reference a \variable{}, but do not affect its liveness (i.e., increase its reference count).
If an entry is found in the \gls{cse} table with a reference count of zero, it is removed from the table and its contents are not used.
The usage of a \gls{cse} table can be costly.
The memory requirement and time spent finding entries can be significant in the overall process.
This is aggravated by the fact that \minizinc{} has many places where a function's body only contains a single call to a function that is not used anywhere else.
Although this structure offers flexibility when defining \minizinc{} libraries, it results in many \gls{cse} entries that differ only by their function identifier.
\begin{example}
For example, the definition of the \mzninline{knapsack} \gls{global} in the \minizinc{} standard library is shown in \cref{lst:rew-knapsack}.
After checking the \parameter{} arguments, the function merely returns the result of \mzninline{fzn_knapsack}.
This function is implemented in the \solver{} library to provide the implementation of the \mzninline{knapsack} \gls{global}.
The extra function layer ensures that these checks can be performed independently of the \solver{} library and that the \solver{} can make certain assumptions about the function arguments.
But because the modeller will only use \mzninline{knapsack}, it does not make sense to also create \gls{cse} entries for \mzninline{fzn_knapsack}, as it will never be used.
\end{example}
\begin{listing}
\mznfile{assets/listing/rew_knapsack.mzn}
\caption{\label{lst:rew-knapsack} The definition of \mzninline{knapsack} in the \minizinc{} standard library.}
\end{listing}
Therefore, we have added a flag to our call instruction.
This flag controls whether the call is subject to \gls{cse}.
This allows us to disable the \gls{cse} when it is guaranteed to be unnecessary.
\subsection{Constraint Aggregation}%
\label{subsec:rew-aggregation}
In our new system it is still possible to support \gls{aggregation}.
We aggregate \constraints{} by combining \constraints{} connected through attached \constraints{}, eliminating the need for \gls{avar} to which they are attached.
To aggregate a certain kind of \constraint{}, it is marked as a \gls{native} \constraint{}.
These functional definitions are kept in the \nanozinc{} program until a top-level \constraint{} is posted that uses the \variables{} to which they are attached.
Once this happens, the interpreter will employ dedicated \gls{aggregation} logic to visit the attached \constraints{} and combine them.
The top-level \gls{constraint} is then replaced by the combined \gls{constraint}.
When the \gls{avar} become unused, they will be removed using the normal mechanisms.
\begin{example} For example, let us reconsider the linear \constraint{} from \cref{ex:back-agg}: \mzninline{x + 2*y <= z}.
The constraint will result in the following \nanozinc{}.
\begin{nzn}
var int: x;
var int: y;
var int: z;
var int: i1;
↳ constraint '*'(y, 2, i1);
var int: i2;
↳ constraint '+'(x, i1, i2);
constraint '<='(i2, z);
\end{nzn}
\mzninline{*} and \mzninline{+} were marked as \gls{native} \constraints{} as they can be aggregated into a linear \constraint{}.
Their functional definitions are put in place in the \nanozinc{}, and then a top-level \mzninline{<=} \constraint{} is created.
The \interpreter{} will then recursively visit the arguments of the \constraints{}.
The \constraints{} are then combined into the following linear \constraint{}.
\begin{mzn}
constraint int_lin_le([1,2,-1], [x,y,z], 0)
\end{mzn}
\codeendsexample{}
\end{example}
\section{Experiments}\label{sec:rew-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 a \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 \glspl{annotation}.
The implementation is not optimized 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 \gls{rewriting} performance as well as a comparison with \glspl{interpreter} of other programming languages to demonstrate the efficiency gains that are possible thanks to the new architecture.
A description of the used computational environment, \minizinc{} instances, and versioned software has been included in \cref{ch:benchmarks}.
For our first experiment, we selected 17 models from the annual \minizinc{} challenge and compiled 5 instances of each model to \flatzinc{}, using the current \minizinc{} release version 2.5.5 and the new prototype system.
In both cases we use the standard \minizinc{} library of \glspl{global} (i.e., we decompose those \constraints{} rather than using \solver{} \gls{native} \constraints{}, in order to stress-test the \gls{rewriting}).
We measured pure \gls{rewriting} time, i.e., without time required to parse and type check in version 2.5.5, and without time required for compilation to \microzinc{} in the new system (compilation is usually very fast).
Times are averages of 10 runs.
\Cref{sfig:rew-compareruntime} compares the \gls{rewriting} time for each of \instances{}.
Points below the line indicate that the new system is faster.
On average, the new system achieves a speed-up of 5.5, with every \instance{} achieving at least 2.5 speed-up and multiple \instances{} with a speed-up of over 100.
In terms of memory performance (\cref{sfig:rew-comparemem}), version 2.5.5 can sometimes still outperform the new prototype.
We have identified that the main memory bottlenecks are our currently unoptimized implementations of \gls{cse} lookup tables and argument vectors.
These are very encouraging results, given that we are comparing a largely unoptimized prototype to a mature piece of software.
\begin{figure}
\centering
\begin{subfigure}[b]{.48\columnwidth}
\centering
\includegraphics[width=\columnwidth]{assets/img/rew_compare_runtime}
\caption{\label{sfig:rew-compareruntime}Average run time (ms)}
\end{subfigure}%
\hspace{0.04\columnwidth}%
\begin{subfigure}[b]{.48\columnwidth}
\centering
\includegraphics[width=\columnwidth]{assets/img/rew_compare_memory}
\caption{\label{sfig:rew-comparemem}Maximum resident set size (kbytes)}
\end{subfigure}
\caption{\label{fig:4-runtime}Performance on \gls{rewriting} \minizinc{} Challenge \instances{}. \minizinc{} 2.5.5 (x-axis) vs our prototype (y-axis), log-log plot. Dots below the line indicate the new system is better.}
\end{figure}
Our second experiment considers three well-known computational recursive functions: Ackermann, Fibonacci, and Takeuchi.
These functions are often used as a benchmark for implementations of functional programming languages.
Since the \minizinc{} language includes a strong functional programming component, we use these same functions as a benchmark for our prototype \interpreter{}.
In addition to \minizinc{} 2.5.5, we also compare our \interpreter{} to Python 3.8.5 \autocite{rossum-2009-python3} and OCaml 4.10.0 \autocite{leroy-2020-ocaml}.
The chosen function arguments try to exercise the \glspl{interpreter}, but without running into their limitations.
Times are averages of 1000 runs.
The results of the experiment are shown in \cref{fig:rew-interpreter-comp}.
For all experiments there is a clear ordering in the results:
\begin{itemize}
\item OCaml is the clear winner, always at least five times faster than its closest competitor;
\item our prototype finishes in second place;
\item Python is a close third, with only a significant difference compared to our prototype on the Ackermann benchmark; and
\item \minizinc{} 2.5.5 always finishes last out of the four interpreters with significant overhead over all other competitors.
\end{itemize}
Our prototype performs well in this test.
It clearly outperforms \minizinc{} 2.5.5 with about ten times speed-up, and it slightly outperforms Python, an optimized \interpreter{} for a general programming language.
It is clear though that OCaml, a language that is dedicated to these types of programs, still outperforms our prototype.
We are convinced, however, that we can get closer to its performance given the right improvements in our prototype.
\begin{figure}
\centering
\includegraphics[width=\columnwidth]{assets/img/rew_interpreter_plot}
\caption{\label{fig:rew-interpreter-comp} Run-time comparison of interpreters
on recursive functions: Takeuchi, Fibonacci, and Ackermann.}
\end{figure}
\pagebreak{}
\section{Summary}%
\label{sec:rew-summary}
This chapter presented a new architecture for \gls{rewriting} \minizinc{} \instances{} to \glspl{slv-mod}.
We introduced the intermediate languages \microzinc{} and \nanozinc{}, which are central to the \gls{rewriting} architecture.
\minizinc{} is transformed into a set of \microzinc{} definitions and a \nanozinc{} program.
Then, a \nanozinc{} program is partially evaluated using \microzinc{} definitions.
We presented formal \gls{rewriting} rules applied during this partial evaluation.
Continuously applying these rules would result in a correct, but inefficient \gls{slv-mod}.
Therefore, we extended the partial evaluation using well-known techniques to improve the \gls{slv-mod}: \gls{propagation} is used throughout the rewriting process, calls can be delayed until more information is known, \gls{cse} is used both when evaluating calls and during the \minizinc{} to \microzinc{} transformation, and \constraints{} can be aggregated when beneficial for the \solver{}.
Crucially, the system can correctly remove all unused and irrelevant \variables{} and \constraints{} by \nanozinc{}'s mechanism to attach \constraints{} to the \variable{} they define.
The prototype of this architecture is shown to be significantly faster than the current implementation of \minizinc{}.
The next chapter further extends the architecture to better reason about \gls{reif}, to fully avoid it or use \gls{half-reif} where possible.