Matrix API#

In this overview of the matrix variable and constraint API in PySCIPOpt we’ll walk through best practices for modelling them and the various information that can be extracted from them.

For the following let us assume that a Model object is available, which is created as follows:

from pyscipopt import Model, quicksum

scip = Model()

This tutorial should only be read after having an understanding of both the Variable object (see the constraint tutorial) and the Constraint object (see the constraint tutorial).

Note

The matrix API is built heavily on numpy. This means that users can use all standard numpy operations that they are familiar with when handling matrix variables and expressions. For example, using the @, matmul, *, +, hstack, vstack, and ** operations work exactly as they do when handling any standard numpy array.

What is a Matrix API?#

The standard approach explained in the variable and constraint tutorials, is to build each variable yourself (storing them in some data structure, e.g., a list or dict, with some loop), and to construct each constraint in a similar manner. That means building up each constraint yourself term by term. This approach is flexible, and still remains the standard, but an increasingly common trend is to view the modelling approach from a vector, matrix, and tensor perspective. That is, directly operate on larger sets of variables and expressions, letting python handle the interaction for each term. For such cases, it is encouraged that users now use the new matrix API!

Matrix Variables#

Matrix variables are added via a single function call. It is important beforehand to know the shape of the new set of variables you want to create, where shape is some tuple or int. Below is an example for creating a 2x2 matrix variable of type continuous with an ub of 8.

x = scip.addMatrixVar(shape, vtype='C', name='x', ub=8)

Note

The name of each variable in the example above becomes x_(indices)

In the case of each kwarg, e.g., vtype and ub, a np.array of explicit values can be passed. In the example above, this means that each variable within the matrix variable can have its own custom information. For example:

x = scip.addMatrixVar(shape, vtype='C', name='x', ub=np.array([[5, 6], [2, 8]]))

Matrix Constraints#

Matrix constraints follow the same logic as matrix variables. They can be constructed quickly and added all at once. The standard variable operators, like sin, exp, sqrt, etc., are applied element-wise. Some examples are provided below (these examples are nonsensical, and there to purely understand the API):

x = scip.addMatrixVar(shape=(2, 2), vtype="B", name="x")
y = scip.addMatrixVar(shape=(2, 2), vtype="C", name="y", ub=5)
z = scip.addVar(vtype="C", name="z", ub=7)

scip.addMatrixCons(x + y <= z)
scip.addMatrixCons(exp(x) + sin(sqrt(y)) == z + y)
scip.addMatrixCons(y <= x @ y)
scip.addMatrixCons(x @ y <= x)
scip.addCons(x.sum() <= 2) # Matrix variables can also appear in standard constraints, if the result expression is type Expr

Note

When creating constraints, one can mix standard variables and values in the same expressions. numpy will then handle this, and broadcast the correct operations. In general this can be viewed as creating an imaginary np.array of the appropriate shape and populating it with the variable / value.

Class Properties#

A MatrixVariable and MatrixConstraint object have all the same getter functions that are in general available for the standard equivalent. An example is provided below for vtype.

x = scip.addVar()
matrix_x = scip.addMatrixVar(shape=(2,2))

x.vtype()
matrix_x.vtype()

The objects are not interchangeable however, when being passed into functions derived from the Model class. That is, there is currently no global support, that the following code runs:

scip.imaginary_function(x) # will always work
scip.imaginary_function(matrix_x) # may have to access each variable manually

Accessing Variables and Constraints#

After creating the matrix variables and matrix constraints, one can always access the individual variables or constraints via their index.

x = scip.addMatrixVar(shape=(2, 2))
assert(isinstance(x, MatrixVariable))
assert(isinstance(x[0][0], Variable))
cons = x <= 2
assert(isinstance(cons, MatrixExprCons))
assert(isinstance(cons[0][0]), ExprCons)

Accessing Solution Values#

After optimizing a model, the solution values of a matrix variable can be accessed in an identical manner to the standard variant. There are two recommended ways to do this.

matrix_var_vals = scip.getVal(x)
sol = scip.getBestSol()
matrix_var_vals = sol[x] # returns a numpy array of values