Skip to content

MiniZinc 2.6.0

Compare
Choose a tag to compare
@cyderize cyderize released this 22 Feb 01:19
· 804 commits to master since this release

This release adds several new features and fixes a number of bugs, see https://www.minizinc.org/changes.html#v2.6.0 for a full change log. Binary releases for different platforms are available in the bundled packages of the MiniZinc IDE at https://github.com/minizinc/minizincide/releases.

MiniZinc 2.6 - What's new?

This is a big release, with many new language features, more flexible ways to structure output, a visualisation API in the MiniZinc IDE, and lots of small improvements and bug fixes.

1. Language

1.1. Indexed array literals and comprehensions

MiniZinc relies heavily on comprehensions to define data and constraints.
With the introduction of enumerated types, these comprehensions often had to
be combined with calls to functions such as array2d in order to coerce the result
into the correct shape and index set.

With MiniZinc 2.6, you can now use indexed array comprehensions and indexed array literals:

enum X = {A, B, C, D};
array[_] of int: s = [ A: 300, B: 200, C: 0, D: 100 ];
array[_,_] of int: p = [ (i,j): s[i]*s[j] | i,j in X];
array[int] of int: q = [ 0: 1, 3, 6, 9 ];

The definition of s uses an implicit index set _, and it defines an array with explicit indexes (e.g. enum value A is mapped to integer 300). The resulting array s has index set X. The comprehension that defines p also uses indexed notation, resulting in a 2d array p with index sets X,X. The array q specifies the index of the first element to be 0, with the rest of the array being indexed consecutively ([0: 1, 1: 3, 2: 6, 3: 9]).

1.2. The `any` type declaration

Variables that have a definition can now be declared with the keyword any instead of a concrete type:

any: x = 3;  % x will be of type int
any: y = join(",", [ show(i) | i in 1..10]);  % y will be of type array[int] of string

This works both for toplevel variables and those defined in let expressions.

1.3. Interval notation

We often need to iterate over all except the smallest or largest element in a set. This is now easier using the new interval notation:

a..<b    % set including a but excluding b
a<..b    % set excluding a but including b
a<..<b   % set of elements between a and b (excluding both)
a..      % set of elements starting with a and ending with largest element of type of a
a<..     % set of elements starting with successor of a and ending with largest element of type of a
a..<     % set of elements starting with a and ending with second largest element of type of a
a<..<    % set of elements starting with successor of a and ending with second largest element of type of a
..b      % analogous
..<b
<..b
<..<b

This notation is particularly useful for enumerated types.

1.4. Enumerated types

We believe that, ideally, integers should only be used in cases where you really
need to represent numbers, such as quantities, costs etc. But whenever you
use integers as identifiers for objects, you should use an enum instead!

That's why we've added more support to make enums easier to use and more versatile.

The language now has support for constructing extended enumerated types from
set arguments (rather than just other enumerated types):

enum X = Person(1..10) ++ { Nobody };

This will produce more readable output than the anon_enum syntax (which is still supported
for backwards compatibility).

The sets can be arbitrary set expressions, but have to result in a contiguous
set value (otherwise MiniZinc aborts with an error).

This syntax is also extended to anonymous enumerated type constructors:

enum X = _(1..10);    % anonymous enum type with 10 elements

Construction from subsets of an enum is now also fully supported,
as long as those sets are contiguous.

Another really nice change for enums is that the enum_next and enum_prev functions
now don't require the enumerated type as an argument any more: You can simply write
enum_next(x) instead of enum_next(X,x). The new function enum_of(x) will return the
base set of the enumerated type of argument x.

1.5. The `default` operator

The default operator can be used to guard against undefinedness or optional values being absent:

var 1..10: x;
var 0..10: y;
var int: z = (x div y) default 1;
    % z=1 if y=0

var opt 1..10: x;
var int: y = (10 div x) default 1;
    % y=1 if x=<>

Note that the default operator binds quite tightly, so you need to use parentheses in the examples above.

