More half-reification work and split on sentence
This commit is contained in:
parent
83c77ab568
commit
3294400a15
@ -47,6 +47,9 @@
|
||||
\usepackage{amssymb}
|
||||
\usepackage{unicode-math}
|
||||
|
||||
% Algorithms
|
||||
\usepackage[ruled,vlined]{algorithm2e}
|
||||
|
||||
% References
|
||||
\usepackage[
|
||||
style=apa,
|
||||
@ -79,9 +82,6 @@
|
||||
\setchessboard{showmover=false}
|
||||
|
||||
|
||||
% Algorithms
|
||||
% \usepackage[ruled,vlined]{algorithm2e}
|
||||
|
||||
% % TODO: What am I doing with this?
|
||||
\newcommand*\justify{%
|
||||
\fontdimen2\font=0.4em% interword space
|
||||
|
@ -2,76 +2,52 @@
|
||||
\chapter{Introduction}\label{ch:introduction}
|
||||
%************************************************
|
||||
|
||||
High-level \cmls{}, like \minizinc{}, were originally designed as a convenient input
|
||||
language for \gls{cp} solvers \autocite{marriott-1998-clp}. A user would write a
|
||||
model consisting of a few loops or comprehensions; given a data file for the
|
||||
parameters, this would be rewritten into a relatively small set of constraints
|
||||
which would be fed whole into the solver. The existing process for translating
|
||||
\minizinc\ into solver-specific constraints is a somewhat ad-hoc, (mostly)
|
||||
single-pass, recursive unrolling procedure, and many aspects (such as call
|
||||
overloading) are resolved dynamically. In its original application, this was
|
||||
acceptable: models (both before and after translation) were small, translation
|
||||
was fast, and the expected results of translation were obvious. The current
|
||||
architecture is illustrated in \Cref{sfig:4-oldarch}.
|
||||
High-level \cmls{}, like \minizinc{}, were originally designed as a convenient input language for \gls{cp} solvers \autocite{marriott-1998-clp}.
|
||||
A user would write a model consisting of a few loops or comprehensions; given a data file for the parameters, this would be rewritten into a relatively small set of constraints which would be fed whole into the solver.
|
||||
The existing process for translating \minizinc\ into solver-specific constraints is a somewhat ad-hoc, (mostly) single-pass, recursive unrolling procedure, and many aspects (such as call overloading) are resolved dynamically.
|
||||
In its original application, this was acceptable: models (both before and after translation) were small, translation was fast, and the expected results of translation were obvious.
|
||||
The current architecture is illustrated in \Cref{sfig:4-oldarch}.
|
||||
|
||||
But over time, the uses of high-level \cmls{} have expanded greatly from this
|
||||
original vision. It is now used to generate input for wildly different solver
|
||||
technologies: not just \gls{cp}, but also \gls{mip} \autocite{wolsey-1988-mip},
|
||||
\gls{sat} \autocite{davis-1962-dpll} and \gls{cbls}
|
||||
\autocite{bjordal-2015-fzn2oscarcbls} solvers. Crucially, the same constraint
|
||||
model can be used with any of these solvers, which is achieved through the use
|
||||
of solver-specific libraries of constraint definitions. In \minizinc{}, these
|
||||
solver libraries are written in the same language.
|
||||
But over time, the uses of high-level \cmls{} have expanded greatly from this original vision.
|
||||
It is now used to generate input for wildly different solver technologies: not just \gls{cp}, but also \gls{mip} \autocite{wolsey-1988-mip}, \gls{sat} \autocite{davis-1962-dpll} and \gls{cbls} \autocite{bjordal-2015-fzn2oscarcbls} solvers.
|
||||
Crucially, the same constraint model can be used with any of these solvers, which is achieved through the use of solver-specific libraries of constraint definitions.
|
||||
In \minizinc{}, these solver libraries are written in the same language.
|
||||
|
||||
As such, \minizinc\ turned out to be much more expressive than expected, so more
|
||||
and more preprocessing and model transformation has been added to both the core
|
||||
\minizinc\ library, and users' models. And in addition to one-shot solving,
|
||||
\minizinc\ is frequently used as the basis of a larger meta-optimisation tool
|
||||
chain: programmatically generating a model, solving it, then using the results
|
||||
to generate another slightly different model.
|
||||
As such, \minizinc\ turned out to be much more expressive than expected, so more and more preprocessing and model transformation has been added to both the core \minizinc\ library, and users' models.
|
||||
And in addition to one-shot solving, \minizinc\ is frequently used as the basis of a larger meta-optimisation tool chain: programmatically generating a model, solving it, then using the results to generate another slightly different model.
|
||||
|
||||
To a great extent, this is testament to the effectiveness of the language.
|
||||
However, as they have become more common, these extended uses have revealed
|
||||
weaknesses of the existing \minizinc\ tool chain. In particular:
|
||||
However, as they have become more common, these extended uses have revealed weaknesses of the existing \minizinc\ tool chain.
|
||||
In particular:
|
||||
|
||||
\begin{itemize}
|
||||
\item The \minizinc\ compiler is inefficient. It does a surprisingly large
|
||||
amount of work for each expression (especially resolving sub-typing
|
||||
and overloading), which may be repeated many times --- for example,
|
||||
inside the body of a comprehension. And as models generated for
|
||||
other solver technologies (particularly \gls{mip}) can be quite
|
||||
large, the resulting flattening procedure can be intolerably slow.
|
||||
As the model transformations implemented in \minizinc\ become more
|
||||
sophisticated, these performance problems are simply magnified.
|
||||
\item The generated models often contain unnecessary constraints. During
|
||||
the transformation, functional expressions are replaced with
|
||||
constraints. But this breaks the functional dependencies: if the
|
||||
original expression later becomes redundant (due to model
|
||||
simplifications), \minizinc\ may fail to detect that the constraint
|
||||
can be removed.
|
||||
\item Monolithic flattening is wasteful. When \minizinc\ is used for
|
||||
multi-shot solving, there is typically a large base model common to
|
||||
all sub-problems, and a small set of constraints which are added or
|
||||
removed in each iteration. But with the existing \minizinc\
|
||||
architecture, the whole model must be re-flattened each time. Many
|
||||
use cases involve generating a base model, then repeatedly adding or
|
||||
removing a few constraints before re-solving. In the current tool
|
||||
chain, the whole model must be fully re-flattened each time. Not
|
||||
only does this repeat all the work done to flatten the base model,
|
||||
This means a large (sometimes dominant) portion of runtime is simply
|
||||
flattening the core model over and over again. But it also prevents
|
||||
\emph{the solver} from carrying over anything it learnt from one
|
||||
problem to the next, closely related, problem.
|
||||
|
||||
\item The \minizinc\ compiler is inefficient.
|
||||
It does a surprisingly large amount of work for each expression (especially resolving sub-typing and overloading), which may be repeated many times --- for example, inside the body of a comprehension.
|
||||
And as models generated for other solver technologies (particularly \gls{mip}) can be quite large, the resulting flattening procedure can be intolerably slow.
|
||||
As the model transformations implemented in \minizinc\ become more sophisticated, these performance problems are simply magnified.
|
||||
|
||||
\item The generated models often contain unnecessary constraints.
|
||||
During the transformation, functional expressions are replaced with constraints.
|
||||
But this breaks the functional dependencies: if the original expression later becomes redundant (due to model simplifications), \minizinc\ may fail to detect that the constraint can be removed.
|
||||
|
||||
\item Monolithic flattening is wasteful.
|
||||
When \minizinc\ is used for multi-shot solving, there is typically a large base model common to all sub-problems, and a small set of constraints which are added or removed in each iteration.
|
||||
But with the existing \minizinc\ architecture, the whole model must be re-flattened each time.
|
||||
Many use cases involve generating a base model, then repeatedly adding or removing a few constraints before re-solving.
|
||||
In the current tool chain, the whole model must be fully re-flattened each time.
|
||||
Not only does this repeat all the work done to flatten the base model, This means a large (sometimes dominant) portion of runtime is simply flattening the core model over and over again.
|
||||
But it also prevents \emph{the solver} from carrying over anything it learnt from one problem to the next, closely related, problem.
|
||||
\end{itemize}
|
||||
|
||||
In this thesis, we revisit the rewriting of high-level \cmls\ into solver-level
|
||||
constraint models and describe an architecture that allows us to:
|
||||
In this thesis, we revisit the rewriting of high-level \cmls\ into solver-level constraint models and describe an architecture that allows us to:
|
||||
|
||||
\begin{itemize}
|
||||
\item easily integrate a range of \textbf{optimisation and simplification}
|
||||
techniques,
|
||||
\item effectively \textbf{detect and eliminate dead code} introduced by
|
||||
functional definitions, and
|
||||
\item support \textbf{incremental flattening and solving}, and better
|
||||
integration with solvers providing native incremental features.
|
||||
|
||||
\item easily integrate a range of \textbf{optimisation and simplification} techniques,
|
||||
|
||||
\item effectively \textbf{detect and eliminate dead code} introduced by functional definitions, and
|
||||
|
||||
\item support \textbf{incremental flattening and solving}, and better integration with solvers providing native incremental features.
|
||||
|
||||
\end{itemize}
|
||||
|
@ -2,17 +2,13 @@
|
||||
\chapter{Half Reification}\label{ch:half-reif}
|
||||
%************************************************
|
||||
|
||||
The complex expressions language used in \cmls{}, such as \minizinc{}, often
|
||||
require the use of \gls{reification} in the flattening process to reach a
|
||||
solver level constraint model. If the Boolean expression \mzninline{pred(...)}
|
||||
is seen in a non-root context, then a new Boolean \variable{} \mzninline{b} is
|
||||
introduced to replace the expression. The flattener then enforces a
|
||||
\constraint{} \mzninline{pred_reif(...,b)}, which binds the \variable{} to be
|
||||
the \emph{truth-value} of the expression (\ie\ \mzninline{b <-> pred(...)}).
|
||||
\section{Introduction to Half Reification}
|
||||
|
||||
A weakness of reification is that each reified version of a constraint requires
|
||||
further implementation to create, and indeed most solvers do not provide any
|
||||
reified versions of their \gls{global} \constraints{}.
|
||||
The complex expressions language used in \cmls{}, such as \minizinc{}, often require the use of \gls{reification} in the flattening process to reach a solver level constraint model.
|
||||
If the Boolean expression \mzninline{pred(...)} is seen in a non-root context, then a new Boolean \variable{} \mzninline{b} is introduced to replace the expression.
|
||||
The flattener then enforces a \constraint{} \mzninline{pred_reif(...,b)}, which binds the \variable{} to be the \emph{truth-value} of the expression (\ie\ \mzninline{b <-> pred(...)}).
|
||||
|
||||
A weakness of reification is that each reified version of a constraint requires further implementation to create, and indeed most solvers do not provide any reified versions of their \gls{global} \constraints{}.
|
||||
|
||||
\begin{example}\label{ex:hr-alldiff}
|
||||
Consider the complex constraint
|
||||
@ -30,29 +26,22 @@ reified versions of their \gls{global} \constraints{}.
|
||||
constraint bool_clause([b2], [b1]) % b1 implies b2
|
||||
\end{mzn}
|
||||
|
||||
but no solver we are aware of implements the third primitive
|
||||
constraint.\footnote{Although there are versions of soft
|
||||
\mzninline{all_different}, they do not define this form.}
|
||||
but no solver we are aware of implements the third primitive constraint.
|
||||
%
|
||||
\footnote{Although there are versions of soft \mzninline{all_different}, they
|
||||
do not define this form.}
|
||||
\end{example}
|
||||
|
||||
Reified \gls{global} \constraints{} are not implemented because a reified constraint
|
||||
\mzninline{b <-> pred(...)} must also implement a propagator for \mzninline{not
|
||||
pred(...)} (in the case that \mzninline{b = false}). While for some global
|
||||
\constraints{}, \eg\ \mzninline{all_different}, this may be reasonable to
|
||||
implement, for most, such as \texttt{cumulative}, the task seems to be very
|
||||
difficult.
|
||||
Reified \gls{global} \constraints{} are not implemented because a reified constraint \mzninline{b <-> pred(...)} must also implement a propagator for \mzninline{not pred(...)} (in the case that \mzninline{b = false}).
|
||||
While for some global \constraints{}, \eg\ \mzninline{all_different}, this may be reasonable to implement, for most, such as \texttt{cumulative}, the task seems to be very difficult.
|
||||
|
||||
Another weakness of the reification is that it may keep track of more
|
||||
information than is required. In a typical solver, the first reified constraint
|
||||
\mzninline{b1 <-> i <= 4} will wake up whenever the upper bound of \texttt{i}
|
||||
changes in order to check whether it should set \texttt{b1} to
|
||||
\mzninline{true}. But setting \mzninline{b1} to \mzninline{true} will
|
||||
\emph{never} cause any further propagation. There is no reason to check this.
|
||||
Another weakness of the reification is that it may keep track of more information than is required.
|
||||
In a typical solver, the first reified constraint \mzninline{b1 <-> i <= 4} will wake up whenever the upper bound of \texttt{i} changes in order to check whether it should set \texttt{b1} to \mzninline{true}.
|
||||
But setting \mzninline{b1} to \mzninline{true} will \emph{never} cause any further propagation.
|
||||
There is no reason to check this.
|
||||
|
||||
This is particularly important when the target solver is a mixed integer
|
||||
programming solver. In order to linearise a reified linear constraint we need
|
||||
to create two linear \constraints{}, but if we are only interested in half of the
|
||||
behaviour we can manage this with one linear constraint.
|
||||
This is particularly important when the target solver is a mixed integer programming solver.
|
||||
In order to linearise a reified linear constraint we need to create two linear \constraints{}, but if we are only interested in half of the behaviour we can manage this with one linear constraint.
|
||||
|
||||
\begin{example}
|
||||
Consider the constraint \mzninline{b1 <-> i <= 4}, where \texttt{i} can take
|
||||
@ -64,22 +53,29 @@ behaviour we can manage this with one linear constraint.
|
||||
\end{mzn}
|
||||
|
||||
But in the system of \constraints{} where this constraint occurs knowing that
|
||||
\texttt{b1} is 0 will never cause the system to fail, hence we do not need to
|
||||
keep track of it. We can simply use the second constraint in the
|
||||
linearisation, which always allows that \texttt{b1} takes the value 0.
|
||||
\texttt{b1} is 0 will never cause the system to fail, hence we do not need to
|
||||
keep track of it.
|
||||
%
|
||||
We can simply use the second constraint in the linearisation, which always
|
||||
allows that \texttt{b1} takes the value 0.
|
||||
\end{example}
|
||||
|
||||
The simple flattening used above treats partial functions in the following
|
||||
manner. Application of a partial function to a value for which it is not defined
|
||||
gives value \undefined, and this \undefined\ function percolates up through
|
||||
every expression to the top level conjunction, making the model unsatisfiable.
|
||||
manner.
|
||||
%
|
||||
Application of a partial function to a value for which it is not defined gives
|
||||
value \undefined, and this \undefined\ function percolates up through every
|
||||
expression to the top level conjunction, making the model unsatisfiable.
|
||||
%
|
||||
For the example
|
||||
%
|
||||
\jip{TODO:\ What goes here???}
|
||||
|
||||
In this chapter we study the usage of \gls{half-reif}. \gls{half-reif} follows
|
||||
from the notion that in many cases it might be sufficient to use the logical
|
||||
implication of an expression, \mzninline{b -> pred(...)}, instead of the
|
||||
logical equivalence, \mzninline{b <-> pred(...)}. Flattening with
|
||||
half-reification is an approach that improves upon all these weaknesses of
|
||||
\gls{half-reif} is an approach that improves upon all these weaknesses of
|
||||
flattening with \emph{full} reification.
|
||||
|
||||
\begin{itemize}
|
||||
@ -105,12 +101,10 @@ flattening with \emph{full} reification.
|
||||
\end{itemize}
|
||||
|
||||
The remainder of the chapter is organised as follows.
|
||||
\Cref{sec:half-propagation} discusses the propagation of half-reified
|
||||
\constraints{}. \Cref{sec:half-decomposition} discusses the decomposition of
|
||||
half-reified constraint. \Cref{sec:half-context} introduces the notion of
|
||||
context analysis: a way to determine if half-reification can be used for a
|
||||
certain expression. Finally, \cref{sec:half-flattening} explains how this
|
||||
information can be used during the flattening process.
|
||||
\Cref{sec:half-propagation} discusses the propagation of half-reified \constraints{}.
|
||||
\Cref{sec:half-decomposition} discusses the decomposition of half-reified constraint.
|
||||
\Cref{sec:half-context} introduces the notion of context analysis: a way to determine if \gls{half-reif} can be used for a certain expression.
|
||||
Finally, \cref{sec:half-flattening} explains how this information can be used during the flattening process.
|
||||
|
||||
\section{Propagation and Half Reification}%
|
||||
\label{sec:half-propagation}
|
||||
@ -122,23 +116,15 @@ information can be used during the flattening process.
|
||||
\item experimental results
|
||||
\end{itemize}
|
||||
|
||||
A propagation engine gains certain advantages from half-reification, but also
|
||||
may suffer certain penalties. Half reification can cause propagators to wake up
|
||||
less frequently, since variables that are fixed to true by full reification will
|
||||
never be fixed by half reification. This is advantageous, but a corresponding
|
||||
disadvantage is that variables that are fixed can allow the simplification of
|
||||
the propagator, and hence make its propagation faster.
|
||||
A propagation engine gains certain advantages from \gls{half-reif}, but also may suffer certain penalties.
|
||||
Half reification can cause propagators to wake up less frequently, since variables that are fixed to true by full reification will never be fixed by half reification.
|
||||
This is advantageous, but a corresponding disadvantage is that variables that are fixed can allow the simplification of the propagator, and hence make its propagation faster.
|
||||
|
||||
When full reification is applicable (where we are not using half reified
|
||||
predicates) an alternative to half reification is to implement full reification
|
||||
\mzninline{x <-> pred(...)} by two half reified propagators \mzninline{x ->
|
||||
pred(...)}, \mzninline{y \half \neg pred(...)}, \mzninline{x <-> not y}. This
|
||||
does not lose propagation strength. For Booleans appearing in a positive
|
||||
context we can make the propagator \mzninline{y -> not pred(...)} run at the
|
||||
lowest priority, since it will never cause failure. Similarly in negative
|
||||
contexts we can make the propagator \mzninline{b -> pred(...)} run at the
|
||||
lowest priority. This means that Boolean variables are still fixed at the same
|
||||
time, but there is less overhead.
|
||||
When full reification is applicable (where we are not using half reified predicates) an alternative to half reification is to implement full reification \mzninline{x <-> pred(...)} by two half reified propagators \mzninline{x -> pred(...)}, \mzninline{y \half \neg pred(...)}, \mzninline{x <-> not y}.
|
||||
This does not lose propagation strength.
|
||||
For Booleans appearing in a positive context we can make the propagator \mzninline{y -> not pred(...)} run at the lowest priority, since it will never cause failure.
|
||||
Similarly in negative contexts we can make the propagator \mzninline{b -> pred(...)} run at the lowest priority.
|
||||
This means that Boolean variables are still fixed at the same time, but there is less overhead.
|
||||
|
||||
\section{Decomposition and Half Reification}%
|
||||
\label{sec:half-decomposition}
|
||||
@ -146,97 +132,76 @@ time, but there is less overhead.
|
||||
\section{Context Analysis}%
|
||||
\label{sec:half-context}
|
||||
|
||||
\Gls{half-reif} can be used instead of full \gls{reification} when the
|
||||
\gls{reification} can never be forced to be false. We see this in, for example,
|
||||
a disjunction \(a \lor b\). No matter the value of \(a\), setting the value of
|
||||
\(b\) to be true can never make the overall expression false. At any \(b\) is thus
|
||||
never forced to be false. This requirement follows from the difference between
|
||||
implication and logical equivalences. Setting the left hand side of a
|
||||
implication to false, does not influence the value on the right hand side. So
|
||||
if we know that this is never required in the overall expression, then using an
|
||||
implication instead of a logical equivalence, \ie a \gls{half-reif} instead of
|
||||
a full \gls{reification}, does not change the meaning of the constraint.
|
||||
\Gls{half-reif} can be used instead of full \gls{reification} when the \gls{reification} can never be forced to be false.
|
||||
We see this in, for example, a disjunction \(a \lor b\).
|
||||
No matter the value of \(a\), setting the value of \(b\) to be true can never make the overall expression false.
|
||||
At any \(b\) is thus never forced to be false.
|
||||
This requirement follows from the difference between implication and logical equivalences.
|
||||
Setting the left hand side of a implication to false, does not influence the value on the right hand side.
|
||||
So if we know that this is never required in the overall expression, then using an implication instead of a logical equivalence, \ie a \gls{half-reif} instead of a full \gls{reification}, does not change the meaning of the constraint.
|
||||
|
||||
This property can be extended to include non-Boolean expressions.
|
||||
Since Boolean expressions in \minizinc{} can be used in, for example, integer expressions, we can apply similar reasoning to these types of expressions.
|
||||
For example the left hand side of the constraint
|
||||
|
||||
This property can be extended to include non-Boolean expressions. Since Boolean
|
||||
expressions in \minizinc{} can be used in, for example, integer expressions, we
|
||||
can apply similar reasoning to these types of expressions. For example the left
|
||||
hand side of the constraint
|
||||
%
|
||||
\begin{mzn}
|
||||
constraint count(x in arr)(x = 5) > 5;
|
||||
\end{mzn}
|
||||
%
|
||||
is an integer expression that contains the Boolean expression \mzninline{x =
|
||||
5}. Since the increasing left hand side of the constraint will only ever help
|
||||
satisfy the constraint, the expression \mzninline{x = 5} will never forced to
|
||||
be false. This means that we can half-reify the expression.
|
||||
|
||||
To systematically analyse whether Booelean expressions can be half-reified, we
|
||||
introduce extra distinctions in the context of expressions. Before, we would
|
||||
merely distinguish between \rootc{} context and \emph{non-root} context. Now,
|
||||
we will categorise the latter into:
|
||||
is an integer expression that contains the Boolean expression \mzninline{x = 5}.
|
||||
Since the increasing left hand side of the constraint will only ever help satisfy the constraint, the expression \mzninline{x = 5} will never forced to be false.
|
||||
This means that we can half-reify the expression.
|
||||
|
||||
To systematically analyse whether Booelean expressions can be half-reified, we introduce extra distinctions in the context of expressions.
|
||||
Before, we would merely distinguish between \rootc{} context and \emph{non-root} context.
|
||||
Now, we will categorise the latter into:
|
||||
|
||||
\begin{description}
|
||||
|
||||
\item[\posc{} context] when an expression must reach \emph{at least} a
|
||||
certain value to satisfy its enclosing constraint. The expression is never
|
||||
forced to take a lower value.
|
||||
\item[\posc{} context] when an expression must reach \emph{at least} a certain value to satisfy its enclosing constraint.
|
||||
The expression is never forced to take a lower value.
|
||||
|
||||
\item[\negc{} context] when an expression can reach \emph{at most} a certain
|
||||
value to satisfy its enclosing constraint. The expression is never forced
|
||||
to take a higher value.
|
||||
\item[\negc{} context] when an expression can reach \emph{at most} a certain value to satisfy its enclosing constraint.
|
||||
The expression is never forced to take a higher value.
|
||||
|
||||
\item[\mixc{} context] when an expression must take an \emph{exact value}, be
|
||||
within a \emph{specified range} or when during flattening it cannot be
|
||||
determined whether the expression must be increased or decreased to satisfy
|
||||
the enclosing constraint.
|
||||
\item[\mixc{} context] when an expression must take an \emph{exact value}, be within a \emph{specified range} or when during flattening it cannot be determined whether the expression must be increased or decreased to satisfy the enclosing constraint.
|
||||
|
||||
\end{description}
|
||||
|
||||
As previously explained, \gls{half-reif} can be used for expressions in \posc{}
|
||||
context. Although expressions in a \negc{} context cannot be directly
|
||||
half-reified, the negation of a expression in a \negc{} context can be
|
||||
half-reified. Consider, for example, the constraint
|
||||
%
|
||||
As previously explained, \gls{half-reif} can be used for expressions in \posc{} context.
|
||||
Although expressions in a \negc{} context cannot be directly half-reified, the negation of a expression in a \negc{} context can be half-reified.
|
||||
Consider, for example, the constraint
|
||||
|
||||
\begin{mzn}
|
||||
constraint b \/ not (x = 5);
|
||||
\end{mzn}
|
||||
%
|
||||
The expression \mzninline{x = 5} is in a \negc{} context. Although a
|
||||
\gls{half-reif} cannot be used directly, in some cases the solver can negate
|
||||
the expression which are then placed in a \posc{} context. Our example can be
|
||||
transformed into:
|
||||
%
|
||||
|
||||
The expression \mzninline{x = 5} is in a \negc{} context.
|
||||
Although a \gls{half-reif} cannot be used directly, in some cases the solver can negate the expression which are then placed in a \posc{} context.
|
||||
Our example can be transformed into:
|
||||
|
||||
\begin{mzn}
|
||||
constraint b \/ x != 5;
|
||||
\end{mzn}
|
||||
%
|
||||
The transformed expression, \mzninline{x != 5}, is now in a \posc{} context. We
|
||||
can also speak of this process as ``pushing the negation inwards''.
|
||||
|
||||
Expressions in a \mixc{} context are in a position where \gls{half-reif} is
|
||||
impossible. Only full \gls{reification} can be used for expressions in that are
|
||||
in this context. This occurs, for example, when using an exclusive or
|
||||
expression in a constraint. The value that one side must take directly depends
|
||||
on the value that the other side takes. Each side can thus be forced to be true
|
||||
or false. The \mixc{} context can also be used as a ``fall back'' context; if
|
||||
it cannot be determined if an expression is in a \posc{} or \negc{} context,
|
||||
then it is always safe to say the expression is in a \mixc{} context.
|
||||
The transformed expression, \mzninline{x != 5}, is now in a \posc{} context.
|
||||
We can also speak of this process as ``pushing the negation inwards''.
|
||||
|
||||
When taking into account the possible undefinedness of an expression, every
|
||||
expression in a \minizinc{} model has two different contexts: the context in
|
||||
which the expression itself occurs, its \emph{value context}, and the context
|
||||
in which the partiality of the expression is captured, its \emph{partiality
|
||||
context}. As described in \cref{subsec:back-mzn-partial}, \minizinc{} uses
|
||||
relational semantics of partial values. This means that if a function does not
|
||||
have a result, then its nearest enclosing Boolean expression is set to false.
|
||||
In practice, this means that a condition that tests if the function will return
|
||||
a value is added to the nearest enclosing Boolean expression. The
|
||||
\emph{partiality} context is the context in which this condition is placed.
|
||||
Expressions in a \mixc{} context are in a position where \gls{half-reif} is impossible.
|
||||
Only full \gls{reification} can be used for expressions in that are in this context.
|
||||
This occurs, for example, when using an exclusive or expression in a constraint.
|
||||
The value that one side must take directly depends on the value that the other side takes.
|
||||
Each side can thus be forced to be true or false.
|
||||
The \mixc{} context can also be used as a ``fall back'' context; if it cannot be determined if an expression is in a \posc{} or \negc{} context, then it is always safe to say the expression is in a \mixc{} context.
|
||||
|
||||
We now specify two context transformations that will be used in further
|
||||
algorithms to transition between different contexts: \changepos{} and
|
||||
\changeneg{}. The transformations have the following behaviour:
|
||||
When taking into account the possible undefinedness of an expression, every expression in a \minizinc{} model has two different contexts: the context in which the expression itself occurs, its \emph{value context}, and the context in which the partiality of the expression is captured, its \emph{partiality context}.
|
||||
As described in \cref{subsec:back-mzn-partial}, \minizinc{} uses relational semantics of partial values.
|
||||
This means that if a function does not have a result, then its nearest enclosing Boolean expression is set to false.
|
||||
In practice, this means that a condition that tests if the function will return a value is added to the nearest enclosing Boolean expression.
|
||||
The \emph{partiality} context is the context in which this condition is placed.
|
||||
|
||||
We now specify two context transformations that will be used in further algorithms to transition between different contexts: \changepos{} and \changeneg{}.
|
||||
The transformations have the following behaviour:
|
||||
|
||||
\begin{tabular}{ccc}
|
||||
\(
|
||||
@ -263,68 +228,133 @@ algorithms to transition between different contexts: \changepos{} and
|
||||
\section{Flattening and Half Reification}%
|
||||
\label{sec:half-flattening}
|
||||
|
||||
During the flattening process the contexts assigned to the different
|
||||
expressions can be used directly to determine if and how a expression has to be
|
||||
reified. The flattening with \gls{half-reif} does, however, interact with some
|
||||
of the optimisations used during the flattening process. Most importantly,
|
||||
\gls{half-reif} has to be considered when using \gls{cse}.
|
||||
During the flattening process the contexts assigned to the different expressions can be used directly to determine if and how a expression has to be reified.
|
||||
|
||||
When using full \gls{reification}, all \glspl{reification} are stored in the
|
||||
\gls{cse} table. This ensure that if we see the same expression is reified
|
||||
twice, then the resulting \variable{} would be reusing. This avoids that the
|
||||
solver has to enforce the same functional relationship twice.
|
||||
\jip{TODO: Add example of flattening with \gls{half-reif}}
|
||||
|
||||
If the flattener uses \gls{half-reif}, in addition to full \gls{reification},
|
||||
then \gls{cse} needs to ensure not just that the expressions are equivalent,
|
||||
but also that the context of the two expressions are compatible. For example,
|
||||
if an expression was first found in a \posc{} context and later found in a
|
||||
\mixc{} context, then the resulting \gls{half-reification} from the first
|
||||
cannot be used for the second expression. In general:
|
||||
The flattening with \gls{half-reif} does, however, interact with some of the optimisations used during the flattening process.
|
||||
Most importantly, \gls{half-reif} has to be considered when using \gls{cse}.
|
||||
In \cref{subsec:half-cse} we will discuss how \gls{cse} can be adjusted to handle \gls{half-reif}.
|
||||
|
||||
As shown in \jip{insert reference}, a consequence of the use of \gls{half-reif} is that it might form so called \emph{implication chains}.
|
||||
This happens when the right hand side of an implication is half reifed and a new Boolean variable is created to represent the variable.
|
||||
Instead, we could have directly posted the half-reified constraint using the left hand side of the implication as its control variable.
|
||||
In \cref{subsec:half-compress} we present a new post-processing method, \emph{chain compression}, that can be used to eliminate these implication chains.
|
||||
|
||||
\subsection{Common Sub-expression Elimination}%
|
||||
\label{subsec:half-cse}
|
||||
|
||||
When using full \gls{reification}, all \glspl{reification} are stored in the \gls{cse} table.
|
||||
This ensure that if we see the same expression is reified twice, then the resulting \variable{} would be reusing.
|
||||
This avoids that the solver has to enforce the same functional relationship twice.
|
||||
|
||||
If the flattener uses \gls{half-reif}, in addition to full \gls{reification}, then \gls{cse} needs to ensure not just that the expressions are equivalent, but also that the context of the two expressions are compatible.
|
||||
For example, if an expression was first found in a \posc{} context and later found in a \mixc{} context, then the resulting \gls{half-reif} from the first cannot be used for the second expression.
|
||||
In general:
|
||||
|
||||
\begin{itemize}
|
||||
|
||||
\item The flattening result of a \posc{} context, a \gls{half-reif}, can only
|
||||
be reused if the same expression is again found in \posc{} context.
|
||||
\item The flattening result of a \posc{} context, a \gls{half-reif}, can only be reused if the same expression is again found in \posc{} context.
|
||||
|
||||
\item The flattening result of a \negc{} context, a \gls{half-reif} with a
|
||||
negation pushed inwards, can only be reused if the same expression is again
|
||||
found in \negc{} context.
|
||||
\item The flattening result of a \negc{} context, a \gls{half-reif} with its negation pushed inwards, can only be reused if the same expression is again found in \negc{} context.
|
||||
|
||||
\item The flattening result of a \mixc{} context, a \gls{reification}, can be
|
||||
reused in \posc{}, \negc{}, and \mixc{} context. Since we assume that the
|
||||
result of a flattening an expression in \negc{} context pushes the negation
|
||||
inwards, the \gls{reification} does, however, need to be negated.
|
||||
\item The flattening result of a \mixc{} context, a \gls{reification}, can be reused in \posc{}, \negc{}, and \mixc{} context.
|
||||
Since we assume that the result of a flattening an expression in \negc{} context pushes the negation inwards, the \gls{reification} does, however, need to be negated.
|
||||
|
||||
\item If the expression was already flattened in \rootc{} context, then any
|
||||
repeated usage of the expression can be assumed to take the value
|
||||
\mzninline{true} (or \mzninline{false} in \negc{} context).
|
||||
\item If the expression was already flattened in \rootc{} context, then any repeated usage of the expression can be assumed to take the value \mzninline{true} (or \mzninline{false} in \negc{} context).
|
||||
|
||||
\end{itemize}
|
||||
|
||||
When considering these compatibility rules, the result of flattening would be
|
||||
highly dependent on the order in which expressions are seen by the flattener.
|
||||
It would always be better to encounter the expression in a context that results
|
||||
in a reusable expression, \eg{} \mixc{}, before seeing the same expression in
|
||||
another context, \eg{} \posc{}. This avoids creating both a full
|
||||
\gls{reification} and a \gls{half-reif} of the same expression.
|
||||
When considering these compatibility rules, the result of flattening would be highly dependent on the order in which expressions are seen by the flattener.
|
||||
It would always be better to encounter the expression in a context that results in a reusable expression, \eg{} \mixc{}, before seeing the same expression in another context, \eg{} \posc{}.
|
||||
This avoids creating both a full \gls{reification} and a \gls{half-reif} of the same expression.
|
||||
|
||||
In the \microzinc{} interpreter, this problem is resolved by only keeping the
|
||||
result of the \emph{most compatible} context. If an expression is found another
|
||||
time in another context that is compatible with more contexts, then only the
|
||||
result of evaluating this context is kept in the \gls{cse} table. Every usage
|
||||
of the less compatible, is replaced by the newly created version. Because of
|
||||
dependency tracking of the constraints that define variables, we can be sure
|
||||
that all \variables{} and \constraints{} created in defining the earlier
|
||||
version are correctly removed.
|
||||
In the \microzinc{} interpreter, this problem is resolved by only keeping the result of the \emph{most compatible} context.
|
||||
If an expression is found another time in another context that is compatible with more contexts, then only the result of evaluating this context is kept in the \gls{cse} table.
|
||||
Every usage of the less compatible, is replaced by the newly created version.
|
||||
Because of dependency tracking of the constraints that define variables, we can be sure that all \variables{} and \constraints{} created in defining the earlier version are correctly removed.
|
||||
|
||||
In addition, if the same expression is found in both \posc{} and \negc{}
|
||||
context, then we would create both the \gls{half-reif} of the expression and
|
||||
its negation. The propagation of these two \glspl{half-reif} would be
|
||||
equivalent to propagating the full \gls{reification} of the same expression. It
|
||||
is therefore better to actually create the full \gls{reification} as it would
|
||||
be able to be reused during flattening.
|
||||
In addition, if the same expression is found in both \posc{} and \negc{} context, then we would create both the \gls{half-reif} of the expression and its negation.
|
||||
The propagation of these two \glspl{half-reif} would be equivalent to propagating the full \gls{reification} of the same expression.
|
||||
It is therefore better to actually create the full \gls{reification} as it would be able to be reused during flattening.
|
||||
|
||||
This problem is solved by introducing a canonical form for expressions where negations can be pushed inwards.
|
||||
In this form the result of flattening an expression and its negation are collected in the same place within the \gls{cse} table.
|
||||
If it is found that for an expression that is about to be half reified there already exists an \gls{half-reif} for its negation, then we instead evaluate the expression in mixed context, reifying the expression and replacing the existing half reified expression.
|
||||
|
||||
This canonical form for expressions and their negations can also be used for the expressions in other contexts.
|
||||
Using the canonical form we can now also be sure that we never create a full \gls{reification} for both an expression and its negation.
|
||||
Instead, when one is created, the negation of the resulting \variable{} releasises its negation.
|
||||
Moreover, this mechanism also allows us to detect when an expression and its negation occur in \rootc{} context.
|
||||
This is simple way to detect conflicts between \constraints{} and, by extend, prove that the constraint model is unsatisfiable.
|
||||
Clearly, a \constraint{} and its negation cannot both hold at the same time.
|
||||
|
||||
\subsection{Chain compression}%
|
||||
\label{subsec:half-compress}
|
||||
|
||||
As shown in \cref{ex:hr-half}, flattening with half reification will in many cases result in implication chains: \mzninline{b1 -> b2 /\ b2 -> c}, where \texttt{b2} has no other occurrences.
|
||||
In this case the conjunction can be replaced by \mzninline{b1 -> c} and \texttt{b2} can be removed from the model.
|
||||
The case shown in the example can be generalised to
|
||||
|
||||
\begin{mzn}
|
||||
b1 -> b2 /\ forall(i in N)(b2 -> c[i])
|
||||
\end{mzn}
|
||||
|
||||
\noindent{}which, if \texttt{b2} has no other usage in the instance, can be resolved to
|
||||
|
||||
\begin{mzn}
|
||||
forall(i in N)(b1 -> c[i])
|
||||
\end{mzn}
|
||||
|
||||
\noindent{}after which \texttt{b1} can be removed from the model.
|
||||
|
||||
|
||||
An algorithm to remove these chains of implications is best visualised through the use of an implication graph.
|
||||
An implication graph \(\tuple{V,E}\) is a directed graph where the vertices \(V\) represent the variables in the instance and an edge \(\tuple{x,y} \in E\) represents the presence of an implication \mzninline{x -> y} in the instance.
|
||||
Additionally, for the benefit of the algorithm, a vertex is marked when it is used in other constraints in the constraint model.
|
||||
The goal of the algorithm is now to identify and remove vertices that are not marked and have only one incoming edge.
|
||||
\Cref{alg:half-compression} provides a formal specification of the chain compression method in pseudo code.
|
||||
|
||||
\begin{algorithm}
|
||||
\KwData{An implication constraint graph \(G=\tuple{V, E}\) and a set \(M
|
||||
\subseteq{} V\) of vertices used in other constraints.}
|
||||
|
||||
\KwResult{An equisatisfiable graph \(G'=\tuple{V', E'}\) where chained
|
||||
implications have been removed.}
|
||||
|
||||
\(V' \longleftarrow V\)\;
|
||||
\(E' \longleftarrow E\)\;
|
||||
\For{\(x \in V\)} {
|
||||
\If{\(x \not\in M\) \textbf{ and }\(\left|\left\{\tuple{a,x}| \tuple{a,x} \in E\right\}\right| = 1\)}{
|
||||
\For{\(\tuple{x, b} \in E\)}{
|
||||
\(E' \longleftarrow E' \cup \{ \tuple{a,b} \} \)\;
|
||||
\(E' \longleftarrow E' \backslash \{ \tuple{x,b} \} \)\;
|
||||
}
|
||||
\(E' \longleftarrow E' \backslash \{ \tuple{a,x} \} \)\;
|
||||
\(V' \longleftarrow V' \backslash \{ x \} \)\;
|
||||
}
|
||||
}
|
||||
\(G' \longleftarrow \tuple{V', E'}\)\;
|
||||
\caption{\label{alg:half-compression} Implication chain compression algorithm}
|
||||
\end{algorithm}
|
||||
|
||||
The algorithm can be further improved by considering implied conjunctions.
|
||||
These can be split up into multiple implications:
|
||||
|
||||
\begin{mzn}
|
||||
b -> forall(x in N)(x)
|
||||
\end{mzn}
|
||||
|
||||
\noindent{}is equivalent to
|
||||
|
||||
\begin{mzn}
|
||||
forall(x in N)(b -> x)
|
||||
\end{mzn}
|
||||
|
||||
Adopting this transformation both simplifies a complicated constraint and possibly allows for the further compression of implication chains.
|
||||
It should however be noted that although this transformation can result in an increase in the number of constraints, it generally increases the propagation efficiency.
|
||||
|
||||
To adjust the algorithm to simplify implied conjunctions more introspection from the \minizinc{} compiler is required.
|
||||
The compiler must be able to tell if a variable is (only) a control variable of a reified conjunction and what the elements of that conjunction are.
|
||||
In the case where a variable has one incoming edge, but it is marked as used in other constraint, we can now check if it is only a control variable for a reified conjunction and perform the transformation in this case.
|
||||
|
||||
This problem is solved by introducing a canonical form for expressions where
|
||||
negations can be pushed inwards. In this form an expression and its negation
|
||||
should map to the same value in the \gls{cse} table, although in different
|
||||
contexts. As we discussed before, \negc{}
|
||||
|
Reference in New Issue
Block a user