To use the extension with constraints,
- install MiniZinc from https://www.minizinc.org/software.html,
- add the path for MiniZinc to the
PATHenvironment variable, and - install the MiniZinc Python interface by running
pip install minizinc.
![]() |
Consider the graph shown on the left. The red vertices form a minimum vertex cover of the graph. A vertex cover of a graph is a set of vertices in the graph such that each edge of the graph is attached to at least one vertex in the set. The minimum vertex cover problem is to find a vertex cover with the minimum number of vertices. The problem can be specified as a constraint problem in DA-rules as shown below. |
def constraint(name= 'vertex_cover', pars={vertex, edge}):
# Parameters:
vertex: set[int] # vertices as a set of integers
edge: dict(key=(vertex,vertex), val=ints(0,1)) # edges as an adj matrix
# Decision variables:
vc: dict(key=vertex, val=ints(0,1)) # vertex cover as a dict too
nvertex: int = sum(vc) # number of vertices, and objective function
# Constraints: each edge has at least one end in the vertex cover
cover = each(i in vertex, j in vertex, has=
edge[i,j] == 0 or vc[i] == 1 or vc[j] == 1)
# Return: any vertex cover with the minimum number of vertices
return anyof((vc, nvertex), cover, to_min(nvertex))
v = set(ints(1,6))
e = [[0,1,1,0,0,0],
[1,0,1,1,1,1],
[1,1,0,0,0,0],
[0,1,0,0,0,0],
[0,1,0,0,0,0],
[0,1,0,0,0,0]]
result = query(constraint='vertex_cover', vertex=v, edge=e)
print(result['vc']) # value of decision variable vc in solution
print(result['nvertex']) # value of decision variable nvertexA Constraint Satisfaction Problem (CSP) is defined by a triple <X,D,C> where X is a set of variables, D is a set of domains respective to the variables, and C is a set of constraints. Variables can be subdivided into parameters and decision variables, where parameters are variables whose values are given, and decision variables are those whose values are to be decided.
Solving a CSP is to find a solution, which is an assignment to decisions variables with values in their respective domains, such that all the constraints are satisfied.
Constraint Optimization Problem (COP) generalizes CSP with an objective, which is a function of some variables. Solving a COP is to find an optimal solution, which is an assignment of values to decision variables such that the value of the objective is minimized or maximized as required by the problem and all the constraints are satisfied.
The vertex cover problem is a COP with parameters vertex and edge, decision variables vc and nvertex where nvertex holds the value of the objective function, and constraint cover. The problem is to find a value of vc that minimizes the value of nvertex and satisfies cover.
A CSP can be specified easily using the syntax of a Python function:
def constraint(name, pars)The function must be named constraint, with two parameters name and pars.
nameis the name of the problem, which is'vertex_cover'in the example. The name serves as the unique identifier of the problem and uses the same naming convention as Python identifiers.parsis a set containing the parameters whose values need to be given when instantiating the problem. In the examplevertexandedgeare parameters.
Inside the body of the constraint function, 3 kinds of information need to be specified: variable domains, constraints, and the return value.
There are two types of variables: parameters and decision variables.
- Parameters are variables whose values must be known when the problem is instantiated.
- Decision variables are variables whose values are to be decided.
For some problems such as Sudoku, a partially filled playboard can be passed in as a parameter. Its empty cells are indicated by _ and will be assigned values when the problem is solved. Parameters with empty cells are also treated as decision variables.
- Variables need to be explicitly declared with domain information. The syntax for specifying the domain of a variable is of the following form, where
= valueis optional:
variable: domain = value- The domain can be one of 5 kinds:
- basic domain:
int,float,str, orbool. - bounded int domain:
ints(lb,ub)
ints(lb,ub)represents anintdomain taking values from lowerboundlbto upperboundub(inclusive),lbandubare ofintor bounded int domain.
- map domain:
dict(key=domain, val=domain). A map domain can be viewed as a multi-dimensional array.keyspecifies the domain of each dimension, which must be ofintor bounded int domain.valuemust be ofint, bounded int, orfloatdomain. - set domain:
set[domain].domaincan be ofint, bounded int, orfloatdomain. Set variables can only be parameters. - variable: A variable name can be used as domains representing the domain of itself.
Constraints are the conditions that must be satisfied when assigning values to decision variables. There are two ways of specifying a constraint:
- in the form of an assignment statement:
c_1 = bexp, where 'c_1' is the name of the constraint, or - in the form of a function definition:
def c_2():
bexp1
bexp2
...Expressions inside the function body are implicitly the conjuncts of logical conjunction, where c_2 is the name of the conjunction. Defining constraints this way can be simpler by omitting the and operator when there are multiple conjuncts.
- Theoretically,
bexpcan be any Boolean-valued expression. But currently only expressions of the following forms are supported:
- universal quantification: It evaluates to
Trueiff for all combinations of values of variables that satisfy all membership clausesvi in sexpi, the conditionbexpevaluates toTrue. A membership clausev in sexpisTrueif valuevis a member of the set value ofsexp,
each(v1 in sexp1, ..., vk in sexpk, has= bexp)
- existential quantification: It evaluates to
Trueiff for some combinations of values of variables that satisfy all membership clausesvi in sexpi, the conditionbexpevaluates toTrue,
some(v1 in sexp1, ..., vk in sexpk, has= bexp)
- binary expression with a comparison operator:
==,!=,<,<=,>,>= - global constraint
alldiff.alldiffis a builtin constraint of one of the form:
-
alldiff(sexp), -
alldiff(exp, v1 in sexp1, ..., vk in sexpk, bexp).It evaluates to
Trueiff -
all items in
sexp, for the first case -
all items in the set of values of
expfor all combinations of values of variables that satisfy all membership clausesvi in sexpiand conditionbexp, for the second casetake pairwisely different values.
- logical operators (
and,or,not) appliedbexp's
The return value of a constraint problem is specified in the form of a return statement. As described above, CSPs and COPs are different so their returns are in different forms:
- CSP:
anyof(variables, c_1, ..., c_k) - COP:
anyof(variables, c_1, ..., c_k, opt(objective))
variablesis a decision variable or a tuple of decision variables whose values are to be returned from solving the constraint problem.c_1, ..., c_kare names of constraints to be satisfied.opt(objective)specifies the desired optimization of the objective valueobjective, whereoptcan beto_maxorto_min, denoting maximization or minimization, respectively.
After specifying the problem, call the following function to solve the problem:
query(constraint, **kwargs)-
constraintis the name of the constraint problem -
kwargsare keyword arguments for passing in values of parameters of the constraint problem. The keys are names of parameters -
When passing in an argument, if the argument name is
- in the same scope as calling
query, or - an instance variable in a class, or
- a global variable,
that argument can be omitted from the call.
- in the same scope as calling
-
The return value of calling
queryis a dictionary, whose keys are names of decision variables in the return of the constraint problem. If the problem is a COP, an additional keyobjectiveis also returned if not already specified in the return, and is the optimal value of the objective. Additionally, the statistics of solving the problem are also returned under keystatistics. Detailed explanation of all items in the statistics can be found here.
Usage: python -m da --constraints <filename>
Save the example program to a file, say named vertex_cover.da, then running following command
>>> python -m da --constraints vertex_cover.da
produces the output:
[0, 1, 1, 0, 0, 0]
2Some examples are given in the examples/constraints directory.