1.6. Better support for polymorphic functions

Many library functions in MiniZinc are polymorphic, i.e., they match multiple different types (this is
similar to generics or templates in other languages). The previous implementation was done before MiniZinc
had enumerated types, which meant that polymorphic functions didn't work well with enum arguments.

Now, polymorphic functions are compiled by monomorphisation. That means that MiniZinc creates a copy
of the function for each combination of types it is called with. The syntax for polymorphic functions was extended
to make use of the any type-inst, which can stand for an arbitrary instantiation (par/var as well as opt/non optional).

As a result, we were able to implement the enum_of function as well as simplify enum_next and enum_prev
(as mentioned earlier). And functions that use show, show2d etc. now correctly print enumerated types.

1.7. Annotations

The annotation language has been extended. It now supports capturing annotations in
predicate and function declarations, and passing the annotated variable as an argument
into an annotation function.

A new empty_annotation literal has been added, which is simply removed by the compiler.
That means you can now define your model like this:

ann: my_solve_ann;
solve ::my_solve_ann satisfy;

And then you could define my_solve_ann as empty_annotation in a data file, or
as an actual search annotation.

Function parameters and output items can now be annotated as well.

The standard library now provides an annotation for a simple Large Neighbourhood Search
(called relax_and_reconstruct). This section of the library will be extended in the future.
The propagation strength annotations have been renamed to domain_propagation and bounds_propagation,
and we added value_propagation as well.

1.8. Small tweaks to the language

  1. Anonymous generators

    Write comprehensions like [0 | _ in 1..10] instead of [0 | i in 1..10]

  2. Generators can iterate over multi-dimensional arrays:

    array[_,_] of int: a = [ (i,j): i*j | i,j in 1..3 ];
    array[_] of int: b = [ 2*i | i in a ]; % = [2, 4, 6, 4, 8, 12, 6, 12, 18]
    
  3. Add if-then without else for strings, annotations and arrays:

    if b then "test" endif             % "" if b=false
    if b then domain_propagation endif % empty annotation if b=false
    if b then [1,2,3] endif            % [] if b=false  
    
  4. Add support for hex and octal characters in string literals.
    You can use this e.g. to embed terminal control characters and produce colourful output.

  5. Identifiers can now start with underscores (this was previously reserved for FlatZinc models,
    but that distinction was not used by the compiler).

  6. More option type support (weak versions of !=, / and div, weak min/max, added missing
    operators for opt bool, optional circuit constraint).

2. Output and visualisations

With the improved support for polymorphic functions, we've been able to rewrite
some of the output functionality of MiniZinc. The MiniZinc IDE now includes a
server component that makes it easy to connect visualisations to a model.

2.1. Default output

One- and two-dimensional arrays are now output using the indexed literal syntax
when possible. That means that the default output (without defining any output items)
is now often much more readable, and doesn't contain any array1d or array2d coercions any more.

To give you better control over what ends up in the default output, we have
introduced the ::output and ::no_output annotations. Simply add ::output to
any variable declaration to ensure it is part of the output. Or add ::no_output to
a top-level variable that would otherwise be printed, but which you're not interested in.

Compared to the previous ::add_to_output annotation, the new ::output annotation doesn't cause
the default output of other variables to be switched off. So

var 1..3: x;
array[_] of var bool: y ::output = [ x = i | i in 1..3];

will print both x and y (whereas y ::add_to_output would have suppressed the output of x).
You can also use ::output with variables declared locally in a let expression, which
is really useful when debugging custom functions or predicates!

2.2. Output sections

Output can now be redirected into different sections. For example, we use this to produce JSON output
for visualising solutions, which is kept separate from the textual solution output.
The sections can also indicate that the output is in HTML format, so that tools like the MiniZinc IDE
can display a nicely formatted result.

