More Complex Models =================== In the last section we introduced the basic structure of a MiniZinc model. In this section we introduce the array and set data structures, enumerated types and more complex constraints. .. _sec-arrayset: Arrays and Sets --------------- Almost always we are interested in building models where the number of constraints and variables is dependent on the input data. In order to do so we will usually use :index:`arrays `. Consider a simple finite element model for modelling temperatures on a rectangular sheet of metal. We approximate the temperatures across the sheet by breaking the sheet into a finite number of elements in a two-dimensional matrix. A model is shown in :numref:`ex-laplace`. It declares the width ``w`` and height ``h`` of the finite element model. The declaration .. literalinclude:: examples/laplace.mzn :language: minizinc :lines: 5-9 declares four fixed sets of integers describing the dimensions of the finite element model: ``HEIGHT`` is the whole height of the model, while ``CHEIGHT`` is the centre of the height omitting the top and bottom, ``WIDTH`` is the whole width of the model, while ``CWIDTH`` is the centre of the width omitting the left and right sides. Finally a two dimensional array of float variables ``t`` with rows numbered :math:`0` to :math:`h` (``HEIGHT``) and columns :math:`0` to :math:`w` (``WIDTH``), to represent the temperatures at each point in the metal plate. We can access the element of the array in the :math:`i^{th}` row and :math:`j^{th}` column using an expression :mzn:`t[i,j]`. Laplace's equation states that when the plate reaches a steady state the temperature at each internal point is the average of its orthogonal neighbours. The constraint .. literalinclude:: examples/laplace.mzn :language: minizinc :lines: 16-18 ensures that each internal point :math:`(i,j)` is the average of its four orthogonal neighbours. The constraints .. literalinclude:: examples/laplace.mzn :language: minizinc :lines: 20-24 restrict the temperatures on each edge to be equal, and gives these temperatures names: ``left``, ``right``, ``top`` and ``bottom``. While the constraints .. literalinclude:: examples/laplace.mzn :language: minizinc :lines: 26-30 ensure that the corners (which are irrelevant) are set to 0.0. We can determine the temperatures in a plate broken into 5 :math:`\times` 5 elements with left, right and bottom temperature 0 and top temperature 100 with the model shown in :numref:`ex-laplace`. .. literalinclude:: examples/laplace.mzn :language: minizinc :name: ex-laplace :caption: Finite element plate model for determining steady state temperatures (:download:`laplace.mzn `). Running the command .. code-block:: bash $ minizinc --solver cbc laplace.mzn gives the output .. code-block:: none -0.00 100.00 100.00 100.00 -0.00 -0.00 42.86 52.68 42.86 -0.00 -0.00 18.75 25.00 18.75 -0.00 -0.00 7.14 9.82 7.14 -0.00 -0.00 -0.00 -0.00 -0.00 -0.00 ---------- .. defblock:: Sets .. index:: single: set Set variables are declared with a declaration of the form .. code-block:: minizincdef set of : ; where sets of integers, enums (see later), floats or Booleans are allowed. The only type allowed for decision variable sets are variable sets of integers or enums. Set literals are of the form .. code-block:: minizincdef { , ..., } or are :index:`range` expressions over either integers, enums or floats of the form .. code-block:: minizincdef .. The standard :index:`set operations ` are provided: element membership (:mzn:`in`), (non-strict) subset relationship (:mzn:`subset`), (non-strict) superset relationship (:mzn:`superset`), union (:mzn:`union`), intersection (:mzn:`intersect`), set difference (:mzn:`diff`), symmetric set difference (:mzn:`symdiff`) and the number of elements in the set (:mzn:`card`). As we have seen set variables and set literals (including ranges) can be used as an implicit type in variable declarations in which case the variable has the type of the elements in the set and the variable is implicitly constrained to be a member of the set. Our cake baking problem is an example of a very simple kind of production planning problem. In this kind of problem we wish to determine how much of each kind of product to make to maximise the profit where manufacturing a product consumes varying amounts of some fixed resources. We can generalise the MiniZinc model in :numref:`ex-cakes2` to handle this kind of problem with a model that is generic in the kinds of resources and products. The model is shown in :numref:`ex-prod-planning` and a sample data file (for the cake baking example) is shown in :numref:`fig-prod-planning-data`. .. literalinclude:: examples/prod-planning.mzn :language: minizinc :name: ex-prod-planning :caption: Model for simple production planning (:download:`prod-planning.mzn `). .. literalinclude:: examples/prod-planning-data.dzn :language: minizinc :name: fig-prod-planning-data :caption: Example data file for the simple production planning problem (:download:`prod-planning-data.dzn `). The new feature in this model is the use of :index:`enumerated types `. These allow us to treat the choice of resources and products as parameters to the model. The first item in the model .. code-block:: minizinc enum Products; declares ``Products`` as an *unknown* set of products. .. defblock:: Enumerated Types .. index:: single: enumerated type single enum Enumerated types, which we shall refer to as ``enums``, are declared with a declaration of the form .. code-block:: minizincdef enum ; An enumerated type is defined by an assignment of the form .. code-block:: minizincdef enum = { , ..., } ; where :mzndef:``, ..., :mzndef:`` are the elements of the enumerated type, with name :mzndef:``. Each of the elements of the enumerated type is also effectively declared by this definition as a new constant of that type. The declaration and definition can be combined into one line as usual. The second item declares an array of integers: .. code-block:: minizinc array[Products] of int: profit; The :index:`index set ` of the array ``profit`` is ``Products``. This means that only elements of the set ``Products`` can be used to index the array. The elements of an enumerated type of :math:`n` elements act very similar to the integers :math:`1\dots n`. They can be compared, they are ordered, by the order they appear in the enumerated type definition, they can be iterated over, they can appear as indices of arrays, in fact they can appear anywhere an integer can appear. In the example data file we have initialized the array using a list of integers .. code-block:: minizinc Products = { BananaCake, ChocolateCake }; profit = [400,450]; meaning the profit for a banana cake is 400, while for a chocolate cake it is 450. Internally ``BananaCake`` will be treated like the integer 1, while ``ChocolateCake`` will be treated like the integer 2. The expression :mzn:`[400,500]` represents a literal one-dimensional array. In MiniZinc, the index set of literal arrays always starts at 1 (this is similar to other mathematically inspired languages such as MATLAB, Julia or Mathematica). While MiniZinc does not provide an explicit list type, one-dimensional arrays with an index set :mzn:`1..n` behave like lists, and we will sometimes refer to them as :index:`lists `. In a similar fashion, in the next two items we declare a set of resources ``Resources``, and an array ``capacity`` which gives the amount of each resource that is available. More interestingly, the item .. code-block:: minizinc array[Products, Resources] of int: consumption; declares a 2-D array ``consumption``. The value of :mzn:`consumption[p,r]` is the amount of resource :mzn:`r` required to produce one unit of product :mzn:`p`. Note that the first index is the row and the second is the column. The data file contains an example initialization of a 2-D array: .. code-block:: minizinc consumption= [| 250, 2, 75, 100, 0, | 200, 0, 150, 150, 75 |]; Notice how the delimiter ``|`` is used to separate rows. As for one-dimensional array literals, indexing of two-dimensional array literals also starts at 1. .. defblock:: Arrays .. index: single: array Thus, MiniZinc provides one- and multi-dimensional arrays which are declared using the type: .. code-block:: minizincdef array [ , ..., ] of MiniZinc requires that the array declaration contains the index set of each dimension and that the index set is either an integer range, a set variable initialised to an integer range, or an :index:`enumeration type `. Arrays can contain any of the base types: integers, enums, Booleans, floats or strings. These can be fixed or unfixed except for strings which can only be parameters. Arrays can also contain sets but they cannot contain arrays. :index:`One-dimensional array literals ` are of form .. code-block:: minizincdef [ , ..., ] and their index sets are :mzn:`1..n`, while :index:`two-dimensional array literals ` are of form .. code-block:: minizincdef [| , ..., | ... | , ..., |] where the array has ``m`` rows and ``n`` columns, with index sets :mzn:`1..m` for the first and :mzn:`1..n` for the second dimension. The family of built-in functions :mzn:`array1d`, :mzn:`array2d`, etc, can be used to initialise an array of any dimension from a list (or more exactly a one-dimensional array). The call: .. code-block:: minizincdef arrayd(, ..., , ) returns an ``n`` dimensional array with index sets given by the first ``n`` arguments and the last argument contains the elements of the array. For instance, :mzn:`array2d(1..3, 1..2, [1, 2, 3, 4, 5, 6])` is equivalent to :mzn:`[|1, 2 |3, 4 |5, 6|]`. Array elements are :index:`accessed ` using bracket syntax: :mzn:`a[i,j]` gives the element at row index :math:`i^{th}` and column index :math:`j^{th}`. .. \pjs{New array functions!} The concatenation operator ``++`` can be used to concatenate two one-dimensional arrays together. The result is a list, i.e. a one-dimensional array whose elements are indexed from 1. For instance :mzn:`[4000, 6] ++ [2000, 500, 500]` evaluates to :mzn:`[4000, 6, 2000, 500, 500]`. The built-in function :mzn:`length` returns the length of a one-dimensional array. The next item in the model defines the parameter :mzn:`mproducts`. This is set to an upper-bound on the number of products of any type that can be produced. This is quite a complex example of nested array comprehensions and aggregation operators. We shall introduce these before we try to understand this item and the rest of the model. First, MiniZinc provides list comprehensions similar to those provided in many functional programming languages, or Python. For example, the list comprehension :mzn:`[i + j | i, j in 1..3 where j < i]` evaluates to :mzn:`[2 + 1, 3 + 1, 3 + 2]` which is :mzn:`[3, 4, 5]`. Of course :mzn:`[3, 4, 5]` is simply an array with index set :mzn:`1..3`. MiniZinc also provides set comprehensions which have a similar syntax: for instance, :mzn:`{i + j | i, j in 1..3 where j < i}` evaluates to the set :mzn:`{3, 4, 5}`. .. defblock:: List and Set Comprehensions .. index: single: comprehension single: comprehension; list The generic form of a list comprehension is .. code-block:: minizincdef [ | ] The expression :mzndef:`` specifies how to construct elements in the output list from the elements generated by :mzndef:``. The generator :mzndef:`` consists of a comma separated sequence of generator expressions optionally followed by a Boolean expression. The two forms are .. code-block:: minizincdef where The optional :mzndef:`` in the second form acts as a filter on the generator expression: only elements satisfying the Boolean expression are used to construct elements in the output list. A :index:`generator ` :mzndef:`` has the form .. code-block:: minizincdef , ..., in Each identifier is an *iterator* which takes the values of the array expression in turn, with the last identifier varying most rapidly. The generators of a list comprehension and :mzndef:`` usually do not involve decision variables. If they do involve decision variables then the list produced is a list of :mzndef:`var opt ` where :mzndef:`` is the type of the :mzndef:``. See the discussion of :index:`option types