Constraint solvers do not directly support MiniZinc models, rather in order
to run a MiniZinc model, it is translated into a simple subset of MiniZinc
called FlatZinc. FlatZinc reflects the fact that most constraint solvers
only solve satisfaction
problems of the form \(\bar{exists} c_1 \wedge \cdots \wedge c_m\)
or optimization problems of the form
\(\text{minimize } z \text{ subject to } c_1 \wedge \cdots \wedge c_m\),
where \(c_i\) are primitive constraints and \(z\) is an integer or float
expression in a restricted form.

The minizinc tool includes the MiniZinc compiler, which
takes a MiniZinc model and data files and creates
a flattened FlatZinc model which is equivalent to the MiniZinc model with
the given data, and that appears in the restricted form discussed above.
Normally the construction of a FlatZinc model which is sent to a solver is
hidden from the user but you can view the result of flattening a model
model.mzn with
its data data.dzn as follows:

minizinc -c model.mzn data.dzn

which creates a FlatZinc model called model.fzn.

In this chapter we explore the process of translation from MiniZinc to FlatZinc.

The restrictions of the underlying solver mean that complex expressions in
MiniZinc need to be flattened to only use conjunctions of primitive
constraints which do not themselves contain structured terms.

Consider the following model for ensuring that two circles in a rectangular
box do not overlap:

float:width;% width of rectangle to hold circlesfloat:height;% height of rectangle to hold circlesfloat:r1;varr1..width-r1:x1;% (x1,y1) is center of circle of radius r1varr1..height-r1:y1;float:r2;varr2..width-r2:x2;% (x2,y2) is center of circle of radius r2varr2..height-r2:y2;% centers are at least r1 + r2 apartconstraint(x1-x2)*(x1-x2)+(y1-y2)*(y1-y2)>=(r1+r2)*(r1+r2);solvesatisfy;

the translation to FlatZinc first simplifies the model by replacing all the
parameters by their values, and evaluating any fixed expression.
After this simplification the values of parameters are not longer needed.
An exception to this is large arrays of parametric values. If they are
used more than once, then the parameter is retained to avoid duplicating the
large expression.

After simplification the variable and parameter declarations parts of
the model of Listing 2.8.1 become

Now no constraint solver directly handles complex constraint expressions
like the one in Listing 2.8.1.
Instead, each subexpression in the expression is named, and we create a
constraint to construct the value of each expression. Let’s examine the
subexpressions of the constraint expression. (x1-x2) is a
subexpression, if we name if FLOAT01 we can define it as
constraintFLOAT01=x1-x2; Notice that this expression occurs
twice in the model. We only need to contruct the value once, we can then
reuse it. This is called common subexpression elimination.
The subexpression (x1-x2)*(x1-x2) can be named
FLOAT02
and we can define it as
constraintFLOAT02=FLOAT01*FLOAT01;
We can similarly name constraintFLOAT03=y1-y2;
and
constraintFLOAT04=FLOAT03*FLOAT03;
and finally constraintFLOAT05=FLOAT02*FLOAT04;.
The inequality constraint itself becomes
constraintFLOAT05>=25.0; since (r1+r2)*(r1+r2)
is calculated as 25.0.
The flattened constraint is hence

Flattening as its final step converts the form of the constraint to a
standard FlatZinc form which is always \(p(a_1, \ldots, a_n)\) where
p is the name of the primitive constraint and \(a_1, \ldots, a_n\) are the
arguments. FlatZinc tries to use a minimum of different constraint forms so
for example the constraint FLOAT01=x1-x2 is first rewritten to
FLOAT01+x2=x1 and then output using the float_plus primitive
constraint. The resulting constraint form is as follows:

We are still missing one thing, the
declarations for the introduced variables FLOAT01, …,
FLOAT05. While these could just be declared as
varfloat, in order to make the solver’s task easier MiniZinc tries to
determine upper and lower bounds on newly introduced variables, by a simple
bounds analysis. For example since FLOAT01=x1-x2
and \(2.0 \leq\)x1\(\leq 8.0\) and \(3.0 \leq\)x2\(\leq 7.0\) then we can see that
\(-5.0 \leq\)FLOAT0\(\leq 5.0\). Given this information we can see that
\(-25.0 \leq\)FLOAT02\(\leq 25.0\) (although note that if we recognized that the
multiplication was in fact a squaring we could give the much more accurate
bounds \(0.0 \leq\)FLOAT02\(\leq 25.0\)).

The alert reader may have noticed a discrepancy between the flattened form
of the constraints in Defining Subexpressions and FlatZinc constraint form. In
the latter there is no inequality constraint. Since unary inequalities can
be fully represented by the bounds of a variable, the inequality forces the
lower bound of FLOAT05 to be 25.0 and is then redundant. The final
flattened form of the model of Listing 2.8.1 is:

