.. _sec-flattening: FlatZinc y Flattening ======================= .. \pjs{Maybe show the toolset at this point?} Los solucionadores de restricciones no son compatibles directamente con los modelos MiniZinc. Sino que para ejecutar un modelo MiniZinc se debe de traducir en un subconjunto simple de MiniZinc llamado FlatZinc. FlatZinc refleja el hecho de que la mayoría de los solucionadores de restricciones solo resuelven problemas de satisfacción de la forma :math:`\bar{exists } c_1 \wedge \cdots \wedge c_m` o problemas de optimización de la forma :math:`\text {minimize} z \text{ sujeto a } c_1 \wedge \cdots \wedge c_m`, donde :math:`c_i` son restricciones primitivas y :math:`z` es un entero o expresión flotante en una forma restringida. .. index:: single: mzn2fzn La herramienta ``mzn2fzn`` toma un modelo de MiniZinc y los archivos de datos, posteriormente crea un modelo FlatZinc aplanado que es equivalente al modelo MiniZinc con los datos dados, y que aparece en la forma restringida discutida anteriormente. Normalmente, la construcción de un modelo de FlatZinc que se envía a un solucionador está oculta para el usuario, pero puede ver el resultado de un modelo ``model.mzn`` con sus datos ``data.dzn`` de la siguiente manera: .. code-block:: bash mzn2fzn model.mzn data.dzn El cual crea un modelo FlatZinc llamado ``model.fzn``. En este capítulo, exploramos el proceso de traducción de MiniZinc a FlatZinc. Flattening Expressiones ----------------------- Las restricciones del solucionador subyacente significan que las expresiones complejas en MiniZinc deben *flattened* (aplanarse o aplanar) para usar solo conjunciones de restricciones primitivas que no contienen por sí mismas términos estructurados. Considere el siguiente modelo para asegurarse de que dos círculos en un recuadro rectangular no se superpongan: .. literalinclude:: examples/cnonoverlap_es.mzn :language: minizinc :caption: Modelado de no superposición de dos círculos (:download:`cnonoverlap_es.mzn `). :name: fig-nonoverlap Simplificación y Evaluación ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Dado el siguiente archivo de datos: .. code-block:: minizinc width = 10.0; height = 8.0; r1 = 2.0; r2 = 3.0; La traducción a FlatZinc primero simplifica el modelo reemplazando todos los parámetros por sus valores y evaluando cualquier expresión fija. Después de esta simplificación, los valores de los parámetros ya no son necesarios. Una excepción a esto, son las grandes matrices de valores paramétricos. Si se usan más de una vez, el parámetro se conserva para evitar la duplicación expresiones grandes. Después de la simplificación, las partes de declaraciones de variables y parámetros del modelo de :numref:`fig-nonoverlap` se transforman en .. literalinclude:: examples/cnonoverlap_es.fzn :language: minizinc :start-after: % Variables :end-before: % .. _sec-flat-sub: Definiendo Subexpresiones ~~~~~~~~~~~~~~~~~~~~~~~~~ Ahora, ningún solucionador de restricciones maneja directamente expresiones de restricciones complejas como la de :numref:`fig-nonoverlap`. En cambio, cada subexpresión en la expresión es nombrada, y posteriormente creamos una restricción para construir el valor de cada expresión. Examinemos las subexpresiones de la expresión de restricción. :mzn:`(x1 - x2)` es una subexpresión, si nombramos :mzn:`FLOAT01` podemos definirlo como :mzn:`constraint FLOAT01 = x1 - x2;`. Observe que esta expresión aparece dos veces en el modelo. Solo necesitamos calcular el valor una vez, luego podemos reutilizarlo. Esto se llama *eliminación de subexpresiones comunes*. La subexpresión :mzn:`(x1 - x2) * (x1 - x2)` se puede llamar :mzn:`FLOAT02` y podemos definirla como :mzn:`constraint FLOAT02 = FLOAT01 * FLOAT01;`. Podemos nombrar de forma similar :mzn:`restricción FLOAT03 = y1 - y2;` y :mzn:`restricción FLOAT04 = FLOAT03 * FLOAT03;` y finalmente :mzn:`restricción FLOAT05 = FLOAT02 * FLOAT04;`. La restricción de desigualdad se convierte en :mzn:`restricción FLOAT05> = 25.0;` desde :mzn:`(r1 + r2) * (r1 + r2)` se calcula como :mzn:`25.0`. La restricción aplanada es por lo tanto: .. 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: Forma de restricciones FlatZinc ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ El flatening como su paso final, convierte la forma de la restricción en una forma estándar de FlatZinc que siempre es :math:`p(a_1, \ldots, a_n)`, donde :mzn:`p` es el nombre de la restricción primitiva y :math:`a_1, \ldots, a_n` son los argumentos. FlatZinc intenta usar un mínimo de diferentes formas de restricción, por ejemplo, la restricción :mzn:`FLOAT01 = x1 - x2` se reescribe primero como :mzn:`FLOAT01 + x2 = x1` y luego se genera usando la restricción primitiva:mzn:`float_plus`. La forma de restricción resultante es la siguiente: .. literalinclude:: examples/cnonoverlap_es.fzn :language: minizinc :start-after: % Restricciones :end-before: % Análisis de límites ~~~~~~~~~~~~~~~~~~~ Todavía nos falta una cosa, las declaraciones de las variables introducidas :mzn:`FLOAT01`, ..., :mzn:`FLOAT05`. Si bien estos podrían simplemente declararse como :mzn:`var float`, para facilitar la tarea del solucionador MiniZinc intenta determinar los límites superiores e inferiores de las variables recién introducidas, mediante un simple análisis de límites. Por ejemplo desde :mzn:`FLOAT01 = x1 - x2` y :math:`2.0 \leq` :mzn:`x1` :math:`\leq 8.0` y :math:`3.0 \leq` :mzn:`x2` :math:`\leq 7.0` luego podemos ver que :math:`-5.0 \leq` :mzn:`FLOAT0` :math:`\leq 5.0`. Dada esta información, podemos ver que :math:`-25.0 \leq` :mzn:`FLOAT02` :math:`\leq 25.0` (aunque tenga en cuenta que si reconociéramos que la multiplicación era en realidad una cuadratura podríamos dar mucho límites más precisos :math:`0.0 \leq` :mzn:`FLOAT02` :math:`\leq 25.0`). El lector alerta puede haber notado una discrepancia entre la forma aplanada de las restricciones en :ref:`sec-flat-sub` y :ref:`sec-flat-fzn`. En este último, no hay restricción de desigualdad. Como las desigualdades unarias pueden representarse completamente por los límites de una variable, la desigualdad fuerza al límite inferior de :mzn:`FLOAT05` a ser :mzn:`25.0` y luego es redundante. La forma aplanada final del modelo de :numref:`fig-nonoverlap` es: .. literalinclude:: examples/cnonoverlap_es.fzn :language: minizinc Objetivos ~~~~~~~~~~ MiniZinc aplana los objetivos de minimización o maximización al igual que las restricciones. La expresión objetivo se aplana y se crea una variable para ella, al igual que para otras expresiones. En la salida de FlatZinc, el elemento de resolver siempre está en una sola variable. Ver :ref:`sec-let` para un ejemplo. .. \pjs{Do we need an example here?} Expresiones lineales -------------------- Una de las restricciones más importantes, ampliamente utilizada para el modelado, son las restricciones lineales de la forma .. math:: a_1 x_1 + \cdots + a_n x_n \begin{array}[c]{c} = \\ \leq \\ < \end{array} a_0 En donde :math:`a_i` son constantes de coma flotante o entero, y :math:`x_i` son variables enteras o de coma flotante. Son altamente expresivos, y son la única clase de restricción soportada por solucionadores de restricciones de programación lineal (entero). El traductor de MiniZinc a FlatZinc intenta crear restricciones lineales, en lugar de dividir las restricciones lineales en muchas subexpresiones. .. \pjs{Maybe use the equation from SEND-MORE-MONEY instead?} .. literalinclude:: examples/linear_es.mzn :language: minizinc :caption: Un modelo MiniZinc para ilustrar el aplanamiento de restricciones lineales (:download:`linear_es.mzn `). :name: fig-lflat Considere el modelo que se muestra en :numref:`fig-lflat`. En lugar de crear variables para todas las subexpresiones :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)`, y :math:`19 + d * (x + y + z) - 4 * d` la traducción intentará crear una restricción lineal grande que capture la mayor cantidad posible de la restricción en una única restricción FlatZinc. El flatening crea expresiones lineales como una sola unidad en lugar de generar variables intermedias para cada subexpresión. También simboliza la expresión lineal creada. La extracción de la expresión lineal de las restricciones conduce a: .. code-block:: minizinc var 0..80: INT01; constraint 4*x + z + INT01 <= 23; constraint INT01 = x * z; Observe cómo la *expresión no lineal* :math:`x \times z` se extrae como una nueva subexpresión y se le da un nombre. Mientras que los términos restantes se recopilan juntos para que cada variable aparezca exactamente una vez (y de hecho variable :math:`y` cuyos términos cancelar se eliminan). Finalmente, cada restricción se escribe en la forma FlatZinc obteniendo: .. 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: Desenrollar expresiones ----------------------- La mayoría de los modelos requieren la creación de una serie de restricciones que dependen de los datos de entrada. MiniZinc admite estos modelos con tipos de matriz, listas y conjuntos de comprensiones y funciones de agregación. Considere la siguiente expresión desde el ejemplo de programación de planificación :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])); Como esto utiliza la sintaxis de llamada del generador, podemos reescribirla en forma equivalente, que es procesada por ``mzn2fzn``: .. code-block:: minizinc int: mproducts = max([ min [ capacity[r] div consumption[p,r] | r in Resources where consumption[p,r] > 0]) | p in Products]); Teniendo en cuenta los datos .. code-block:: minizinc nproducts = 2; nresources = 5; capacity = [4000, 6, 2000, 500, 500]; consumption= [| 250, 2, 75, 100, 0, | 200, 0, 150, 150, 75 |]; Esto primero construye la matriz de :mzn:`p = 1` .. code-block:: minizinc [ capacity[r] div consumption[p,r] | r in 1..5 where consumption[p,r] > 0] Que es :mzn:`[16, 3, 26, 5]` y luego calcula el mínimo como 3. Luego construye la misma matriz para :mzn:`p = 2` que es :mzn:`[20, 13, 3, 6]` y calcula el mínimo como 3. Luego construye la matriz :mzn:`[3, 3]` y calcula el máximo como 3. No hay representación de :mzn:`mproducts` en la salida FlatZinc, esta evaluación se usa simplemente para reemplazar :mzn:`mproducts` por el valor calculado 3. La forma más común de expresión agregada en un modelo de restricción es :mzn:`forall`. Todas las expresiones se desenrollan en múltiples restricciones. Considere el siguiente fragmento de MiniZinc .. 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]) Que surge del ejemplo SEND-MORE-MONEY de :numref:`ex-smm` utilizando una descomposición predeterminada para :mzn:`alldifferent`. La expresión :mzn:`forall` crea una restricción para cada par :math:`i, j` que cumple con el requisito :math:`i 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; Cada restricción primitiva ahora se puede asignar a la forma FlatZinc. Observe cómo la matriz de dos dimensiones :mzn:`s` está mapeada a una forma unidimensional. Cada restricción se puede asignar a la forma FlatZinc. Observe cómo la matriz de dos dimensiones :mzn:`s` está mapeada a una forma unidimensional. .. 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); El :mzn:`int_lin_le_reif` es la forma reificada de la restricción lineal :mzn:` int_lin_le`. La mayoría de las restricciones primitivas de FlatZinc :math:`p(\bar{x})` tiene una forma reificada :math:`\mathit{p\_reif}(\bar{x},b)` que toma un argumento final adicional :math:`b` y define la restricción :math:`b \leftrightarrow p(\bar{x})`. Las restricciones primitivas de FlatZinc que definen relaciones funcionales, como :mzn:`int_plus` y :mzn:`int_plus`, no necesitan admitir la reificación. En cambio, la igualdad con el resultado de la función se reifica. Otro uso importante de la reificación surge cuando usamos la función de coerción :mzn:`bool2int` (explícita o implícitamente usando una expresión booleana como una expresión entera). El aplanamiento crea una variable booleana para contener el valor del argumento de expresión booleana, así como una variable entera (restringida a :mzn:`0..1`) para mantener este valor. Considere el problema de la serie mágica de :numref:`ex-magic-series`. .. literalinclude:: examples/magic-series_es.mzn :language: minizinc :end-before: solve satisfy Dado :mzn:`n = 2` el desenrollado crea .. 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); y el aplanamiento crea: .. 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; El formulario final de FlatZinc es: .. 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; Predicados ---------- Un factor importante en el soporte para MiniZinc por muchos solucionadores diferentes es que las restricciones globales (y de hecho las restricciones de FlatZinc) pueden ser especializadas para el solucionador particular. Cada solucionador especificará un predicado sin una definición, o con una definición. Por ejemplo, un solucionador que tiene un predicado global integrado :mzn:`alldifferent`, incluirá la definición .. code-block:: minizinc predicate alldifferent(array[int] of var int:x); en su biblioteca global, mientras que un solucionador que usa la descomposición por defecto tendrá la definición .. 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]); Las llamadas a predicados :math:`p(\bar{t})` se aplanan construyendo primero las variables :math:`v_i` para cada argumento :math:`t_i`. Si el predicado no tiene definición, simplemente usamos una llamada al predicado con los argumentos construidos: :math:`p(\bar{v})`. Si el predicado tiene una definición :math:`p(\bar{x}) = \phi(\bar{x})` luego reemplazamos la llamada del predicado :math:`p(\bar{t})` con el cuerpo del predicado con los argumentos formales reemplazados por las variables del argumento, es decir :math:`\phi(\bar{v})`. Tenga en cuenta que si una llamada de predicado :math:`p(\bar{t})` aparece en una posición reificada y no tiene definición, verificamos la existencia de una versión reificada del predicado :math:`\mathit{p\_reif}(\bar{x},b)` en cuyo caso usamos eso. Consideremos la restricción :mzn:`alldifferent` en el ejemplo de SEND-MORE-MONEY de :numref:`ex-smm` .. code-block:: minizinc constraint alldifferent([S,E,N,D,M,O,R,Y]); Si el solucionador tiene una función incorporada :mzn:`alldifferent` simplemente construimos una nueva variable para el argumento y la reemplazamos en la llamada. .. code-block:: minizinc array[1..8] of var 0..9: v = [S,E,N,D,M,O,R,Y]; constraint alldifferent(v); Observe que el análisis de límites intenta encontrar límites estrechos en la nueva variable de matriz. La razón para construir el argumento de la matriz es si usamos la misma matriz dos veces, el solucionador FlatZinc no tiene que construirla dos veces. En este caso, dado que no se usa dos veces, una etapa posterior de la traducción reemplazará :mzn:`v` por su definición. ¿Qué ocurre si el solucionador usa la definición predeterminada de :mzn:`alldifferent`?. Entonces la variable :mzn:`v` se define como de costumbre, y la llamada del predicado se reemplaza por una copia renombrada donde :mzn:`v` reemplaza el argumento formal :mzn:`x`. El código resultante es: .. 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]) Que examinamos en :ref:`sec-unroll`. Considere la siguiente restricción, donde :mzn:`alldifferent` aparece en una posición reificada. .. code-block:: minizinc constraint alldifferent([A,B,C]) \/ alldifferent([B,C,D]); Si el solucionador tiene una forma reificada de :mzn:`alldifferent` esto será flattend a: .. code-block:: minizinc constraint alldifferent_reif([A,B,C],BOOL01); constraint alldifferent_reif([B,C,D],BOOL02); constraint array_bool_or([BOOL01,BOOL02],true); Usando la descomposición por defecto, el reemplazo del predicado creará primero: .. 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)); Por concisión asumimos solo los dos primeros Hatreds (odios), entonces: .. code-block:: minizinc set of int: Hatreds = 1..2; array[Hatreds] of Guests: h1 = [groom, carol]; array[Hatreds] of Guests: h2 = [clara, bestman]; El primer paso de flattening es desenrollar la expresión :mzn:`sum`, mantenemos los nombres y parámetros de los invitados :mzn:`Seats` solo para mayor claridad, en realidad serían reemplazados por su definición: .. 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)); A continuación, cada nueva variable en una expresión ``let`` se renombra para ser distinta .. 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)); Las variables en la expresión ``let`` se extraen al nivel superior y las restricciones de definición se extraen al nivel correcto (que en este caso también es el nivel superior). .. 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)); Ahora hemos construido un código en MiniZinc equivalente sin el uso de expresiones ``let`` y el aplanamiento puede continuar como de costumbre. Como una ilustración de las expresiones ``let`` que no aparecen en el nivel superior, considere el siguiente modelo: .. 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; Extraemos las definiciones de las variables al nivel superior y las restricciones al primer contexto booleano que lo incluye, que aquí está en el lado derecho de la implicación. .. 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); Tenga en cuenta que si sabemos que la ecuación que define una definición de variable no puede fallar, podemos extraerla al nivel superior. Esto generalmente hará que la resolución sea mucho más rápida. Para el ejemplo anterior, la restricción :mzn:`y = x - 1` puede fallar ya que el dominio de :mzn:`y` no es lo suficientemente grande para todos los valores posibles de :mzn:`x - 1`. Pero la restricción :mzn:`z = x * y` no puede (de hecho el análisis de límites dará :mzn:`z` límites lo suficientemente grandes como para contener todos los valores posibles de :mzn:`x * y`). Un mejor aplanamiento dará: .. 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); Actualmente ``mzn2fzn`` hace esto definiendo siempre los límites declarados de una variable introducida para que sea lo suficientemente grande como para que su ecuación de definición mantenga siempre y luego agregue restricciones de límites en el contexto correcto para la expresión ``let``. En el ejemplo anterior, esto da como resultado .. 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); Esta traducción conduce a una solución más eficiente ya que el cálculo posiblemente complejo de la variable ``let`` no se reifica. Otra razón para este enfoque es que también funciona cuando las variables introducidas aparecen en contextos negativos (siempre que tengan una definición). Considere el siguiente ejemplo similar al anterior: .. 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; Las expresiones ``let`` aparecen en un contexto negado, pero se define cada variable introducida. El código aplanado es: .. 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; Tenga en cuenta que el método simple para la eliminación de ``let`` no proporciona una traducción correcta: .. 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; Da respuestas para todos los valores posibles de :mzn:`x`, mientras que la restricción original elimina la posibilidad de que :mzn:`x = 4`. El tratamiento de *elementos de restricción* en ``let`` expresiones es análogo a variables definidas. Uno puede pensar en una restricción como equivalente a la definición de una nueva variable booleana. Las definiciones de las nuevas variables booleanas se extraen al nivel superior y las Boolean permanece en el contexto correcto. .. 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; Es tratado como: .. 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; Y se aplana a: .. code-block:: minizinc constraint b1 = ((x >= 0) -> y = x); constraint b2 = ((x < 0) -> y = -x); constraint z > 1 -> (b1 /\ b2 /\ y * (y - 2) >= z);