An output item can be annotated with a string indicating the section to output it to.
All output to a particular section is concatenated together.
The default section is used if there is no output section annotation.
Sections can be switched on or off on the command line with the options --only-sections and --not-sections,
or toggled using the toolbar in the output window in the IDE.

output ["this goes to the default section\n"];
output :: "foo" ["this goes to the foo section\n"];
output ["this is also in the default section\n"];
output :: "my_html" ["<strong>This is some bold output</strong>"];

will result in the following in the MiniZinc IDE:

this goes to the default section
this is also in the default section
this goes to the foo section
This is some bold output
----------

2.3 Visualisations in the MiniZinc IDE

The MiniZinc IDE now features a web-browser based visualisation system that allows MiniZinc models to pass data to HTML/JavaScript applications in real time. There are a number of predefined visualisations which can be used by including the file ide/vis.mzn. These include line graphs, scatter plots, bar graphs, network visualisations, and 2D packing visualisations. Multiple predefined visualisations can be used in a single MiniZinc model, and will appear side-by-side on the resulting web page. Note that this feature is currently experimental.

For example, to visualise the value of the objective value, the following can be added to a model:

include "ide/vis.mzn";
constraint vis_line(_objective, "Objective value over time");

This is also a JavaScript API for writing custom visualisations for specific models. This is described in detail in the documentation.

3. Debugging

Debugging constraint models can be hard. MiniZinc 2.6 tries to make your life a little bit easier
by adding a few new features.

3.1. Tracing

Printing intermediate values during the compilation from MiniZinc to FlatZinc can often help
identify problems in your model. We noticed that we often ended up writing code like this:

constraint forall (i,j in X) ( trace("x[\(i)] < x[\(j)]\n", x[i] <= x[j]) );

to create output like this:

x[1] < x[1]
x[1] < x[2]
x[1] < x[3]
x[2] < x[1]
x[2] < x[2]
x[2] < x[3]
x[3] < x[1]
x[3] < x[2]
x[3] < x[3]

This is now made much easier with the new trace_exp function. You can simply wrap it around
any expression, and MiniZinc will try to print a useful, readable output of the expression
you're tracing:

constraint forall (i,j in X) ( trace_exp(x[i] <= x[j]) );

This will automatically produce output like this:

    test_trace_exp.mzn:4.32-54:
        x[i(≡1)]<=x[j(≡1)]
        x[i(≡1)]<=x[j(≡2)]
        x[i(≡1)]<=x[j(≡3)]
        x[i(≡2)]<=x[j(≡1)]
        x[i(≡2)]<=x[j(≡2)]
        x[i(≡2)]<=x[j(≡3)]
        x[i(≡3)]<=x[j(≡1)]
        x[i(≡3)]<=x[j(≡2)]
        x[i(≡3)]<=x[j(≡3)]

In addition, the trace_to_section function can be used to produce tracing output into a different output
section, to keep it separate from e.g. error messages in the output.

3.2. Error messages for internal errors

MiniZinc will now output human-readable error messages in the case of an unrecoverable crash.
In particular, if the compiler runs into a stack overflow (e.g. in an unbounded recursion), it will
print an error message that identifies the recursion and attempts to print part of the
MiniZinc call stack. That should help you identify the issues.

If MiniZinc crashes due to an internal error, you should now also see an error message that
clearly identifies this as a bug.

3.3. Error messages for undefined results

Error messages and warnings arising from undefinedness (such as array access out of bounds) now contain
more information about the error, such as the expected range of values vs. the actual value.

3.4. Debug mode

Finally, we have added debugging builtins (tracing functions and assertions) that are only evaluated if MiniZinc is run with the
--debug command line option. These are useful in case the debugging information is costly to
compute and not required when the model is used in production.

4. Documentation

The tool that generates the reference documentation has been updated. All overloaded versions
of a function or predicate are now grouped together, which reduces clutter and should make it
easier to find and compare e.g. different versions of the same constraint.

The output of mathematical expressions in the documentation should also look much cleaner now.

Both the tutorial and the reference documentation have been updated with the new features.