MiniZinc flattens minimization or maximization
objectives just like constraints. The objective
expression is flattened and a variable is created for it, just as for other
expressions. In the FlatZinc output the solve item is always on a single
variable. See Let Expressions for an example.

where \(a_i\) are integer or floating point constants, and
\(x_i\) are integer or floating point variables.
They are highly expressive, and are the only class of constraint supported
by (integer) linear programming constraint solvers.
The translator from MiniZinc to FlatZinc tries to create linear
constraints, rather than break up linear constraints into many
subexpressions.

Flattening creates linear expressions as a single unit rather than building
intermediate variables for each subexpression. It also simplfies the linear
expression created. Extracting the linear expression from the constraints
leads to

var0..80:INT01;constraint4*x+z+INT01<=23;constraintINT01=x*z;

Notice how the nonlinear expression\(x \times z\) is extracted as a
new subexpression and given a name, while the remaining terms are collected
together so that each variable appears exactly once (and indeed variable \(y\)
whose terms cancel is eliminated).

which is [16,3,26,5] and then calculates the minimum as 3.
It then builds the same array for p=2 which is
[20,13,3,6] and calculates the minimum as 3. It then constructs the
array [3,3] and calculates the maximum as 3.
There is no representation of mproducts in the output FlatZinc,
this evaluation is simply used to replace mproducts by the
calculated value 3.

The most common form of aggregate expression in a constraint model is
forall. Forall expressions are unrolled into multiple constraints.

which arises from the SEND-MORE-MONEY example of Listing 2.2.4
using a default decomposition for alldifferent.
The forall expression creates a constraint for each \(i, j\) pair which meet
the requirement \(i < j\), thus creating

One dimensional arrays
in MiniZinc can have arbitrary indices as long as they are
contiguous integers. In FlatZinc all arrays are indexed from 1..l
where l is the length of the array. This means that array lookups need to
be translated to the FlatZinc view of indices.

Consider the following MiniZinc model for balancing a seesaw
of length 2*l2,
with a child of weight cw kg using exactly m 1kg weights.

int:cw;% child weightint:l2;% half seesaw lengthint:m;% number of 1kg weightarray[-l2..l2]ofvar0..max(m,cw):w;% weight at each pointvar-l2..l2:p;% position of childconstraintsum(iin-l2..l2)(i*w[i])=0;% balanceconstraintsum(iin-l2..l2)(w[i])=m+cw;% all weights usedconstraintw[p]=cw;% child is at position psolvesatisfy;

But FlatZinc insists that the w array starts at index 1.
This means we need to rewrite all the array accesses to use the new index
value. For fixed array lookups this is easy, for variable array lookups we
may need to create a new variable. The result for the equations above is

Multidimensional arrays are supported by MiniZinc, but only single
dimension arrays are supported by FlatZinc (at present).
This means that multidimensional arrays must be mapped to single dimension
arrays, and multidimensional array access must be mapped to single dimension
array access.

setofint:HEIGHT=0..h;setofint:CHEIGHT=1..h-1;setofint:WIDTH=0..w;setofint:CWIDTH=1..w-1;array[HEIGHT,WIDTH]ofvarfloat:t;% temperature at point (i,j)varfloat:left;% left edge temperaturevarfloat:right;% right edge temperaturevarfloat:top;% top edge temperaturevarfloat:bottom;% bottom edge temperature% equation% Laplace equation: each internal temp. is average of its neighboursconstraintforall(iinCHEIGHT,jinCWIDTH)(4.0*t[i,j]=t[i-1,j]+t[i,j-1]+t[i+1,j]+t[i,j+1]);

Assuming w=4 and h=4 this creates the constraints

array[0..4,0..4]ofvarfloat:t;% temperature at point (i,j)constraint4.0*t[1,1]=t[0,1]+t[1,0]+t[2,1]+t[1,2];constraint4.0*t[1,2]=t[0,2]+t[1,1]+t[2,2]+t[1,3];constraint4.0*t[1,3]=t[0,3]+t[1,2]+t[2,3]+t[1,4];constraint4.0*t[2,1]=t[1,1]+t[2,0]+t[3,1]+t[2,2];constraint4.0*t[2,2]=t[1,2]+t[2,1]+t[3,2]+t[2,3];constraint4.0*t[2,3]=t[1,3]+t[2,2]+t[3,3]+t[2,4];constraint4.0*t[3,1]=t[2,1]+t[3,0]+t[4,1]+t[3,2];constraint4.0*t[3,2]=t[2,2]+t[3,1]+t[4,2]+t[3,3];constraint4.0*t[3,3]=t[2,3]+t[3,2]+t[4,3]+t[3,4];

