Totalisation
Overview
A function is total iff it is defined for any argument in its domain. In MiniZinc, the domain of a function is determined by the type-insts of its arguments, but not by any constraints on its arguments.
A function is partial iff it is undefined for certain arguments in its domain. In MiniZinc, a function is partial if its return type is not a subtype of var opt bool, and at least one of these conditions holds:
- If at least one of its arguments has a constrained type. E.g.,
function int: foo(1..3: x) = x + 1;is partial, because its argumentxis constrained. - If its body is a partially defined expression
- If it is a built-in function that is known to be partial (e.g. integer division, modulo etc.)
A function that is partial according to the rules above can be annotated as ::promise_total if it can be guaranteed that it will return a value for any input in its domain.
An expression is partially defined if its type is not a subtype of var opt bool and it does not evaluate to a value for all possible evaluations of its free variables. At least one of the following conditions needs to hold for an expression to be partially defined:
- It is a call to a partial function or built-in operator
- It is a
letexpression with a type that is not a subtype ofvar opt bool, and one of the cases in theletis aconstraint, or one of the variables in thelethas a constrained type-inst - One of its immediate subexpressions is partially defined
Array types
Two options:
- An undefined element in an array makes the entire array undefined (MiniZinc 2 semantics)
- An undefined element in an array only causes undefinedness if the element is accessed (Stuckey/Frisch paper)
Issues with option 2: Passing an array to a function now means that all arrays have to be arrays of tuples (even if all elements are statically known to be defined); or, we have to generate different versions (tuple and non-tuple) for each function, for all array argument types.
Transformation
The basic idea of the transformation is to turn any partially defined expression into a pair of expressions (b,e) where b is a Boolean that is true iff the expression is defined, and e is the value of the expression if it is defined, and ⊥ otherwise.
-
The return type of a partial function returning type
Tis changed totuple(bool, T). -
Any variable declaration of type
Twhose right hand side is a partially defined expression is changed into a variable declaration of typetuple(bool, T). NOTE: What about array types? Do they capture definedness for each element, or for the array? -
Set literals (containing partially defined expressions) The type is transformed from
set of Tintotuple(bool, set of T)as follows.{ e1, e2, pde1, ..., pdek, ... }intolet { any: (b1,tmp1) = pde1; any: (bk,tmpk) = pdek; } in (forall([b1,...,bk]), {e1, e2, tmp1, ..., tmpk ...}) -
Array literals (containing partially defined expressions)
Change typeChange typearray[...] of Tintoarray[...] of tuple(bool, T)and change[ e1, ..., pde1, ... pdek, ... en]into[ (true, e1), ..., pde1, ... pdek, ... (true,en) ]array[...] of Tintotuple(bool, array [...] of Tand change[ e1, ..., (b1, pde1), ... (bk, pdek), ... en]into(b1 /\ ... /\ bk, [e1, pde1, ... pdek, ... en]) -
Array comprehensions
- Partially defined generators are not permitted (static type error).
- Partially defined generated expressions are fine. The resulting type changes from
array[...] of Tintotuple(bool, array[...] of T)(like array literals). May need to createarray [...] of tuple(bool, T)first, then extract the definedness from that.**** - Partially defined where clauses are fine (they are their own Boolean context)
- These examples show why it would be a bad idea to allow partial generators.
-
par comprehensions:
[ pde | i in 0..10, j in x[i] ][ pde | i in 0..10, (b, e) = x[i] where b, j in e ] -
var generator comprehensions:
var 0..10: x; [ pde | i in 0..10, j in i div x..100 ];[ let { any: (b,t) = i div x..100; any: (eb, et) = pde; } in if eb /\ b /\ j in t..100 then (true, et) else (b, <>) endif | i in 0..10, j in ub(i div x..100) ]
-
-
Set comprehensions These are transformed into array comprehensions and
array2setin a previous compiler phase. -
Array access
vararray access turns intoelement()which has the right semantics.function tuple(bool, any $T): element(array [int, ...] of any $T: x, int: i, ...)where the boolean is -
if then else endif
Can't use a normal function since the arguments would escape into the boolean context outside, so instead define the function which returns the pair:
function tuple(var bool, var $T): if_then_else(array [int] of var bool: c, array [int] of tuple(var bool, var $T): r) -
Tuple field access
Same as for arrays, so undefined inside a tuple makes the whole tuple undefined -
Calls Transformed to return a tuple if not known to be total
-
let { ... } inIf not in::promise_total/root context and non-boolean then the conjunction of the constraints and the variable definedness becomes the let's definednesslet { var int: x = e1; constraint c; } in e2becomes
let { tuple(var bool, var int): x' = (de1, e1'); var bool: c' = c; } in (de1 /\ c' /\ de2, e2') -
Annotations Annotations are compiled in the root context.
-
Output The expression in an output statement must be total. It is a static type error if it isn't. Users can use
defaultto make all expressions total.