.. _sec-flattening: FlatZinc and Flattening ======================= .. \pjs{Maybe show the toolset at this point?} 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 :math:`\bar{exists} c_1 \wedge \cdots \wedge c_m` or optimization problems of the form :math:`\text{minimize } z \text{ subject to } c_1 \wedge \cdots \wedge c_m`, where :math:`c_i` are primitive constraints and :math:`z` is an integer or float expression in a restricted form. .. index:: single: minizinc -c 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: .. code-block:: bash 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. Flattening Expressions ---------------------- 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: .. literalinclude:: examples/cnonoverlap.mzn :language: minizinc :caption: Modelling non overlap of two circles (:download:`cnonoverlap.mzn `). :name: fig-nonoverlap Simplification and Evaluation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Given the data file .. code-block:: minizinc width = 10.0; height = 8.0; r1 = 2.0; r2 = 3.0; 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 :numref:`fig-nonoverlap` become .. literalinclude:: examples/cnonoverlap.fzn :language: minizinc :start-after: % Variables :end-before: % .. _sec-flat-sub: Defining Subexpressions ~~~~~~~~~~~~~~~~~~~~~~~ Now no constraint solver directly handles complex constraint expressions like the one in :numref:`fig-nonoverlap`. 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. :mzn:`(x1 - x2)` is a subexpression, if we name if :mzn:`FLOAT01` we can define it as :mzn:`constraint FLOAT01 = x1 - x2;` Notice that this expression occurs twice in the model. We only need to construct the value once, we can then reuse it. This is called *common subexpression elimination*. The subexpression :mzn:`(x1 - x2)*(x1 - x2)` can be named :mzn:`FLOAT02` and we can define it as :mzn:`constraint FLOAT02 = FLOAT01 * FLOAT01;` We can similarly name :mzn:`constraint FLOAT03 = y1 - y2;` and :mzn:`constraint FLOAT04 = FLOAT03 * FLOAT03;` and finally :mzn:`constraint FLOAT05 = FLOAT02 * FLOAT04;`. The inequality constraint itself becomes :mzn:`constraint FLOAT05 >= 25.0;` since :mzn:`(r1+r2)*(r1 + r2)` is calculated as :mzn:`25.0`. The flattened constraint is hence .. code-block:: minizinc constraint FLOAT01 = x1 - x2; constraint FLOAT02 = FLOAT01 * FLOAT01; constraint FLOAT03 = y1 - y2; constraint FLOAT04 = FLOAT03 * FLOAT03; constraint FLOAT05 = FLOAT02 * FLOAT04; constraint FLOAT05 >= 25.0 .. _sec-flat-fzn: FlatZinc constraint form ~~~~~~~~~~~~~~~~~~~~~~~~ Flattening as its final step converts the form of the constraint to a standard FlatZinc form which is always :math:`p(a_1, \ldots, a_n)` where :mzn:`p` is the name of the primitive constraint and :math:`a_1, \ldots, a_n` are the arguments. FlatZinc tries to use a minimum of different constraint forms so for example the constraint :mzn:`FLOAT01 = x1 - x2` is first rewritten to :mzn:`FLOAT01 + x2 = x1` and then output using the :mzn:`float_plus` primitive constraint. The resulting constraint form is as follows: .. literalinclude:: examples/cnonoverlap.fzn :language: minizinc :start-after: % Constraints :end-before: % Bounds analysis ~~~~~~~~~~~~~~~ We are still missing one thing, the declarations for the introduced variables :mzn:`FLOAT01`, ..., :mzn:`FLOAT05`. While these could just be declared as :mzn:`var float`, 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 :mzn:`FLOAT01 = x1 - x2` and :math:`2.0 \leq` :mzn:`x1` :math:`\leq 8.0` and :math:`3.0 \leq` :mzn:`x2` :math:`\leq 7.0` then we can see that :math:`-5.0 \leq` :mzn:`FLOAT0` :math:`\leq 5.0`. Given this information we can see that :math:`-25.0 \leq` :mzn:`FLOAT02` :math:`\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 :math:`0.0 \leq` :mzn:`FLOAT02` :math:`\leq 25.0`). The alert reader may have noticed a discrepancy between the flattened form of the constraints in :ref:`sec-flat-sub` and :ref:`sec-flat-fzn`. 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 :mzn:`FLOAT05` to be :mzn:`25.0` and is then redundant. The final flattened form of the model of :numref:`fig-nonoverlap` is: .. literalinclude:: examples/cnonoverlap.fzn :language: minizinc Objectives ~~~~~~~~~~ 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 :ref:`sec-let` for an example. .. \pjs{Do we need an example here?} Linear Expressions ------------------ One of the most important form of constraints, widely used for modelling, are linear constraints of the form .. math:: a_1 x_1 + \cdots + a_n x_n \begin{array}[c]{c} = \\ \leq \\ < \end{array} a_0 where :math:`a_i` are integer or floating point constants, and :math:`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. .. \pjs{Maybe use the equation from SEND-MORE-MONEY instead?} .. literalinclude:: examples/linear.mzn :language: minizinc :caption: A MiniZinc model to illustrate linear constraint flattening (:download:`linear.mzn `). :name: fig-lflat Consider the model shown in :numref:`fig-lflat`. Rather than create variables for all the subexpressions :math:`3*x`, :math:`3*x - y`, :math:`x * z`, :math:`3*x - y + x*z`, :math:`x + y + z`, :math:`d * (x + y + z)`, :math:`19 + d * (x + y + z)`, and :math:`19 + d * (x + y + z) - 4*d` translation will attempt to create a large linear constraint which captures as much as possible of the constraint in a single FlatZinc constraint. Flattening creates linear expressions as a single unit rather than building intermediate variables for each subexpression. It also simplifies the linear expression created. Extracting the linear expression from the constraints leads to .. code-block:: minizinc var 0..80: INT01; constraint 4*x + z + INT01 <= 23; constraint INT01 = x * z; Notice how the *nonlinear expression* :math:`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 :math:`y` whose terms cancel is eliminated). Finally each constraint is written to FlatZinc form obtaining: .. code-block:: minizinc var 0..80: INT01; constraint int_lin_le([1,4,1],[INT01,x,z],23); constraint int_times(x,z,INT01); .. _sec-unroll: Unrolling Expressions --------------------- Most models require creating a number of constraints which is dependent on the input data. MiniZinc supports these models with array types, list and set comprehensions, and aggregation functions. Consider the following aggregate expression from the production scheduling example of :numref:`ex-prod-planning`. .. code-block:: minizinc int: mproducts = max (p in Products) (min (r in Resources where consumption[p,r] > 0) (capacity[r] div consumption[p,r])); Since this uses generator call syntax we can rewrite it to equivalent form which is processed by the compiler: .. code-block:: minizinc int: mproducts = max([ min [ capacity[r] div consumption[p,r] | r in Resources where consumption[p,r] > 0]) | p in Products]); Given the data .. code-block:: minizinc nproducts = 2; nresources = 5; capacity = [4000, 6, 2000, 500, 500]; consumption= [| 250, 2, 75, 100, 0, | 200, 0, 150, 150, 75 |]; this first builds the array for :mzn:`p = 1` .. code-block:: minizinc [ capacity[r] div consumption[p,r] | r in 1..5 where consumption[p,r] > 0] which is :mzn:`[16, 3, 26, 5]` and then calculates the minimum as 3. It then builds the same array for :mzn:`p = 2` which is :mzn:`[20, 13, 3, 6]` and calculates the minimum as 3. It then constructs the array :mzn:`[3, 3]` and calculates the maximum as 3. There is no representation of :mzn:`mproducts` in the output FlatZinc, this evaluation is simply used to replace :mzn:`mproducts` by the calculated value 3. The most common form of aggregate expression in a constraint model is :mzn:`forall`. Forall expressions are unrolled into multiple constraints. Consider the MiniZinc fragment .. code-block:: minizinc array[1..8] of var 0..9: v = [S,E,N,D,M,O,R,Y]; constraint forall(i,j in 1..8 where i < j)(v[i] != v[j]) which arises from the SEND-MORE-MONEY example of :numref:`ex-smm` using a default decomposition for :mzn:`alldifferent`. The :mzn:`forall` expression creates a constraint for each :math:`i, j` pair which meet the requirement :math:`i < j`, thus creating .. code-block:: minizinc constraint v[1] != v[2]; % S != E constraint v[1] != v[3]; % S != N ... constraint v[1] != v[8]; % S != Y constraint v[2] != v[3]; % E != N ... constraint v[7] != v[8]; % R != Y In FlatZinc form this is .. code-block:: minizinc constraint int_neq(S,E); constraint int_neq(S,N); ... constraint int_neq(S,Y); constraint int_neq(E,N); ... constraint int_neq(R,Y); Notice how the temporary array variables :mzn:`v[i]` are replaced by the original variables in the output FlatZinc. Arrays ------ One dimensional arrays in MiniZinc can have arbitrary indices as long as they are contiguous integers. In FlatZinc all arrays are indexed from :mzn:`1..l` where :mzn:`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 :mzn:`2 * l2`, with a child of weight :mzn:`cw` kg using exactly :mzn:`m` 1kg weights. .. code-block:: minizinc int: cw; % child weight int: l2; % half seesaw length int: m; % number of 1kg weight array[-l2..l2] of var 0..max(m,cw): w; % weight at each point var -l2..l2: p; % position of child constraint sum(i in -l2..l2)(i * w[i]) = 0; % balance constraint sum(i in -l2..l2)(w[i]) = m + cw; % all weights used constraint w[p] = cw; % child is at position p solve satisfy; Given :mzn:`cw = 2`, :mzn:`l2 = 2`, and :mzn:`m = 3` the unrolling produces the constraints .. code-block:: minizinc array[-2..2] of var 0..3: w; var -2..2: p constraint -2*w[-2] + -1*w[-1] + 0*w[0] + 1*w[1] + 2*w[2] = 0; constraint w[-2] + w[-1] + w[0] + w[1] + w[2] = 5; constraint w[p] = 2; But FlatZinc insists that the :mzn:`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 .. code-block:: minizinc array[1..5] of var 0..3: w; var -2..2: p var 1..5: INT01; constraint -2*w[1] + -1*w[2] + 0*w[3] + 1*w[4] + 2*w[5] = 0; constraint w[1] + w[2] + w[3] + w[4] + w[5] = 5; constraint w[INT01] = 2; constraint INT01 = p + 3; Finally we rewrite the constraints into FlatZinc form. Note how the variable array index lookup is mapped to :mzn:`array_var_int_element`. .. code-block:: minizinc array[1..5] of var 0..3: w; var -2..2: p var 1..5: INT01; constraint int_lin_eq([2, 1, -1, -2], [w[1], w[2], w[4], w[5]], 0); constraint int_lin_eq([1, 1, 1, 1, 1], [w[1],w[2],w[3],w[4],w[5]], 5); constraint array_var_int_element(INT01, w, 2); constraint int_lin_eq([-1, 1], [INT01, p], -3); 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. Consider the Laplace equation constraints defined for a finite element plate model in :numref:`ex-laplace`: .. literalinclude:: examples/laplace.mzn :language: minizinc :start-after: % arraydec :end-before: % sides Assuming :mzn:`w = 4` and :mzn:`h = 4` this creates the constraints .. code-block:: minizinc array[0..4,0..4] of var float: t; % temperature at point (i,j) constraint 4.0*t[1,1] = t[0,1] + t[1,0] + t[2,1] + t[1,2]; constraint 4.0*t[1,2] = t[0,2] + t[1,1] + t[2,2] + t[1,3]; constraint 4.0*t[1,3] = t[0,3] + t[1,2] + t[2,3] + t[1,4]; constraint 4.0*t[2,1] = t[1,1] + t[2,0] + t[3,1] + t[2,2]; constraint 4.0*t[2,2] = t[1,2] + t[2,1] + t[3,2] + t[2,3]; constraint 4.0*t[2,3] = t[1,3] + t[2,2] + t[3,3] + t[2,4]; constraint 4.0*t[3,1] = t[2,1] + t[3,0] + t[4,1] + t[3,2]; constraint 4.0*t[3,2] = t[2,2] + t[3,1] + t[4,2] + t[3,3]; constraint 4.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 :mzn:`[i,j]` becomes :mzn:`[i * 5 + j + 1]`. .. code-block:: minizinc array [1..25] of var float: t; constraint 4.0*t[7] = t[2] + t[6] + t[12] + t[8]; constraint 4.0*t[8] = t[3] + t[7] + t[13] + t[9]; constraint 4.0*t[9] = t[4] + t[8] + t[14] + t[10]; constraint 4.0*t[12] = t[7] + t[11] + t[17] + t[13]; constraint 4.0*t[13] = t[8] + t[12] + t[18] + t[14]; constraint 4.0*t[14] = t[9] + t[13] + t[19] + t[15]; constraint 4.0*t[17] = t[12] + t[16] + t[22] + t[18]; constraint 4.0*t[18] = t[13] + t[17] + t[23] + t[19]; constraint 4.0*t[19] = t[14] + t[18] + t[24] + t[20]; Reification ----------- .. index:: single: reification 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 :math:`c` creates a new constraint equivalent to :math:`b \leftrightarrow c` where the Boolean variable :math:`b` is :mzn:`true` if the constraint holds and :mzn:`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 :numref:`ex-jobshop`. .. code-block:: minizinc constraint %% ensure no overlap of tasks forall(j in 1..tasks) ( forall(i,k in 1..jobs where i < k) ( s[i,j] + d[i,j] <= s[k,j] \/ s[k,j] + d[k,j] <= s[i,j] ) ); Given the data file .. code-block:: minizinc jobs = 2; tasks = 3; d = [| 5, 3, 4 | 2, 6, 3 |] then the unrolling creates .. code-block:: minizinc constraint s[1,1] + 5 <= s[2,1] \/ s[2,1] + 2 <= s[1,1]; constraint s[1,2] + 3 <= s[2,2] \/ s[2,2] + 6 <= s[1,2]; constraint s[1,3] + 4 <= s[2,3] \/ s[2,3] + 3 <= s[1,3]; Reification of the constraints that appear in the disjunction creates new Boolean variables to define the values of each expression. .. code-block:: minizinc array[1..2,1..3] of var 0..23: s; constraint BOOL01 <-> s[1,1] + 5 <= s[2,1]; constraint BOOL02 <-> s[2,1] + 2 <= s[1,1]; constraint BOOL03 <-> s[1,2] + 3 <= s[2,2]; constraint BOOL04 <-> s[2,2] + 6 <= s[1,2]; constraint BOOL05 <-> s[1,3] + 4 <= s[2,3]; constraint BOOL06 <-> s[2,3] + 3 <= s[1,3]; constraint BOOL01 \/ BOOL02; constraint BOOL03 \/ BOOL04; constraint BOOL05 \/ BOOL06; Each primitive constraint can now be mapped to the FlatZinc form. Note how the two dimensional array :mzn:`s` is mapped to a one dimensional form. .. code-block:: minizinc array[1..6] of var 0..23: s; constraint int_lin_le_reif([1, -1], [s[1], s[4]], -5, BOOL01); constraint int_lin_le_reif([-1, 1], [s[1], s[4]], -2, BOOL02); constraint int_lin_le_reif([1, -1], [s[2], s[5]], -3, BOOL03); constraint int_lin_le_reif([-1, 1], [s[2], s[5]], -6, BOOL04); constraint int_lin_le_reif([1, -1], [s[3], s[6]], -4, BOOL05); constraint int_lin_le_reif([-1, 1], [s[3], s[6]], -3, BOOL06); constraint array_bool_or([BOOL01, BOOL02], true); constraint array_bool_or([BOOL03, BOOL04], true); constraint array_bool_or([BOOL05, BOOL06], true); The :mzn:`int_lin_le_reif` is the reified form of the linear constraint :mzn:`int_lin_le`. Most FlatZinc primitive constraints :math:`p(\bar{x})` have a reified form :math:`\mathit{p\_reif}(\bar{x},b)` which takes an additional final argument :math:`b` and defines the constraint :math:`b \leftrightarrow p(\bar{x})`. FlatZinc primitive constraints which define functional relationships, like :mzn:`int_plus` and :mzn:`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 :mzn:`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 :mzn:`0..1`) to hold this value. Consider the magic series problem of :numref:`ex-magic-series`. .. literalinclude:: examples/magic-series.mzn :language: minizinc :end-before: solve satisfy Given :mzn:`n = 2` the unrolling creates .. code-block:: minizinc constraint s[0] = bool2int(s[0] = 0) + bool2int(s[1] = 0); constraint s[1] = bool2int(s[0] = 1) + bool2int(s[1] = 1); and flattening creates .. code-block:: minizinc constraint BOOL01 <-> s[0] = 0; constraint BOOL03 <-> s[1] = 0; constraint BOOL05 <-> s[0] = 1; constraint BOOL07 <-> s[1] = 1; constraint INT02 = bool2int(BOOL01); constraint INT04 = bool2int(BOOL03); constraint INT06 = bool2int(BOOL05); constraint INT08 = bool2int(BOOL07); constraint s[0] = INT02 + INT04; constraint s[1] = INT06 + INT08; The final FlatZinc form is .. code-block:: minizinc var bool: BOOL01; var bool: BOOL03; var bool: BOOL05; var bool: BOOL07; var 0..1: INT02; var 0..1: INT04; var 0..1: INT06; var 0..1: INT08; array [1..2] of var 0..2: s; constraint int_eq_reif(s[1], 0, BOOL01); constraint int_eq_reif(s[2], 0, BOOL03); constraint int_eq_reif(s[1], 1, BOOL05); constraint int_eq_reif(s[2], 1, BOOL07); constraint bool2int(BOOL01, INT02); constraint bool2int(BOOL03, INT04); constraint bool2int(BOOL05, INT06); constraint bool2int(BOOL07, INT08); constraint int_lin_eq([-1, -1, 1], [INT02, INT04, s[1]], 0); constraint int_lin_eq([-1, -1, 1], [INT06, INT08, s[2]], 0); solve satisfy; Predicates ---------- An important factor in the support for MiniZinc by many different solvers is that global constraints (and indeed FlatZinc constraints) can be specialized for the particular solver. Each solver will either specify a predicate without a definition, or with a definition. For example a solver that has a builtin global :mzn:`alldifferent` predicate, will include the definition .. code-block:: minizinc predicate alldifferent(array[int] of var int:x); in its globals library, while a solver using the default decomposition will have the definition .. code-block:: minizinc predicate alldifferent(array[int] of var int:x) = forall(i,j in index_set(x) where i < j)(x[i] != x[j]); Predicate calls :math:`p(\bar{t})` are flattened by first constructing variables :math:`v_i` for each argument terms :math:`t_i`. If the predicate has no definition we simply use a call to the predicate with the constructed arguments: :math:`p(\bar{v})`. If the predicate has a definition :math:`p(\bar{x}) = \phi(\bar{x})` then we replace the predicate call :math:`p(\bar{t})` with the body of the predicate with the formal arguments replaced by the argument variables, that is :math:`\phi(\bar{v})`. Note that if a predicate call :math:`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 :math:`\mathit{p\_reif}(\bar{x},b)` in which case we use that. Consider the :mzn:`alldifferent` constraint in the SEND-MORE-MONEY example of :numref:`ex-smm` .. code-block:: minizinc constraint alldifferent([S,E,N,D,M,O,R,Y]); If the solver has a builtin :mzn:`alldifferent` we simply construct a new variable for the argument, and replace it in the call. .. code-block:: minizinc array[1..8] of var 0..9: v = [S,E,N,D,M,O,R,Y]; constraint alldifferent(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 :mzn:`v` by its definition. What if the solver uses the default definition of :mzn:`alldifferent`? Then the variable :mzn:`v` is defined as usual, and the predicate call is replaced by a renamed copy where :mzn:`v` replaces the formal argument :mzn:`x`. The resulting code is .. code-block:: minizinc array[1..8] of var 0..9: v = [S,E,N,D,M,O,R,Y]; constraint forall(i,j in 1..8 where i < j)(v[i] != v[j]) which we examined in :ref:`sec-unroll`. Consider the following constraint, where :mzn:`alldifferent` appears in a reified position. .. code-block:: minizinc constraint alldifferent([A,B,C]) \/ alldifferent([B,C,D]); If the solver has a reified form of :mzn:`alldifferent` this will be flattened to .. code-block:: minizinc constraint alldifferent_reif([A,B,C],BOOL01); constraint alldifferent_reif([B,C,D],BOOL02); constraint array_bool_or([BOOL01,BOOL02],true); Using the default decomposition, the predicate replacement will first create .. code-block:: minizinc array[1..3] of var int: v1 = [A,B,C]; array[1..3] of var int: v2 = [B,C,D]; constraint forall(i,j in 1..3 where i p2 <= 6) } in same * abs(p1 - p2) + (1-same) * (abs(13 - p1 - p2) + 1)); For conciseness we assume only the first two Hatreds, so .. code-block:: minizinc set of int: Hatreds = 1..2; array[Hatreds] of Guests: h1 = [groom, carol]; array[Hatreds] of Guests: h2 = [clara, bestman]; The first step of flattening is to unroll the :mzn:`sum` expression, giving (we keep the guest names and parameter :mzn:`Seats` for clarity only, in reality they would be replaced by their definition): .. code-block:: minizinc solve maximize (let { var Seats: p1 = pos[groom], var Seats: p2 = pos[clara], var 0..1: same = bool2int(p1 <= 6 <-> p2 <= 6) } in same * abs(p1 - p2) + (1-same) * (abs(13 - p1 - p2) + 1)) + (let { var Seats: p1 = pos[carol], var Seats: p2 = pos[bestman], var 0..1: same = bool2int(p1 <= 6 <-> p2 <= 6) } in same * abs(p1 - p2) + (1-same) * (abs(13 - p1 - p2) + 1)); Next each new variable in a let expression is renamed to be distinct .. code-block:: minizinc solve maximize (let { var Seats: p11 = pos[groom], var Seats: p21 = pos[clara], var 0..1: same1 = bool2int(p11 <= 6 <-> p21 <= 6) } in same1 * abs(p11 - p21) + (1-same1) * (abs(13 - p11 - p21) + 1)) + (let { var Seats: p12 = pos[carol], var Seats: p22 = pos[bestman], var 0..1: same2 = bool2int(p12 <= 6 <-> p22 <= 6) } in same2 * abs(p12 - p22) + (1-same2) * (abs(13 - p12 - p22) + 1)); Variables in the let expression are extracted to the top level and defining constraints are extracted to the correct level (which in this case is also the top level). .. code-block:: minizinc var Seats: p11; var Seats: p21; var 0..1: same1; constraint p12 = pos[clara]; constraint p11 = pos[groom]; constraint same1 = bool2int(p11 <= 6 <-> p21 <= 6); var Seats p12; var Seats p22; var 0..1: same2; constraint p12 = pos[carol]; constraint p22 = pos[bestman]; constraint same2 = bool2int(p12 <= 6 <-> p22 <= 6) } in solve maximize same1 * abs(p11 - p21) + (1-same1) * (abs(13 - p11 - p21) + 1)) + same2 * abs(p12 - p22) + (1-same2) * (abs(13 - p12 - p22) + 1)); Now we have constructed equivalent MiniZinc code without the use of let expressions and the flattening can proceed as usual. As an illustration of let expressions that do not appear at the top level consider the following model .. code-block:: minizinc var 0..9: x; constraint x >= 1 -> let { var 2..9: y = x - 1 } in y + (let { var int: z = x * y } in z * z) < 14; We extract the variable definitions to the top level and the constraints to the first enclosing Boolean context, which here is the right hand side of the implication. .. code-block:: minizinc var 0..9: x; var 2..9: y; var int: z; constraint x >= 1 -> (y = x - 1 /\ z = x * y /\ y + z * z < 14); 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 :mzn:`y = x - 1` can fail since the domain of :mzn:`y` is not big enough for all possible values of :mzn:`x - 1`. But the constraint :mzn:`z = x * y` cannot (indeed bounds analysis will give :mzn:`z` bounds big enough to hold all possible values of :mzn:`x * y`). A better flattening will give .. code-block:: minizinc var 0..9: x; var 2..9: y; var int: z; constraint z = x * y; constraint x >= 1 -> (y = x - 1 /\ y + z * z < 14); 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 .. code-block:: minizinc var 0..9: x; var -1..8: y; var -9..72: z; constraint y = x - 1; constraint z = x * y; constraint x >= 1 -> (y >= 2 /\ y + z * z < 14); 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 .. code-block:: minizinc var 0..9: x; constraint (let { var 2..9: y = x - 1 } in y + (let { var int: z = x * y } in z * z) > 14) -> x >= 5; The let expressions appear in a negated context, but each introduced variable is defined. The flattened code is .. code-block:: minizinc var 0..9: x; var -1..8: y; var -9..72: z; constraint y = x - 1; constraint z = x * y; constraint (y >= 2 /\ y + z * z > 14) -> x >= 5; Note the analog to the simple approach to let elimination does not give a correct translation: .. code-block:: minizinc var 0..9: x; var 2..9: y; var int: z; constraint (y = x - 1 /\ z = x * y /\ y + z * z > 14) -> x >= 5; gives answers for all possible values of :mzn:`x`, whereas the original constraint removes the possibility that :mzn:`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 extracted to the top level, and the Boolean remains in the correct context. .. code-block:: minizinc constraint z > 1 -> let { var int: y, constraint (x >= 0) -> y = x, constraint (x < 0) -> y = -x } in y * (y - 2) >= z; is treated like .. code-block:: minizinc constraint z > 1 -> let { var int: y, var bool: b1 = ((x >= 0) -> y = x), var bool: b2 = ((x < 0) -> y = -x), constraint b1 /\ b2 } in y * (y - 2) >= z; and flattens to .. code-block:: minizinc constraint b1 = ((x >= 0) -> y = x); constraint b2 = ((x < 0) -> y = -x); constraint z > 1 -> (b1 /\ b2 /\ y * (y - 2) >= z);