The 2 dimensional array of 25 elements is converted to a one dimensional
array and the indices are changed accordingly: so index [i,j] becomes
[i*5+j+1].

FlatZinc models involve only variables and parameter declarations
and a series of primitive constraints. Hence when we model in MiniZinc
with Boolean connectives other than conjunction, something has to be done.
The core approach to handling complex formulae that use
connectives other than conjunction is by
reification.
Reifying a constraint \(c\) createsa new constraint equivalent to \(b \leftrightarrow c\)
where the Boolean variable \(b\) is true
if the constraint holds and false if it doesn’t hold.

Once we have the capability to reify constraints the treatment of
complex formulae is not different from arithmetic expressions. We create a
name for each subexpression and a flat constraint to constrain the name to
take the value of its subexpression.

Consider the following constraint expression that occurs in the jobshop
scheduling example of Listing 2.2.8.

constraint%% ensure no overlap of tasksforall(jin1..tasks)(forall(i,kin1..jobswherei<k)(s[i,j]+d[i,j]<=s[k,j]\/s[k,j]+d[k,j]<=s[i,j]));

The int_lin_le_reif is the reified form of the linear constraint
int_lin_le.

Most FlatZinc primitive constraints \(p(\bar{x})\) have a reified form
\(\mathit{p\_reif}(\bar{x},b)\) which takes an additional final argument \(b\)
and defines the constraint \(b \leftrightarrow p(\bar{x})\).
FlatZinc primitive constraints which define functional relationships,
like int_plus and int_plus, do
not need to support reification. Instead, the equality with the result of the function is reified.

Another important use of reification arises when we use the coercion
function bool2int (either explicitly, or implicitly by using a Boolean
expression as an integer expression). Flattening creates a Boolean
variable to hold the value of the Boolean expression argument, as well as an
integer variable (restricted to 0..1) to hold this value.

Predicate calls \(p(\bar{t})\)
are flattened by first constructing variables \(v_i\) for
each argument terms \(t_i\).
If the predicate has no definition we simply use a call to the predicate
with the constructed arguments: \(p(\bar{v})\).
If the predicate has a definition \(p(\bar{x}) = \phi(\bar{x})\)
then we replace the predicate call \(p(\bar{t})\)
with the body of the predicate with the formal arguments replaced by the
argument variables, that is \(\phi(\bar{v})\).
Note that if a predicate call \(p(\bar{t})\)
appears in a reified position and it has no definition, we check for the
existence of a reified version of the predicate \(\mathit{p\_reif}(\bar{x},b)\) in which
case we use that.

Consider the alldifferent constraint in the
SEND-MORE-MONEY example of Listing 2.2.4

constraintalldifferent([S,E,N,D,M,O,R,Y]);

If the solver has a builtin alldifferent we simply construct a new variable
for the argument, and replace it in the call.

array[1..8]ofvar0..9:v=[S,E,N,D,M,O,R,Y];constraintalldifferent(v);

Notice that bounds analysis attempts to find tight bounds on the new array
variable. The reason for constructing the array argument is if we use the
same array twice the FlatZinc solver does not have to construct it twice.
In this case since it is not used twice a later stage of the translation
will replace v by its definition.

What if the solver uses the default definition of alldifferent?
Then the variable v is defined as usual, and the predicate call is
replaced by a renamed copy where v replaces the formal argument x.
The resulting code is

Let expressions are a powerful facility of MiniZinc to introduce new
variables. This is useful for creating common sub expressions, and for
defining local variables for predicates.
During flattening let expressions are translated to variable and constraint
declarations. The relational semantics of MiniZinc means that these
constraints must appear as if conjoined
in the first enclosing Boolean expression.

A key feature of let expressions is that each time they are used they
create new variables.

Note that if we know that the equation defining a variable definition
cannot fail we can extract it to the top level. This will usually make
solving substantially faster.

For the example above the constraint y=x-1 can fail since the domain
of y is not big enough for all possible values of x-1. But the
constraint z=x*y cannot (indeed bounds analysis will give z
bounds big enough to hold all possible values of x*y).
A better flattening will give

Currently the MiniZinc compiler does this by always defining the declared
bounds of an
introduced variable to be big enough for its defining equation to always
hold and then adding bounds constraints in the correct context for the let
expression. On the example above this results in

This translation leads to more efficient solving since the possibly
complex calculation of the let variable is not reified.

Another reason for this approach is that it also works when introduced variables
appear in negative contexts (as long as they have a definition).
Consider the following example similar to the previous one

gives answers for all possible values of x, whereas the original
constraint removes the possibility that x=4.

The treatment of constraint items in let expressions is analogous
to defined variables. One can think of a constraint as equivalent to
defining a new Boolean variable. The definitions of the new Boolean variables
are extractted to the top level, and the Boolean remains in the correct
context.