Minizinc With Functions
Minizinc With Functions
1 Introduction
2
model, and automatically keeps only one of these expressions, replacing all others by a
reference to it. For example, the division in the following code occurs twice:
constraint (x div y) + a = 0;
constraint b * (x div y) = c;
So an equivalent model would hoist the division to the top level:
var int: tmp = x div y;
constraint tmp + a = 0;
constraint b * tmp = c;
User-defined functions increase the potential for automatic CSE because they intro-
duce syntactic equalities. One could introduce and detect the same equalities without
functional notation, by e.g. annotating “functional predicates”. However, this would not
simplify the translation presented in section 5 that performs CSE while maintaining the
relational semantics, but it would make the syntax much less convenient.
CSE is particularly effective if the function definition is complex, it introduces ad-
ditional variables, or its constraints are expensive to propagate. CSE also makes the
abstraction facilities in a language more useful, as common subexpressions will be de-
tected across different functions.
We extend MiniZinc by adding functions, using the Zinc [10] syntax for functions. The
other important extension to MiniZinc, not currently supported by Zinc, is to allow
constraints to occur inside let constructs.
The most basic form of user-defined functions would be a simple macro mechanism
that can be used to define abbreviations for functional compositions. For instance, we
could define a function for the Manhattan distance between two points as
function var int: manhattan(var int:x1, var int:y1,
var int:x2, var int:y2) =
abs(x2 - x1) + abs(y2 - y1);
Such a macro mechanism would be useful by itself and straightforward to imple-
ment; both CSE and the relational semantics would be ensured by the translation of
primitive functions. However, as soon as we permit let expressions that introduce new
variables and constraints in the function body, we have to define how these are translated
in reified contexts. This is the main contribution of this paper.
Suppose we wished to add a sqr function to MiniZinc that squares its argument.3
While we could do this without using local variables
function var int: sqr(var int:x) = x * x;
we can make the model propagate stronger using the following definition
3 This does not preclude solvers from using a more efficient built-in version of sqr, as they can
simply override the standard definition in their solver-specific MiniZinc library.
3
function var int: sqr(var int: x) =
let { var int: y = x * x; constraint y >= 0 } in y;
which explicitly adds that the result is non-negative.
Note that we assume that functions defined in MiniZinc are indeed pure (always
give the same answer for each input). It is certainly possible to write impure functions:
function var int: f(var int:x) = let { var 0..1: b } in b*x;
We consider this a modelling error – non-functional relations like this must be expressed
using predicates in MiniZinc. As purity analysis is difficult, our implementation does
not enforce purity but results in undefined behaviour of impure functions. We plan to
investigate a simple but incomplete purity analysis in future work.
3 Using Functions
In this section we illustrate the modelling possibilities that arise from the introduction
of user-defined functions.
Big Data. The bigger the data sets involved, the more crucial it is to get common subex-
pression elimination. The motivating example that made us add functions to MiniZinc
arose from modelling data mining problems [7].
An item set mining problem consists of a large data base of transactions TDB, which
is a list of sets of items (e.g., items that were bought together in one transaction from a
supermarket). Each transaction has an integer identifier.
An important concept in item set mining is the cover of an item set, the set of labels
of transactions in which the item set occurs. It can be defined naturally as a function:
function var set of int: cover(var set of int: Items,
array[int] of set of int: TDB) =
let { var set of index_set(TDB): Trans;
constraint forall (t in index_set(TDB)) (
t in Trans <-> Items subset TDB[t])
} in Trans;
Constraints involving the cover could restrict its size to at least k (frequent item set
mining), or require item sets to be closed (i.e., maximal).
With TDB being a huge data base, it would have a catastrophic impact on translation
and solver performance if we did not get CSE for different calls to cover. Without
support for functions, we can only lift out the definition of cover, performing CSE by
hand. The result is a much less readable model, and indeed a loss in compositionality,
as predicates that use cover can no longer be defined in a self-contained way.
Functional Global Constraints. The global constraint catalog [3] lists 120 constraints
(almost a third) as functional in nature, such as cycle, change, common, distance,
global_cardinality, graph_crossing, indexed_sum, path.
We can define functional versions that map to the global constraints, which yields
natural models and gives the MiniZinc compiler important hints for performing com-
mon subexpression elimination.
4
In a model for the Warehouse Location Problem (CSPLib 034) we need to constrain
the number of stores supplied by each warehouse. We can use the global constraint
count(x, i, c) which constrains c to be the number of times that the value i appears in
the array x. Assuming an array supplier mapping each store to its supplying ware-
house, the function inUse(i) returns the number of stores supplied by warehouse i.
function var int: inUse(int: i) =
let { var int: use;
constraint count(supplier,i,use) } in use;
constraint forall (i in index_set(supplier))
( inUse(i) <= capacity[i] );
Using MiniZinc functions we do not have to introduce auxiliary variables or per-
form CSE by hand in our model.
In recent work on reifying global constraints [2], functional global constraints play
a key role, as they permit decomposition of globals into a functionally defined part that
need not be reified, and a part that is easy to reify. User-defined functions make these
techniques immediately accessible in MiniZinc.
5
Solver specific translation / channelling. Solver independent models in MiniZinc
are translated to solver-specific versions using custom predicate definitions. In many
cases these definitions need to introduce new variables that are functionally defined in
terms of the original variables. We can use functions to make this straightforward and
automatically achieve CSE if the translation is required more than once.
Consider the mapping of not equals for an integer programming solver. We can
define a function int2array01 such that int2array01(x)[i]=1 iff x=i. Using
this function, the not equals constraint is straightforward to define4 :
predicate int_neq(var int: x, var int:y) = let {
array[int] of var 0..1: bx = int2array01(x);
array[int] of var 0..1: by = int2array01(y);
} in forall(i in max(lb(x),lb(y))..min(ub(x),ub(y)))
(bx[i] + by[i] <= 1);
Crucially, CSE guarantees that for any integer variable, at most one array of 01
variables is created, so that we get an efficient translation of constraints that share vari-
ables such as int_neq(x,y) /\ int_neq(x,z). Without functions, an efficient
abstraction like int_neq would not be possible, and the user would be required to
carefully avoid introducing common subexpressions while modelling.
The above encoding generalises easily to the alldifferent constraint on an array of
variables. We can use similar mechanisms for translating integer and set models for
SAT solvers, and for channelling constraints between two different viewpoints of a
model, such as a set-based model and a Boolean model where for each set variable x
we introduce an array of Boolean variables b such that i ∈ x ⇔ b[i].
Simplifying MiniZinc Translation. Once we have generic methods for handling func-
tions, many of the built-in functions in MiniZinc can be simply treated as library func-
tions. We can then use the generic methods for flattening. This simplifies the flattening
process and makes it more transparent and adaptable.
Consider the built-in in abs function. We can remove it from MiniZinc and add the
following library definition.
function var int: abs(var int: x) =
let { var int: y; constraint int_abs(x,y) } in y;
The handling of this function will automatically create the FlatZinc constraint int_abs
for any absolute value expressions.5 Built-in functions that can be relegated in this way
include: the trigonometric functions, their inverses and hyperbolic versions, exponenti-
ation and logarithms, amongst others.
6
function var int: evendiv2(var int: x) =
let { var int: y; constraint x = 2*y } in y;
constraint not evendiv2(x)=1;
A logical interpretation would be ¬∃y.x = 2 × y ∧ y = 1 or ∀y.x 6= 2 × y ∨ y 6= 1.
However, we cannot easily express universal quantification, and a naive translation of
the above (simply ignoring the universal quantifier) would produce the following:
var int: y;
constraint not (x = 2*y /\ y = 1);
This does not have the desired semantics, as it permits for example the solution
x = 2, y = 3, although x = 2 clearly should not be a solution. MiniZinc (and Zinc)
therefore consider models illegal that contain let expressions in a negative context
which introduce non-functionally defined variables (like y in the above example).
The upshot of this is that user-defined functions become almost useless in negative
contexts, since interesting functions can usually only be defined by introducing new
variables (as in most of our examples). The following technique avoids this problem.
Totality Annotations. Functions with local variables can be used safely in negative
contexts if they are total, that is they are defined on all their inputs. Consider the fol-
lowing example:
function var int: g(var int: x) :: total =
let { var int: y;
constraint (x > 0) -> y = x;
constraint (x <= 0) -> y = 10-x
} in y;
var -10..10: u;
constraint not g(u) = 5;
Even though syntactically y is not functionally defined by the function, it is semanti-
cally. It is safe to translate the last line as
var int: y = g(u); % evaluate g in root context
constraint not y = 5; % only negate the equality
To allow the user to declare total functions, we introduce the annotation total
which can be added to function definitions as seen above. The annotation total promi-
ses that the function is total for all uses, and that it does not constrain its arguments. The
function will be translated in the root context, which means that free variable definitions
are allowed. If a user annotates a function as total that is in fact partial, this effectively
results in the function being translated according to the strict semantics. We do not, at
this point, attempt to detect incorrect totality annotations automatically.
Recipe for partial functions. Totality annotations enable us to define some partial
functions that introduce variables, by following a simple recipe. Assume that we want
to implement a partial function f (x), and that we can express its domain of definition
by a constraint c(x) that does not introduce any free variables. Then
7
1. create a total extension f 0 such that f 0 (x) = f (x) if c(x), and f 0 (x) = 0 otherwise.
We annotate f 0 as ::total, so it can introduce arbitrary free variables.
2. create a partial guard function g(x) = let { c(x) } in f 0 (x) ;
Consider for example evendiv2 as defined above. It can be rewritten as
function var int: evendiv2(var int: x) =
let { constraint x mod 2 = 0 } in safe_ed2(x);
function var int: safe_ed2(var int: x) :: total =
let { var int: y;
constraint x mod 2 = 0 -> x = 2*y;
constraint not (x mod 2 = 0) -> y = 0} in y;
Now evendiv2 is a partial function, but does not introduce variables which are not
functionally defined, and safe_ed2 is a total function, which is the same as the origi-
nal evendiv2 when x mod 2 = 0. The constraint not evendiv2(x)=1 now trans-
lates as
var int: y;
constraint safe_ed2(x)=y;
constraint not (x mod 2 = 0 /\ y = 1);
Now x = 2 enforces y = 1, so the negation fails and only correct solutions are returned.
We use the same mechanism for the translation of built-in partial functions div and
array access, assuming built-in total functions safediv and safeelement.
If we can express the implicit constraint c(x) of a partial function on its input argu-
ments without introducing new variables, we can freely use the partial function in any
context. Of course, for some functions, c(x) may be difficult to express, so this mecha-
nism is no general solution to the problem. This is not surprising, as otherwise we could
translate models with arbitrary quantifiers efficiently to MiniZinc.
5 Translation to FlatZinc
Constraint problems formulated in MiniZinc are solved by translating them to a simpler,
solver-specific subset of MiniZinc, called FlatZinc. This section shows how to translate
our extended MiniZinc to FlatZinc.
The complexities in the translation arise from the need to simultaneously (a) unroll
array comprehensions (and other loops), (b) replace predicate and function applications
by their body, and (c) flatten expressions.
Once we take into account CSE, we cannot perform these separately. In order to
have names for common subexpressions we need to flatten expressions. And in order to
take advantage of functions for CSE we cannot replace predicate and function applica-
tions without flattening to generate these names. And without replacing predicate and
function application by their body we are unable to see all the loops to unroll.
The translation algorithm presented below generates a flat model equivalent to the
original model as a global set of constraints S. We ignore the collection of variable dec-
larations, which is also clearly important, but quite straightforward. The translation uses
full reification to create the model. It can be extended to use half reification [4], but we
8
omit this for space reasons. Common subexpression elimination is implemented using
a technique similar to hash-consing in Lisp [1]. For simplicity we only show syntac-
tic CSE, which eliminates expressions that are identical after parameter replacement.
The extension to semantic CSE, using commutativity and other equivalences, is well
understood (see [12] for a detailed discussion) but makes the pseudo-code much longer.
The grammar uses the symbols bvar for Boolean variables, relop for relational op-
erators { ==, <=, <, !=, >=, > }, pred for names of predicates, int for integer constants,
ivar for integer variables, and arithop for arithmetic operators { +, -, *, div }.
In the let constructs we make use of the nonterminal decls for declarations. We
define this below using idecl for integer variable declarations, bdecl for Boolean vari-
able declarations. We also define args as a list of integer variable declarations, an item
item as either a predicate declaration or a constraint, items as a list of items, and a model
model as some declarations followed by items. Note that ε represents the empty string.
idecl −→ int : ivar | var int : ivar | var term .. term : ivar
bdecl −→ bool : bvar | var bool : bvar
decls −→ ε | idecl ; decls | idecl = term ; decls | bdecl ; decls | bdecl = cons ; decls
args −→ var int : ivar | var int : ivar , args | int : ivar | int : ivar , args
item −→ predicate pred( args) = cons ; | constraint cons ;
items −→ ε | item items
model −→ decls items
To simplify presentation, we assume all predicate arguments are integers, but the
translation can be extended to arbitrary arguments in a straightforward way.
To introduce functions in MiniZinc, the grammar above is modified as follows. We
add new item types for integer variable and integer parameter functions (again for sim-
plicity all arguments are assumed to be integers). We change the form of the let con-
struct for both constraints and terms to allow optional constraints (ocons).
9
item −→ function var int : func( args ) = term ; |
−→ function int : func( args ) = term ;
cons −→ let { decls ocons } in cons
term −→ let { decls ocons } in term
ocons −→ ε | ; constraint cons ocons
10
addition to S on the first line of flatc. Note that in this simplified presentation, if an
expression introduces a fresh variable and it appears first in a negative context and only
later in the root context, the translation aborts. This can be fixed in a preprocessing step
that sorts expressions according to their context.
The flattening proceeds by evaluating fixed Boolean expressions and returning the
value. We assume fixed checks if an expression is fixed (determined during MiniZinc’s
type analysis), and eval evaluates a fixed expression. For simplicity of presentation, we
assume that fixed expressions are never undefined.
For non-fixed expressions we treat each form in turn. Boolean literals and variables
are simply returned. Basic relational operations flatten their terms using the function
flatt, which for a term t returns a tuple hv, bi of an integer variable/value v and a Boolean
literal b such that b ⇔ (v = t) (described in detail below). The relational operations
then return a reified form of the relation. The logical operators recursively flatten their
arguments, passing in the correct context. The logical array operators evaluate their
array argument, then create an equivalent term using foldl and either /\ or \/ which is
then flattened. A Boolean array lookup flattens its arguments, and creates a safeelement
constraint (which does not constrain the index variable) and Boolean b00 to capture
whether the array lookup was safe. Built-in predicates abort if not in the root context.
They flatten their arguments and add an appropriate built-in constraint. User defined
predicate applications flatten their arguments and then flatten a renamed copy of the
body. if-then-else evaluates the condition (which must be fixed) and flattens the then or
else branch appropriately. The handling of let is the most complicated. The expression
is renamed with new copies of the let variables. We extract the constraints from the let
expression using function flatlet which returns the extracted constraint and a rewritten
term (not used in this case, but used in flatt). The constraints returned by function flatlet
are then flattened. Finally if we are in the root context, we ensure that the Boolean b
returned must be true by adding b to S.
flatc(c,ctxt)
h := hash[c]; if (h 6= ⊥) S ∪:= {(ctxt = root) ⇒ h}; return h
if (fixed(c)) b := eval(c)
else
switch c
case b0 (bvar): b := b0 ;
case t1 r t2 (relop): hv1 , b1 i := flatt(t1 , ctxt); hv2 , b2 i := flatt(t2 , ctxt);
S ∪:= {new b ⇔ (b1 ∧ b2 ∧ new b0 ), b0 ⇔ v1 r v2 }
case not c1 : b := ¬flatc(c1 , −ctxt)
case c1 /\ c2 : S ∪:= {new b ⇔ (flatc(c1 , ctxt) ∧ flatc(c2 , ctxt))}
case c1 \/ c2 : S ∪:= {new b ⇔ (flatc(c1 , +ctxt) ∨ flatc(c2 , +ctxt))}
case c1 -> c2 : S ∪:= {new b ⇔ (flatc(c1 , −ctxt) ⇒ flatc(c2 , +ctxt))}
case c1 <-> c2 : S ∪:= {new b ⇔ (flatc(c1 , mix) ⇔ flatc(c2 , mix))}
case forall ba: b := flatc( foldl(evala(ba), true, /\), ctxt)
case exists ba: b := flatc( foldl(evala(ba), false, \/), ctxt)
case [c1 , . . . , cn ] [ t ]: foreach( j ∈ 1..n) b j := flatc(c j , +ctxt); hv, bn+1 i := flatt(t, ctxt);
S := {new b ⇔ (bn+1 ∧ new b0 ∧ new b00 ), safeelement(v, [b1 , . . . , bn ], b0 ), b00 ⇔ v ∈ {1, .., n}}
case p (t1 , . . . ,tn ) (pred) built-in predicate:
if (ctxt 6= root) abort
foreach( j ∈ 1..n) hv j , _i := flatt(t j , ctxt);
11
b := true; S ∪:= {p(v1 , . . . , vn )}
case p (t1 , . . . ,tn ) (pred): user-defined predicate
let p(x1 , . . . , xn ) = c0 be defn of p
foreach( j ∈ 1..n) hv j , b j i := flatt(t j , ctxt);
new b0 := flatc(c0 [[x1 /v1 , . . . , xn /vn ]], ctxt)
S ∪:= {new b ⇔ b0 ∧ nj=1 b j }
V
case if c0 then c1 else c2 endif: if (eval(c0 )) b := flatc(c1 , ctxt) else b := flatc(c2 , ctxt)
case let { d } in c1 :
let v̄0 be a renaming of variables v̄ defined in d
hc0 , _i := flatlet(d[[v̄/v̄0 ]], c1 [[v̄/v̄0 ]], 0, ctxt)
b := flatc(c0 , ctxt);
if (ctxt = root) S ∪:= {b};
hash[c] := b
return b
The function evala replaces an array comprehension by the resulting array. Note
that terms lt and ut must be fixed in a correct MiniZinc model.
evala(t)
switch t
case [ t1 , . . . ,tn ]: return [ t1 , . . . ,tn ]
case [ e | v in lt .. ut ]: let l = eval(lt), u = eval(ut)
return [ e[[v/l]], e[[v/l + 1]], . . . e[[v/u]] ]
12
Flattening integer expressions. flatt(t, ctxt) flattens an integer term t in context ctxt. It
returns a tuple hv, bi of an integer variable/value v and a Boolean literal b, and as a side
effect adds constraints to S so that S |= b ⇔ (v = t). Note that again flattening in the
root context always returns a Boolean b made equivalent to true by the constraints in S.
Flattening first checks if the same expression has been flattened previously and if so
returns the stored result. flatt evaluates fixed integer expressions and returns the result.
For non-fixed integer expressions t each form is treated in turn. Simple integer expres-
sions are simply returned. Operators have their arguments flattened and the new value
calculated on the results. Safe division (which does not constrain its second argument to
be non-zero) is used for division with the constraint being captured by the new Boolean
b0 . Array lookup flattens all the integer expressions involved and creates a safeelement
constraint as in the Boolean case. sum expressions evaluate the array argument, and
then replace the sum by repeated addition using foldl and flatten that. if-then-else sim-
ply evaluates the if condition (which must be fixed) and flattens the then or else branch
appropriately. Functions are simply handled by flattening each of the arguments, the
function body is then renamed to use the variables representing the arguments, and the
body is then flattened. Importantly, if the function is declared total it is flattened in the
root context. Let constructs are handled analogously to flatc. We rename the scoped
term t1 to t 0 and collect the constraints in the definitions in c0 . The result is the flattening
of t 0 , with b capturing whether anything inside the let leads to failure.
flatt(t,ctxt)
hh, b0 i := hash[t]; if (h 6= ⊥) S ∪:= {(ctxt = root) ⇒ b0 }; return hh, b0 i
if (fixed(t)) v := eval(t); b := true
else switch t
case v0 (ivar): v := v0 ; b := true
case t1 a t2 (arithop): hv1 , b1 i := flatt(t1 , ctxt); hv2 , b2 i := flatt(t2 , ctxt);
if (a is not div) S ∪:= {new b ⇔ (b1 ∧ b2 ), a(v1 , v2 , new v)}
else S ∪:= {new b ⇔ (b1 ∧ b2 ∧ new b0 ), safediv(v1 , v2 , new v), b0 ⇔ v2 6= 0}
case [t1 , . . . ,tn ] [ t0 ]: foreach( j ∈ 0..n) hv j , b j i := flatt(t j , ctxt);
S := {new b ⇔ (new b0 ∧ nj=0 b j ), safeelement(v0 , [v1 , . . . , vn ], new v), b0 ⇔ v0 ∈ {1, ..., n}}
V
case let { d } in t1 :
let v̄0 be a renaming of variables v̄ defined in d
hc0 ,t 0 i := flatlet(d[[v̄/v̄0 ]], true,t1 [[v̄/v̄0 ]], ctxt)
hv, b1 i := flatt(t 0 , ctxt); b2 := flatc(c0 , ctxt); S ∪:= {new b ⇔ (b1 ∧ b2 )}
if (ctxt = root) S ∪:= {b};
hash[t] := hv, bi
return hv, bi
13
function var int: h(var int: a) =
let { var int: d = 12 div a; constraint d < 3 } in d;
constraint not (h(c) = 1);
Flattening of not (h(c) = 1) requires flattening the term h(c) in a neg context.
This requires flattening let { var int: d = 12 div c; constraint d < 3
} in d; in a negative context. This requires flattening 12 div c in a negative con-
text which creates the constraints safediv(12, c, v), b1 ⇔ c 6= 0 and returns hv, b1 i.
Flattening the let adds d = v and collects b1 /\ d < 3 in the constraints c which are
flattened to give b3 ⇔ (d < 3), b4 ⇔ (b3 ∧ b1 ) and returns tuple hd, b4 i. The treatment
of the equality adds b5 ⇔ d = 1, b6 ⇔ (b5 ∧ b4 ) and returns b6 . The negation returns
¬b6 and asserts ¬b6 .
Implementation. We have implemented the above rules in a compiler that turns MiniZinc
with functions into FlatZinc. This prototype can handle the complete grammar as pre-
sented here, extended with support for arbitrary function arguments (arrays, Booleans,
sets). Compared to the existing mzn2fzn translator, most built-in functions (such as
abs, bool2int, card) are now realised as user-defined functions in the MiniZinc
standard library rather than hard coding them into the translation.
The following table shows some experimental results obtained with the prototype.
We compiled five different instances of a standard model for a 16x16 Sudoku problem.
Each instance has 48 alldifferent constraints, and we used the linearisation presented in
section 3 to show the effect of common subexpression elimination. Crucially, the alldif-
ferent constraints in Sudoku puzzles overlap, many pairs of constraints share either one
or four variables. The standard decomposition without functions cannot take advantage
of this sharing across constraints, as the results below will show.
Benchmark Translation (s) # Cons Solving (s)
fn nofn fn nofn fn nofn speedup
Sudoku 1 (16x16) 0.2 0.3 1280 2304 0.24 0.78 3.33
Sudoku 2 (16x16) 0.2 0.3 1280 2304 0.29 3.45 11.96
Sudoku 3 (16x16) 0.2 0.3 1280 2304 0.32 15.32 47.78
Sudoku 4 (16x16) 0.2 0.3 1280 2304 0.24 0.43 1.80
Sudoku 5 (16x16) 0.2 0.3 1280 2304 0.34 6.07 17.72
The columns labelled fn present the results for the new prototype translator using
a decomposition of alldifferent based on functions. The nofn columns use the exist-
ing mzn2fzn tool with the standard MiniZinc linearisation library (available with the
command line option -G linear).
Translation time does not suffer from the additional CSE, as the results in column
Translation show. In fact, the new translator is slightly faster. CSE clearly reduces the
number of constraints by almost half (column # Cons). The solving time (column Solv-
ing) is the average of 20 runs of the Gurobi MIP solver [8] on a 3.4 GHz Intel Core i7,
running on a single core under Windows. The additional, redundant constraints in nofn
cause a noticeable overhead.
We will extend our prototype into a full replacement for the current mzn2fzn,
working towards version 2.0 of the MiniZinc language and toolchain. The prototype
and the benchmark problems are available from the authors upon request.
14
6 Related Work and Conclusion
The only modelling language that supports functions other than MiniZinc that we are
aware of is Zinc [10]. Zinc functions share the same restrictions as MiniZinc functions
on not allowing new variable definitions in negative or mixed contexts. Constraints
in let expression and total annotations are (currently) not part of Zinc. This means
the functions in MiniZinc are much more expressive than those in Zinc. Almost all
examples in this paper make use of constraints in let expressions to define functions. Of
course we intend to extend Zinc as we have extended MiniZinc. But a further difficulty
arises as Zinc models are compiled without instance data. This means that the treatment
of partiality is much more complex in Zinc, since we must lift local variable definitions
to the root context, without lifting any failure that they may cause. This also precludes
most CSE, since the Zinc compiler can only detect common subexpressions that occur
at the model level, rather than after instantiation with the data.
Constraint-based local search languages such as Comet [15] support user-defined
objective functions. These cannot be used as arguments in other constraints, and there-
fore these systems do not deal with partiality, Boolean contexts, or CSE.
Other modelling languages such as OPL [14] and Essence [6] do not include user-
defined functions or local variables (let expressions), and hence the issues that we con-
sider here do not arise. Essence supports constrained function variables, which are used
to model problems whose solution is a function. Our user-defined functions, in contrast,
serve a different purpose, they express the constraints of a problem. The approach to
flattening and CSE for Essence is described in [12], but Essence includes neither let
expressions, predicates or functions which are the main complicating features herein.
This earlier work [12] showed the importance of CSE for modelling languages, and this
is only magnified by the introduction of user-defined functions.
Modelling languages incorporated in a procedural OO language, such as IBM ILOG
Concert [9] expressions, Gecode’s [13] MiniModel expressions or Comet’s [15] mod-
elling constructs, do allow the use of functions in the host language. The problem is
that such functions do not extend the modelling language, and if treated in this manner
define the strict semantics.
In conclusion the addition of user-defined functions, local constraints in let con-
structs, and totality annotations gives a powerful modelling capability to MiniZinc. Us-
ing our schema for separating a partial function into a total extension with local vari-
able introduction and a partial function with no local variables, user-defined functions
are usable in all parts of a model. We believe functional modelling will become more
and more commonplace, particularly given the prevalence of functional constraints in
the global constraint catalog, given the importance of abstraction for defining solver-
specific MiniZinc libraries, and given that functions can be implemented efficiently as
shown in this paper.
15
References
1. Allen, J.: Anatomy of LISP. McGraw-Hill, Inc., New York, NY, USA (1978)
2. Beldiceanu, N., Carlsson, M., Flener, P., Pearson, J.: On the reification of global constraints.
Constraints (2012), DOI: 10.1007/s10601-012-9132-0
3. Beldiceanu, N., Carlsson, M., Rampon, J.X.: Global constraint catalog, working version as
of December 2012. http://www.emn.fr/z-info/sdemasse/gccat/
4. Feydy, T., Somogyi, Z., Stuckey, P.: Half-reification and flattening. In: Lee, J. (ed.) Proceed-
ings of the 17th International Conference on Principles and Practice of Constraint Program-
ming. LNCS, vol. 6876, pp. 286–301. Springer (2011)
5. Frisch, A., Stuckey, P.: The proper treatment of undefinedness in constraint languages. In:
Gent, I. (ed.) Proceedings of the 15th International Conference on Principles and Practice of
Constraint Programming. LNCS, vol. 5732, pp. 367–382. Springer (2009)
6. Frisch, A.M., Harvey, W., Jefferson, C., Hernández, B.M., Miguel, I.: Essence : A constraint
language for specifying combinatorial problems. Constraints 13(3), 268–306 (2008)
7. Guns, T.: Declarative Pattern Mining using Constraint Programming. Ph.D. thesis, Depart-
ment of Computer Science, K.U.Leuven (2012)
8. Gurobi Optimization, Inc.: Gurobi Optimizer Reference Manual (2012), http://www.
gurobi.com
9. IBM: ILOG Concert, part of IBM ILOG CPLEX Optimization Studio (2012),
http://www-01.ibm.com/software/integration/optimization/
cplex-optimizer/interfaces/
10. Marriott, K., Nethercote, N., Rafeh, R., Stuckey, P., Garcia de la Banda, M., Wallace, M.:
The design of the Zinc modelling language. Constraints 13(3), 229–267 (2008)
11. Nethercote, N., Stuckey, P., Becket, R., Brand, S., Duck, G., Tack, G.: MiniZinc: Towards a
standard CP modelling language. In: Bessiere, C. (ed.) Proceedings of the 13th International
Conference on Principles and Practice of Constraint Programming. LNCS, vol. 4741, pp.
529–543. Springer (2007)
12. Rendl, A.: Effective Compilation of Constraint Models. Ph.D. thesis, School of Computer
Science, University of St Andrews (2010)
13. Schulte, C., et al.: Gecode, the generic constraint development environment (2009), http:
//www.gecode.org/
14. Van Hentenryck, P.: The OPL Optimization Programming Language. MIT Press (1999)
15. Van Hentenryck, P., Michel, L.: Constraint-Based Local Search. MIT Press (2005)
16