MiniZinc Interface for R

Akshit Achara, Lars Kotthoff, Hans W. Borchers, Guido Tack

2020-12-10

Introduction

MiniZinc is a free and open-source constraint modeling language. Constraint satisfaction and discrete optimization problems can be formulated in a high-level modeling language. Models are compiled into an intermediate representation that is understood by a wide range of solvers. MiniZinc itself provides several solvers, for instance GeCode. The existing packages in R are not powerful enough to solve even mid-sized problems in combinatorial optimization.

There are implementations of an Interface to MiniZinc in Python like MiniZinc Python and pymzn and JMiniZinc for Java but such an interface does not exist for R.

This package provides an implementation of a very simple and easy to use interface for R that will help R users to solve optimization problems that can’t be solved with R currently.

It’s important to understand R6 classes before getting into the details. If you are not comfortable with R6, please go through this tutorial.

It would be nice to go through the tutorials on the MiniZinc website to understand more about MiniZinc. This is mainly for those who are interested in contributing to the package.

Installation

First, You need to download and build libminizinc (2.5.2) library for MiniZinc to work properly. Please follow these steps:

Linux:

Similarly, build libminizinc on Windows and OSX.

Now download the solver binaries from the binary bundles at (https://www.minizinc.org/) to be able to solve the models and achieve full functionality of the package.

Once these steps are over, you just need to re-install rminizinc by using

If you have the source tar, then you can do R CMD INSTALL rminizinc_0.0.1.tar.gz --configure-args="--with-bin=/path/to/bin --with-mzn=/path/to/libminizinc/"

NOTE: Please don’t use \ at the end of the path given to --with-bin as it will cause some solver configuration issues.

Getting Started

Load the library and the project root directory path.

library(rminizinc)
## Loading required package: rjson
# load the project directory
data("proot")
# check if the library is present
data("config")
parse.next = FALSE
if(LIBMINIZINC_PATH == ""){
  warning("Please install libminizinc on your system!")
  parse.next = TRUE
}
## Warning: Please install libminizinc on your system!
# check if solver binaries are present
data("slvbin")
evaluate.next = FALSE
if(SOLVER_BIN == ""){
  warning("Please download solver binaries to solve the model")
  evaluate.next = TRUE
}
## Warning: Please download solver binaries to solve the model

Parse a MiniZinc Model

A parser function mzn_parse has been implemented which can be used to detect possible syntax errors and get the smallest of details before the MiniZinc model is evaluated. The function returns the initialize Model R6 object.

Now, let’s solve a job shop model:

# mzn file path
mzn_path = paste0(PROJECT_DIRECTORY, "/inst/extdata/mzn_examples/jobshop/jobshop_0.mzn")

# parse the model
parseObj = rminizinc:::mzn_parse(mzn_path = mzn_path)
## Error in rminizinc:::mzn_parse(mzn_path = mzn_path): Please install libminizinc (2.5.2) on your system!

Look at the contents of parseObj for more understanding of the model.

Find the Missing Parameters

The missing parameters can be obtained using get_missing_pars()

missingPars = get_missing_pars(model = parseObj)
## Error in checkR6(x, classes, ordered, cloneable, public, private, null.ok): object 'parseObj' not found
print(missingPars)
## Error in print(missingPars): object 'missingPars' not found

Set Values of Missing Parameters

pVals = list(Int$new(3), Int$new(4),
             Array$new(exprVec = intExpressions(c(3, 3, 4, 4, 4, 3, 2, 2, 3, 3, 3, 4)),
               dimranges = list(IntSetVal$new(1,3), IntSetVal$new(1,4))), 
             Array$new(exprVec = intExpressions(c(1, 2, 3, 4, 1, 3, 2, 4, 4, 2, 1, 3)),
               dimranges = list(IntSetVal$new(1,3), IntSetVal$new(1,4))))
names(pVals) = missingPars
## Error in eval(expr, envir, enclos): object 'missingPars' not found
model = set_params(model = parseObj, modData = pVals)
## Error in checkR6(x, classes, ordered, cloneable, public, private, null.ok): object 'parseObj' not found
cat(model$mzn_string())
## Error in cat(model$mzn_string()): object 'model' not found

Solve the Model

The function mzn_eval() is used to evaluate a MiniZinc model and returns the solution string and a list of solutions if they were parsed without any error by the function sol_parse() otherwise it returns the solution string and an appropriate error. The parsed solutions are a named list where elements are of type OBJ$SOLUTIONS$SOLUTION<n>$<VARIABLE_NAME>. The optimal solution if found can be accessed using OBJ$SOLUTIONS$OPTIMAL_SOLUTION and the best solution can be accessed using OBJ$SOLUTIONS$BEST_SOLUTION. More details about the functions can be obtained using ?mzn_eval and ?sol_parse.

The solver name of the solver that should be used to solve the model needs to be specified by the user (default is “Gecode”) and the lib_path (path of the solver related files) is by default provided but a custom path can be provided the user in case it is required. The model must be provided as one and only one of R6 Model object, mzn_path i.e. path of mzn file or model_string i.e. the string representation of the model. If the user wishes to provide a data file, it’s path can be provided to the argument dznpath. A time limit (in ms) can also be provided to the argument time_limit. (default is 300000 ms)

A sample job shop problem has been solved below:

# R List object containing the solutions
solObj = rminizinc:::mzn_eval(model, solver = "org.gecode.gecode",
                   lib_path = paste0(PROJECT_DIRECTORY, "/inst/minizinc/"))
## Error in rminizinc:::mzn_eval(model, solver = "org.gecode.gecode", lib_path = paste0(PROJECT_DIRECTORY, : object 'model' not found
# get all the solutions
print(solObj$SOLUTIONS)
## Error in print(solObj$SOLUTIONS): object 'solObj' not found

Let’s solve another problem.

# file path
mzn_path = paste0(PROJECT_DIRECTORY, "/inst/extdata/mzn_examples/knapsack/knapsack_0.mzn")

# get missing parameter values
missingVals=rminizinc:::get_missing_pars( model = mzn_parse(mzn_path = mzn_path))
## Error in mzn_parse(mzn_path = mzn_path): Please install libminizinc (2.5.2) on your system!
print(missingVals)
## Error in print(missingVals): object 'missingVals' not found
# list of the data
pVals = list(Int$new(3), Int$new(9), Array$new(intExpressions(c(15,10,7)))
             , Array$new(intExpressions(c(4,3,2))))
## dimensions not provided: initializing as 1d Array with
##                                min index 1 and max index <number_of_elements>
## dimensions not provided: initializing as 1d Array with
##                                min index 1 and max index <number_of_elements>
names(pVals) = missingVals
## Error in eval(expr, envir, enclos): object 'missingVals' not found
# set the missing parameters
model = rminizinc:::set_params(modData = pVals, 
                                   mzn_parse(mzn_path = mzn_path))
## Error in mzn_parse(mzn_path = mzn_path): Please install libminizinc (2.5.2) on your system!
# R List object containing the solutions
solObj = rminizinc:::mzn_eval(r_model = model)
## Error in rminizinc:::mzn_eval(r_model = model): object 'model' not found
# get all the solutions
print(solObj$SOLUTIONS)
## Error in print(solObj$SOLUTIONS): object 'solObj' not found

Some examples of how to use these functions to solve optimization problems can be found in mzn_examples which are taken from minizinc-examples.

NOTE: Please don’t include output formatting in the mzn files or the solutions might not be parsed properly.

Variables

There are two types of variables in MiniZinc namely, decision variables and parameters.

The data types of variables can be single types i.e integers (int), floating point numbers (float), Booleans (bool) and strings (string) and collections i.e sets, enums and arrays (upto 6 dimensional arrays).

Parameter is used to specify a parameter in a given problem and they are assigned a fixed value or expression.

Decision variables are the unknowns that Minizinc model is finding solutions for. We do not need to give them a value, but instead we give them a domain of possible values. Sometimes expressions involving other variables and parameters are also assigned to decision variables. Decision variables need to satisfy a set of constraints which form the core of the problem.

To create a variable declaration one needs to understand the elements of R6 classes that will be used to create the variables.

Easy to use declaration functions have been created for the users to declare variables and parameters of different data types. Examples of how to declare variables is shown below.

# create the variable and parameter declarations
decl = IntDecl(name = "n", kind = "par")
item1 = VarDeclItem$new(decl = decl)

par2_val = BinOp$new(lhs = Int$new(1), binop = "..", rhs = item1$getId())
item2 = VarDeclItem$new(decl = IntSetDecl(name = "OBJ", kind = "par", value = par2_val))

item3 = VarDeclItem$new(decl = IntDecl(name = "capacity", kind = "par"))

item4 = VarDeclItem$new(decl = IntArrDecl(name = "profit", kind = "par", ndim = 1, 
                                          ind = list(item2$getId())))

item5 = VarDeclItem$new(decl = IntArrDecl(name = "size", kind = "par", ndim = 1, ind =                                                            list(item2$getId())))

item6 = VarDeclItem$new(decl = IntArrDecl(name = "x", kind = "var", ndim = 1, ind = list(item2$getId())))

Constraints

Constraints are defined on the decision variables to restrict the range of values that they can take. They can also be thought of as the rules of a problem.

Constraints can be created using different R6 sub classes of the super class Expression. In this example Generator, BinOp, Comprehension and Call classes have been used. These classes take in the elements required to create an expression that will be used as a constraint. More information can be found using ?<class Name>

Create constraints:

# declare parameter for iterator
parIter = IntDecl(name = "i", kind = "par")


gen_forall = Generator$new(IN = item2$getId(), decls = list(parIter))
bop1 = BinOp$new(lhs = ArrayAccess$new(v = item6$getId(),  args= list(gen_forall$getDecl(1)$getId())),
                                                             binop = ">=", rhs = Int$new(0))

Comp1 = Comprehension$new(generators = list(gen_forall), body = bop1, set = FALSE)
cl1 = Call$new(fnName = "forall", args = list(Comp1))
item7 = ConstraintItem$new(e = cl1)

gen_sum = Generator$new(IN = item2$getId(), decls = list(parIter))

bop2 = BinOp$new(lhs = ArrayAccess$new(v = item5$getId(), args = list(gen_sum$getDecl(1)$getId())),             
                 binop = "*",  rhs = ArrayAccess$new(v = item6$getId() , 
                 args = list(gen_sum$getDecl(1)$getId())))

Comp2 = Comprehension$new(generators = list(gen_sum), body = bop2, set = FALSE)
cl2 = Call$new(fnName = "sum", args = list(Comp2))
bop3 = BinOp$new(lhs = cl2, binop = "<=", rhs = item3$getId())
item8 = ConstraintItem$new(e = bop3)

Solve Type

The constraint programming problem can be of three types, namely: Satisfaction , Minimization and Maximization. Satisfaction problems produce all the solutions that satisfy the constraints whereas minimization and maximization problems produce the solution which minimizes and maximizes the given expression.

An example is shown below:

bop4 = BinOp$new(lhs = ArrayAccess$new(v = item4$getId(), args = list(gen_sum$getDecl(1)$getId())),
                      binop = "*", rhs = ArrayAccess$new(v = item6$getId(), 
                      args = list(gen_sum$getDecl(1)$getId())))

Comp3 = Comprehension$new(generators = list(gen_sum), body = bop4, set = FALSE)

cl3 = Call$new(fnName = "sum", args = list(Comp3))

item9 = SolveItem$new(solve_type = "maximize", e = cl3)

Create the Model

Combine all the items to create a MiniZinc model.

items  = c(item1, item2, item3, item4, item5, item6, item7, item8, item9)
mod = Model$new(items = items)
modString = mod$mzn_string()
cat(modString)
## int: n;
## 
## set of int: OBJ = (1 .. n);
## 
## int: capacity;
## 
## array[OBJ] of int: profit;
## 
## array[OBJ] of int: size;
## 
## array[OBJ] of var int: x;
## 
## constraint forall([(x[i] >= 0) | i in OBJ ]);
## 
## constraint (sum([(size[i] * x[i]) | i in OBJ ]) <= capacity);
## 
## solve  maximize sum([(profit[i] * x[i]) | i in OBJ ]);

Delete Items

All the Item and Expression classes have a delete() function which is used to delete the objects from everywhere in the model. Note that the objects will be deleted from all the models present in the environment from where the delete() function is called. An example to demonstrate the same is shown below:

# delete the item 1 i.e declaration of n 
item1$delete()
cat(mod$mzn_string())
## set of int: OBJ = (1 .. n);
## 
## int: capacity;
## 
## array[OBJ] of int: profit;
## 
## array[OBJ] of int: size;
## 
## array[OBJ] of var int: x;
## 
## constraint forall([(x[i] >= 0) | i in OBJ ]);
## 
## constraint (sum([(size[i] * x[i]) | i in OBJ ]) <= capacity);
## 
## solve  maximize sum([(profit[i] * x[i]) | i in OBJ ]);

Create Items Using Strings

The strings containing MiniZinc syntax of items can be directly supplied to the constructors to initialize the objects. If strings are supplied, no other argument should be supplied to any of the Item classes except for AssignItem where you need to provided the associated variable declaration for the assignment.

Varaible Item

declItem = VarDeclItem$new(mzn_str = "set of int: WORKSHEET = 0..worksheets-1;")
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
sprintf("Is this a parameter? %s", declItem$getDecl()$isPar())
## Error in sprintf("Is this a parameter? %s", declItem$getDecl()$isPar()): object 'declItem' not found
sprintf("Is this a set? %s", declItem$getDecl()$ti()$type()$isSet())
## Error in sprintf("Is this a set? %s", declItem$getDecl()$ti()$type()$isSet()): object 'declItem' not found
sprintf("Base type of set: %s", declItem$getDecl()$ti()$type()$bt())
## Error in sprintf("Base type of set: %s", declItem$getDecl()$ti()$type()$bt()): object 'declItem' not found
sprintf("Name: %s", declItem$getId()$getName())
## Error in sprintf("Name: %s", declItem$getId()$getName()): object 'declItem' not found
sprintf("Value: %s", declItem$getDecl()$getValue()$c_str())
## Error in sprintf("Value: %s", declItem$getDecl()$getValue()$c_str()): object 'declItem' not found

Constraint Item

CstrItem = ConstraintItem$new(mzn_str = "constraint forall (i in PREC)
                  (let { WORKSHEET: w1 = preceeds[i];
                             WORKSHEET: w2 = succeeds[i]; } in
                   g[w1] * e[w1] <= d[w2] + days * (1 - g[w2]));")
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
sprintf("Expression involved: %s", CstrItem$getExp()$c_str())
## Error in sprintf("Expression involved: %s", CstrItem$getExp()$c_str()): object 'CstrItem' not found
sprintf("Call function name: %s", CstrItem$getExp()$getName())
## Error in sprintf("Call function name: %s", CstrItem$getExp()$getName()): object 'CstrItem' not found
sprintf("Number of Arguments: %s", CstrItem$getExp()$nargs())
## Error in sprintf("Number of Arguments: %s", CstrItem$getExp()$nargs()): object 'CstrItem' not found
sprintf("Class of Argument: %s",  class(CstrItem$getExp()$getArg(1))[1])
## Error in sprintf("Class of Argument: %s", class(CstrItem$getExp()$getArg(1))[1]): object 'CstrItem' not found
sprintf("Number of Generators: %s", CstrItem$getExp()$nargs())
## Error in sprintf("Number of Generators: %s", CstrItem$getExp()$nargs()): object 'CstrItem' not found
sprintf("Generator: %s", CstrItem$getExp()$getArg(1)$getGen(1)$c_str())
## Error in sprintf("Generator: %s", CstrItem$getExp()$getArg(1)$getGen(1)$c_str()): object 'CstrItem' not found
sprintf("Comprehension body: %s", CstrItem$getExp()$getArg(1)$getBody()$c_str())
## Error in sprintf("Comprehension body: %s", CstrItem$getExp()$getArg(1)$getBody()$c_str()): object 'CstrItem' not found

Solve Item

SlvItem = SolveItem$new(mzn_str = "solve 
    :: int_search(
        [ if j = 1 then g[import_first[i]] else -d[import_first[i]] endif  | i in 1..worksheets, j in 1..2], 
        input_order, indomain_max, complete)
    maximize objective;")
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
sprintf("Objective: %s", SlvItem$getSt())
## Error in sprintf("Objective: %s", SlvItem$getSt()): object 'SlvItem' not found
cat(sprintf("Annotation: %s", SlvItem$getAnn()$c_str()))
## Error in sprintf("Annotation: %s", SlvItem$getAnn()$c_str()): object 'SlvItem' not found

Function Item

fnItem = FunctionItem$new(mzn_str = "predicate nonoverlap(var int:s1, var int:d1,
                     var int:s2, var int:d2)=
          s1 + d1 <= s2 \\/ s2 + d2 <= s1;")
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
sprintf("Function name: %s", fnItem$name())
## Error in sprintf("Function name: %s", fnItem$name()): object 'fnItem' not found
sprintf("No of function declarations: %s", length(fnItem$getDecls()))
## Error in sprintf("No of function declarations: %s", length(fnItem$getDecls())): object 'fnItem' not found
sprintf("Function expression: %s", fnItem$getBody()$c_str())
## Error in sprintf("Function expression: %s", fnItem$getBody()$c_str()): object 'fnItem' not found

Include Item

iItem = IncludeItem$new(mzn_str = "include \"cumulative.mzn\" ;")
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
sprintf("Included mzn name: %s", iItem$getmznName())
## Error in sprintf("Included mzn name: %s", iItem$getmznName()): object 'iItem' not found

Modify Variable Domain

vd = VarDomainDecl(name = "n", dom = Set$new(IntSetVal$new(imin = 1, imax = 2)))
sprintf("The current declaration is: %s", vd$c_str())
vd$setDomain(Set$new(IntSetVal$new(imin = 3, imax = 5)))
sprintf("The modified declaration is: %s", vd$c_str())
## [1] "The current declaration is: var 1..2: n"
## [1] "The modified declaration is: var 3..5: n"

Modify Constraints

There are various getter and setter functions for the expression classes that can be used to modify existing constraints. For example:

vItem = VarDeclItem$new(mzn_str = "set of int: a = {1, 2, 3, 4};") 
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
cItem = ConstraintItem$new(mzn_str = "constraint sum(a) < 10;")
## Error in mzn_parse(model_string = mzn_str): Please install libminizinc (2.5.2) on your system!
sprintf("The current constraint is: %s", cItem$c_str())
## Error in sprintf("The current constraint is: %s", cItem$c_str()): object 'cItem' not found
cItem$setExp(BinOp$new(lhs = Call$new(fnName = "max", args = list(vItem$getDecl()$getId())),
                       binop = "<", rhs = Int$new(10)))
## Error in eval(expr, envir, enclos): object 'cItem' not found
sprintf("The modified constraint is: %s", cItem$c_str())
## Error in sprintf("The modified constraint is: %s", cItem$c_str()): object 'cItem' not found

ACKNOWLEDGMENT

I would like to thank all the developers of libminizinc for allowing me to use the library in my package and for providing help in understanding the usage of the library and MiniZinc.