Initializing help system before first use

Problem Creation Reference

There are two possibilities to input a problem into the FICO Xpress optimizer from R. The problem can be either incrementally formulated, for example by specifying each row separately, or by loading the entire problem at once. The FICO Xpress R interface provides a convenience function for loading an entire problem at once, which accepts R matrices as input for the various matrices that may describe the linear and quadratic constraint parts.

The xprs_loadproblemdata function accepts as input a list of named properties to declare all input at once. Internally, it uses the Xpress functions XPRSloadmip, XPRSloadmiqp etc. to translate the problem data into the internal formulation. The advantage of using a problem data object is that it lets the R user input matrices instead of sparse matrix representations for declaring linear and quadratic terms in the objective and constraints.

This page illustrates the two methods of formulating optimization problems for the FICO Xpress optimizer using an example of a mixed integer problem (MIP) and a mixed-integer quadratically constrained optimization problem (MIQCQP). As to the method that loads problem data at once, different ways of constructing matrices are discussed as well.

This page also shows the input of problems that contain some special constraints or variables, such as indicator constraints and semi-continuous variables, etc. It serves as a quick guide for the different elements of optimization problems and the interface functions to declare them.

Mixed Integer Programs (MIP)

We assume familiarity with the basic concepts of mathematical programming and its constraint notation. A mixed integer program is an optimization problem of the form

\begin{aligned}[t] &\min & cx\\ & \text{s.t.}& Ax &\{=, \leq, \geq\} b\\ & & \ell &\leq x \leq u\\ & & x_j &\in \{0, 1\} & \forall j \in \mathcal{B} \\ & & x_j &\in \mathbb{Z} & \forall j \in \mathcal{I}\\ & & \\ \end{aligned}

The Problem Data Representation

We use a simple MIP example to illustrate the two methods of loading problem data into the FICO Xpress optimizer.

\begin{aligned}[t] &\min & 2x_1 & +x_2 + 3x_3\\ & \text{s.t.}& 3x_1 & +2x_3 \geq\ 5\\ & & 6x_2 & -7x_3 \geq\ 8\\ & & & x_1\in \{0, 1\}\\ & & & x_2, x_3 \in \mathbb{Z} \\ & & 0 \leq x_2 &< \infty, 0 \leq x_3 < \infty\\ & & \\ \end{aligned}

The first way to input a problem into the FICO Xpress optimizer is to load the entire data at once using the function xprs_loadproblemdata. To realize this, we can write the constraint coefficients into an R matrix (in different ways, see next section), and then use the xprs_loadproblemdata function to load all data. Here we write the matrix in the example as a dense matrix and show the usage of xprs_loadproblemdata.

# create a list to store the problem data
problemdata <- list()

# write the constraint coefficients into a dense matrix
coefmatrix <- matrix(c(3, 0, 2, 0, 6,-7), nrow = 2, byrow = TRUE)
# load the matrix into the list as row coefficients
problemdata$A <- coefmatrix

# objective coefficients
problemdata$objcoef <- c(2, 1, 3)

# right-hand-side of the constraints
problemdata$rhs <- c(5, 8)

# row sense
problemdata$rowtype <- c("G", "G")

# specify all column types, where 'B' means 'binary' and 'I' means 'integer'
problemdata$coltype <- c('B', 'I', 'I')

#specify the indices of the binary and integer variables (use 0-based indexing)
problemdata$entind <- 0:2

# specify lower bounds and upper bounds for the columns
problemdata$lb <- c(0, 0, 0)
problemdata$ub <- c(1, Inf, Inf)

# specify the column names
problemdata$colname <- c("x_1", "x_2", "x_3")

# specify the row names
problemdata$rowname <- c("row1", "row2")

# problem name displayed when the solver solves the problem
problemdata$probname <- "MIPexample"

# load the problemdata into a problem 'p'
p <- xprs_loadproblemdata(problemdata = problemdata)
print(p)
# an alternative and equivalent way to do this is:
# p <- createprob()
# xprs_loadproblemdata(p, problemdata = problemdata)
## XPRESS problem object MIPexample
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
# use `xprs_optimize` to solve the problem and see the results
summary(xprs_optimize(p))
print(data.frame(Variable = problemdata$colname, Value = getsolution(p)$x))
##
## Final MIP objective                   :                    8
## Final MIP bound                       :                    8
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         2 /        0
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     1
## 2      x_2     3
## 3      x_3     1

Working With R Matrices

There are several ways to represent the matrices in R according to the nature of the matrices. For low-dimensional matrices with mostly nonzero elements, we can use a dense specification as shown above, where every entry is specified.

Most high-dimensional matrices occurring in optimization problems are usually very sparse, i.e., most of the coefficients are zero. This fact is widely used in sparse matrix representations, which only store the nonzero coefficients of each column of a matrix together with an index into the rows that correspond to those nonzero entries. Using a sparse representation for such matrices saves a lot of memory. As a rule of thumb, we create sparse matrices when the problem data is too large to store every single of the ROWS \( \times \) COLS coefficients, or the matrix contains mostly 0 coefficients.

There are many ways to create sparse matrices. Here we still use the same MIP example to illustrate some possible methods to construct sparse matrices.

The first method only works for small-sized matrices because at creation we actually specify every element of the matrix.

We can always convert a sparse matrix back to a dense matrix:

# create a sparse matrix
coefmatrix_sparse1 <-
  Matrix(c(3, 0, 2, 0, 6,-7),
         nrow = 2,
         byrow = TRUE,
         sparse = TRUE)
coefmatrix_sparse1

# a sparse matrix can be converted to a dense matrix
coefmatrix_dense <- as.matrix(coefmatrix_sparse1)
print("The sparse matrix can be converted into a dense matrix:")
coefmatrix_dense
## 2 x 3 sparse Matrix of class "dgCMatrix"
##
## [1,] 3 .  2
## [2,] . 6 -7
## [1] "The sparse matrix can be converted into a dense matrix:"
##      [,1] [,2] [,3]
## [1,]    3    0    2
## [2,]    0    6   -7

In the second chunk, we first create a sparse matrix containing only 0’s, and then assign the nonzero elements:

# first create a sparse matrix containing only 0 values
coefmatrix_sparse2 <- Matrix(0, nrow = 2, ncol = 3, byrow = TRUE, sparse = TRUE,
                             doDiag = FALSE)

# then assign the nonzero values
coefmatrix_sparse2[1, c(1, 3)] <- c(3, 2)
coefmatrix_sparse2[2, 2:3] <- c(6, -7)
coefmatrix_sparse2
## 2 x 3 sparse Matrix of class "dgCMatrix"
##
## [1,] 3 .  2
## [2,] . 6 -7

The third one, which creates a sparse matrix in triplet format:

# create a sparse matirx in triplet format
coefmatrix_sparse3 <-
  sparseMatrix(
    i = c(1, 1, 2, 2),
    j = c(1, 3, 2:3),
    x = c(3, 2, 6,-7),
    dims = c(2, 3)
  )
coefmatrix_sparse3
## 2 x 3 sparse Matrix of class "dgCMatrix"
##
## [1,] 3 .  2
## [2,] . 6 -7

Incremental Formulation

Another way to input problem data into Xpress optimizer is to add the problem data incrementally. We can use the convenience functions xprs_addrow and xprs_addcol, which are exclusive to the R interface, or addrows and addcols to realize this. One difference between these two pairs of functions is that, for xprs_addrow and xprs_addcol, row type, row name, column type and column name can be specified inside these functions, whereas for addrows and addcols, row type can be defined inside function addrows, the other three features need to be specified using chgcoltype and addnames. Another difference is that xprs_addrow and xprs_addcol only add a single row and a single column, while addrows and addcols can add multiple rows and columns.

Of course, it is possible to mix the use of these functions to formulate problems.

Firstly we show how to use xprs_addcol and xprs_addrow to incrementally add the data.

# firstly, create a new empty problem 'prob1'
prob1 <- createprob()

# add the columns
xprs_addcol(prob1, lb = 0, ub = 1, coltype = 'B', name = "x_1", objcoef = 2)
xprs_addcol(prob1, lb = 0, ub = Inf, coltype = 'I', name = "x_2", objcoef = 1)
xprs_addcol(prob1, lb = 0, ub = Inf, coltype = 'I', name = "x_3", objcoef = 3)

# add the rows
xprs_addrow(prob1,
       rowtype = "G",
       rhs = 5,
       name = "row1",
       colind = c(0, 2),
       rowcoef = c(3, 2),
       )

xprs_addrow(prob1,
       rowtype = "G",
       rhs = 8,
       name = "row2",
       colind = c(1, 2),
       rowcoef = c(6, -7),
       )

# specify the name of the problem
setprobname(prob1, "MIPexample")
# show the problem created
print(prob1)
## XPRESS problem object MIPexample
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons

Next we show how to use addrows and addcols to incrementally add the problem data.

# firstly, create a new empty problem 'prob2'
prob2 <- createprob()

# add the columns
addcols(prob2,
        objcoef = c(2, 1, 3),
        start = c(0, 1, 2),
        rowind = NULL,
        rowcoef = NULL,
        lb = c(0, 0, 0),
        ub = c(1, Inf, Inf))

# specify the column types
chgcoltype(prob2, c(0, 1, 2), c('B', 'I', 'I'), ncols = 3)

# specify the column names
addnames(prob2, 2, c("x_1", "x_2", "x_3"), 0, 2)

# add the rows
addrows(prob2,
       rowtype = "G",
       rhs = 5,
       start = 0,
       colind = c(0, 2),
       rowcoef = c(3, 2)
       )
addrows(prob2,
       rowtype = "G",
       rhs = 8,
       start= 0,
       colind = c(1, 2),
       rowcoef = c(6, -7)
       )

# specify the row names
addnames(prob2, 1, c("row1", "row2"), 0, 1)

# specify the name of the problem
setprobname(prob2, "MIPexample")
# show the problem created
print(prob2)
## XPRESS problem object MIPexample
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons

Mixed Integer Quadratically Constrained Programs (MIQCQP)

Mixed Integer Quadratically Constrained Programming (MIQCQP) problems are an extension of Mixed Integer Programming (MIP) problems where the objective function and constraints may include a second order polynomial.

\begin{aligned}[t] &\min & \frac{1}{2} x^t Q_0 x + cx &\\ & \text{s.t.}& Ax + Q(x)&\{=, \leq, \geq\} b\\ & & \ell \leq x &\leq u\\ & & x_j &\in \{0, 1\} & \forall j \in \mathcal{B} \\ & & x_j &\in \mathbb{Z} & \forall j \in \mathcal{I}\\ & & \\ \end{aligned}

The function \( Q:\mathbb{R}^n \rightarrow \mathbb{R}^m \), \( Q(x) := (x^t Q_1x, x^t Q_2x, \dots, x^t Q_m x)^t \) is shorthand for the quadratic parts of the constraints.

The sets \( \mathcal{B} \) and \( \mathcal{I} \) denote the subsets of the columns (of \( A \)) that are restricted to binary or integer values, respectively. There are also some more advanced column types such as semi-continuous, semi-integer, and partial integer columns that are explained below.

The Problem Data Representation

As for MIP, there are two possibilities to input a problem into the FICO Xpress optimizer: loading at once and incremental formulation. To illustrate this, we use the example below, which extends the MIP example by some quadratic terms.

\begin{aligned}[t] &\min & \ x_1^2 + 2x_3^2+2x_1+x_2 -3x_3\\ & \text{s.t.}& 3x_1 + 2x_3^2 \leq\ 5\\ & & x_1^2 -7x_3 \leq\ 8\\ & & x_1\in \{0, 1\}\\ & & x_2, x_3 \in \mathbb{Z} \\ & & 0 \leq x_2 < \infty, 0 \leq x_3 < \infty\\ & & \\ \end{aligned}

In this example, the matrix \( Q_0 \) in the objective is: \begin{aligned}[t] && Q_0 = \begin{bmatrix} 2 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 4\end{bmatrix} & & \\ \end{aligned}

The matrix \( Q_1 \) in the first row is: \begin{aligned}[t] && Q_1 = \begin{bmatrix} 0 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 2\end{bmatrix} & & \\ \end{aligned}

The matrix \( Q_2 \) in the second row is:

\begin{aligned}[t] && Q_2 = \begin{bmatrix} 1 & 0 & 0 \\ 0 & 0 & 0 \\ 0 & 0 & 0\end{bmatrix} & & \\ \end{aligned}

The first way we load the problem into the FICO Xpress optimizer at once is to use the function xprs_loadproblemdata.

qproblemdata <- list()

# objective coefficients
qproblemdata$objcoef <- c(2, 1, -3)

# write the constraint coefficients into a dense matrix
coefmatrix <- matrix(c(3, 0, 0, 0, 0,-7), nrow = 2, byrow = TRUE)
# load the matrix into the list as row coefficients
qproblemdata$A <- coefmatrix

# right-hand-side of the constraints
qproblemdata$rhs <- c(5, 8)

# row sense
qproblemdata$rowtype <- c("L", "L")

# specify all column types, where 'B' means 'binary' and 'I' means 'integer'
qproblemdata$coltype <- c('B', 'I', 'I')

#specify the indices of the binary and integer variables (use 0-based indexing)
qproblemdata$entind <- 0:2

# specify lower bounds and upper bounds for the columns
qproblemdata$lb <- c(0, 0, 0)
qproblemdata$ub <- c(1, Inf, Inf)

# specify the column names
qproblemdata$colname <- c("x_1", "x_2", "x_3")

# specify the row names
qproblemdata$rowname <- c("row1", "row2")

# problem Name displayed when the solver solves the problem
qproblemdata$probname <- "MIQCQPexample"


# # quadratic part in objective
# # 1.C-Style notation
# qproblemdata$nobjqcoef <- 2
# qproblemdata$objqcol1 <- c(0, 2)
# qproblemdata$objqcol2 <- c(0, 2)
# qproblemdata$objqcoef <- c(2, 4)

# 2.Matrix notation
qproblemdata$Qobj <-
  Matrix(
    c(2, 0, 0, 0, 0, 0, 0, 0, 4),
    nrow = 3,
    byrow = TRUE,
    sparse = TRUE,
    doDiag = FALSE
  )


# # quadratic part in constraints
# # 1.C-Style notation
# qproblemdata$nqrows <- 2
# qproblemdata$qrowind <- c(0, 1)
# qproblemdata$nrowqcoefs <- c(1, 1)
# qproblemdata$rowqcol1 <- c(2, 0)
# qproblemdata$rowqcol2 <- c(2, 0)
# qproblemdata$rowqcoef <- c(2, 1)

# 2.Matrix notation
qproblemdata$Qrowlist <- list()
qproblemdata$Qrowlist[[1]] <-
  Matrix(
    c(0, 0, 0, 0, 0, 0, 0, 0, 2),
    nrow = 3,
    byrow = TRUE,
    sparse = TRUE,
    doDiag = FALSE
  )
qproblemdata$Qrowlist[[2]] <-
  Matrix(
    c(1, 0, 0, 0, 0, 0, 0, 0, 0),
    nrow = 3,
    byrow = TRUE,
    sparse = TRUE,
    doDiag = FALSE
  )


# load the problemdata into a problem 'qcqp'
qcqp <- xprs_loadproblemdata(problemdata = qproblemdata)
## 'as(<dsCMatrix>, "dgTMatrix")' is deprecated.
## Use 'as(as(., "generalMatrix"), "TsparseMatrix")' instead.
## See help("Deprecated") and help("Matrix-deprecated").
print(qcqp)
# an alternative and equivalent way to do this is:
# p <- createprob()
# xprs_loadproblemdata(p, problemdata=qproblemdata)
## XPRESS problem object MIQCQPexample
##      2 rows         3 cols        2 elems
##      3 entities      0 sets        0 indicators
##      2 qelems        2 qcelems
##      0 gencons       0 pwlcons
# use `xprs_optimize` to solve the problem and see the results
summary(xprs_optimize(qcqp))
print(data.frame(Variable = qproblemdata$colname, Value = getsolution(qcqp)$x))
##
## Final MIP objective                   :                   -1
## Final MIP bound                       :                   -1
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     0
## 2      x_2     0
## 3      x_3     1

Working With R Matrices

Like for the linear part of the constraints (matrix \( A \)) and the quadratic objective matrix and the quadratic terms in constraints can be specified as matrices in sparse or dense representation.

In the above section, we specify all the matrices as dense matrices, and now we show how to construct them as sparse matrices.

The first way:

# create sparse matrix
qobjmat_sparse1 <-
  Matrix(
    c(2, 0, 0, 0, 0, 0, 0, 0, 4),
    nrow = 3,
    byrow = TRUE,
    sparse = TRUE
  )
qobjmat_sparse1

qrowmat1_sparse1 <-
  Matrix(
    c(0, 0, 0, 0, 0, 0, 0, 0, 2),
    nrow = 3,
    byrow = TRUE,
    sparse = TRUE
  )
qrowmat1_sparse1

qrowmat2_sparse1 <-
  Matrix(
    c(1, 0, 0, 0, 0, 0, 0, 0, 0),
    nrow = 3,
    byrow = TRUE,
    sparse = TRUE
  )
qrowmat2_sparse1
## 3 x 3 diagonal matrix of class "ddiMatrix"
##      [,1] [,2] [,3]
## [1,]    2    .    .
## [2,]    .    0    .
## [3,]    .    .    4
## 3 x 3 diagonal matrix of class "ddiMatrix"
##      [,1] [,2] [,3]
## [1,]    0    .    .
## [2,]    .    0    .
## [3,]    .    .    2
## 3 x 3 diagonal matrix of class "ddiMatrix"
##      [,1] [,2] [,3]
## [1,]    1    .    .
## [2,]    .    0    .
## [3,]    .    .    0

The second way:

# first create sparse matrices containing only 0 values
qobjmat_sparse2 <-
  Matrix(
    0,
    nrow = 3,
    ncol = 3,
    byrow = TRUE,
    sparse = TRUE,
    doDiag = FALSE
  )
qrowmat1_sparse2 <-
  Matrix(
    0,
    nrow = 3,
    ncol = 3,
    byrow = TRUE,
    sparse = TRUE,
    doDiag = FALSE
  )
qrowmat2_sparse2 <-
  Matrix(
    0,
    nrow = 3,
    ncol = 3,
    byrow = TRUE,
    sparse = TRUE,
    doDiag = FALSE
  )

# then assign the nonzero values
qobjmat_sparse2[1, 1] <- 2
qobjmat_sparse2[3, 3] <- 4

qrowmat1_sparse2[3, 3] <- 2

qrowmat2_sparse2[1, 1] <- 1

qobjmat_sparse2
qrowmat1_sparse2
qrowmat2_sparse2
## 3 x 3 sparse Matrix of class "dsCMatrix"
##
## [1,] 2 . .
## [2,] . . .
## [3,] . . 4
## 3 x 3 sparse Matrix of class "dsCMatrix"
##
## [1,] . . .
## [2,] . . .
## [3,] . . 2
## 3 x 3 sparse Matrix of class "dsCMatrix"
##
## [1,] 1 . .
## [2,] . . .
## [3,] . . .

The third way:

# create sparse matrices in triplet format
qobjmat_sparse3 <-
  sparseMatrix(
    i = c(1, 3),
    j = c(1, 3),
    x = c(2, 4),
    dims = c(3, 3)
  )
qobjmat_sparse3

qrowmat1_sparse3 <-
  sparseMatrix(
    i = 3,
    j = 3,
    x = 2,
    dims = c(3, 3)
  )
qrowmat1_sparse3

qrowmat2_sparse3 <-
  sparseMatrix(
    i = 1,
    j = 1,
    x = 1,
    dims = c(3, 3)
  )
qrowmat2_sparse3
## 3 x 3 sparse Matrix of class "dgCMatrix"
##
## [1,] 2 . .
## [2,] . . .
## [3,] . . 4
## 3 x 3 sparse Matrix of class "dgCMatrix"
##
## [1,] . . .
## [2,] . . .
## [3,] . . 2
## 3 x 3 sparse Matrix of class "dgCMatrix"
##
## [1,] 1 . .
## [2,] . . .
## [3,] . . .

Incremental Formulation

We can also formulate the MIQCQP example incrementally, and same as MIP, we can use the functions xprs_addrow and xprs_addcol, or addrows and addcols to realize this. The differences between these two pairs of functions are described in the MIP section. Here, we only show the usage of the convenience functions xprs_addrow and xprs_addcol.

As for the quadratic terms, we always use chgmqobj to add quadratic terms to the objective and addqmatrix to add quadratic terms to constraints.

# firstly, create a new empty problem 'qcqp1'
qcqp1 <- createprob()

# add the columns
xprs_addcol(qcqp1, lb = 0, ub = 1, coltype = 'B', name = "x_1", objcoef = 2)
xprs_addcol(qcqp1, lb = 0, ub = Inf, coltype = 'I', name = "x_2", objcoef = 1)
xprs_addcol(qcqp1, lb = 0, ub = Inf, coltype = 'I', name = "x_3", objcoef = -3)

# add the quadratic terms in objective
# since the quadratic matrix is assumed to be symmetric, so specifying the upper
# diagonal part of the matrix is enough, and the off-diagonal coefficients can be
# specified as they are.
chgmqobj(qcqp1, c(0, 2), c(0, 2), c(2, 4), 2)

# add the rows
xprs_addrow(qcqp1,
       rowtype = "L",
       rhs = 5,
       name = "row1",
       colind = 0,
       rowcoef = 3,
       )
xprs_addrow(qcqp1,
       rowtype = "L",
       rhs = 8,
       name = "row2",
       colind = 2,
       rowcoef = -7,
       )

# add quadratic terms in the constraints
addqmatrix(qcqp1,
           row = 0,
           rowqcol1 = 2,
           rowqcol2 = 2,
           rowqcoef = 2
           )
addqmatrix(qcqp1,
           row = 1,
           rowqcol1 = 0,
           rowqcol2 = 0,
           rowqcoef = 1
           )

# specify the name of the problem
setprobname(qcqp1, "MIQCQPexample")
# show the problem created
print(qcqp1)
## XPRESS problem object MIQCQPexample
##      2 rows         3 cols        2 elems
##      3 entities      0 sets        0 indicators
##      2 qelems        2 qcelems
##      0 gencons       0 pwlcons
# use `xprs_optimize` to solve the problem and see the results
summary(xprs_optimize(qcqp1))
print(data.frame(Variable=c("x_1", "x_2", "x_3"), Value=getsolution(qcqp1)$x))
##
## Final MIP objective                   :                   -1
## Final MIP bound                       :                   -1
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     0
## 2      x_2     0
## 3      x_3     1

Problems With Special Constraints And Variables

In this section, we display some examples that contain special constraints or variables and show how to input them into the FICO Xpress optimizer. To see how these special constraints or variables can have effect on the solutions, we use examples based on the MIP example so that we can compare the new solutions obtained with the MIP solution. For convenience, we create a function load_MIP_example to load the MIP example and we will use this function at the begining of each sub-section and then make adjustments on the MIP example incrementally (adding constraints or changing variable types, etc.).

load_MIP_example = function(){
  # firstly, create a new empty problem 'prob'
  prob <- createprob()

  # add the columns
  xprs_addcol(prob, lb = 0, ub = 1, coltype = 'B', name = "x_1", objcoef = 2)
  xprs_addcol(prob, lb = 0, ub = Inf, coltype = 'I', name = "x_2", objcoef = 1)
  xprs_addcol(prob, lb = 0, ub = Inf, coltype = 'I', name = "x_3", objcoef = 3)

  # add the rows
  xprs_addrow(prob,
              rowtype = "G",
              rhs = 5,
              name = "row1",
              colind = c(0, 2),
              rowcoef = c(3, 2),
              )

  xprs_addrow(prob,
              rowtype = "G",
              rhs = 8,
              name = "row2",
              colind = c(1, 2),
              rowcoef = c(6, -7),
              )
  # return 'prob'
  prob
}

Notice that for the constraints and special variables that will be discussed in this section, ‘Indicator Constraints’, ‘General Constraints’ and ‘Piecewise Linear Constraints’ cannot be specified when using the function xprs_loadproblemdata to load the problem at once. Instead, they need to be added using corresponding functions.

Indicator Constraints

Indicator constraints are constraints each with a specified associated binary ‘controlling’ variable where we assume the constraint must be satisfied when the binary variable is at a specified binary value; otherwise the constraint does not need to be satisfied.

We use an extension of the MIP example to show the formulation of problems with indicator constraints. Based on the MIP example, we add the constraint that if the binary variable \( x_1 \) takes value 1, then integer variable \( x_3 \) must be 0. Mathematically, we want to impose the implication:

\begin{aligned}[t] x_1 = 1 \Rightarrow x_3 = 0 \end{aligned}

To add this constraint, we first add a new row \( x_3 = 0 \), and then use the function setindicators to specify the indicator constraint.

# firstly, load the MIP example to a problem 'probIndicator'
probIndicator <- load_MIP_example()

# add the new row x_3 = 0
xprs_addrow(probIndicator,
       rowtype = "E",
       rhs = 0,
       name = "row3",
       colind = 2,
       rowcoef = 1,
       )

# add the indicator implication between variable x_1 and the new row
setindicators(probIndicator,
  rowind = 2,
  colind = 0,
  complement = 1 # row is active when x_1 = 1
  )

# specify the name of the problem
setprobname(probIndicator, "IndicatorConstraintExample")

Now we solve this example. We see that the solution satisfies the indicator constraint, where \( x_1 \) takes 0 and thus \( x_3 \) can take nonzero value 3.

# show the problem created
print(probIndicator)
summary(xprs_optimize(probIndicator))
print(data.frame(
  Variable = c("x1", "x2", "x3"),
  Value = getsolution(probIndicator)$x
))
## XPRESS problem object IndicatorConstraintExample
##      3 rows         3 cols        5 elems
##      3 entities      0 sets        1 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
##
## Final MIP objective                   :                   14
## Final MIP bound                       :                   14
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1       x1     0
## 2       x2     5
## 3       x3     3

General Constraints

General constraints are specific type of MIP constraints to model min, max, and, or, and absolute value relationships between two or more variables.

Here we still use the MIP example and add a general constraint that \( x_3 \) takes the minimum value between 2 and \( x_2 \), mathematically, we require:

\begin{aligned}[t] x_3 = \min \left\{ x_2, 2 \right\} \end{aligned}

We use the function addgencons to add this general constraint:

# firstly, load the MIP example to a problem 'probGeneral'
probGeneral <- load_MIP_example()

# set the general constraint:
addgencons(probGeneral,
           contype = 1,
           resultant = 2,
           colstart = 0,
           colind = 1,
           valstart = 0,
           val = 2)

# specify the name of the problem
setprobname(probGeneral, "GeneralConstraintExample")

Now we solve this example. We see that the solution satisfies the general constraint, because \( x_3 \) takes the value \( \min \left\{ x_2=4, \ 2 \right\}=2 \).

# show the problem created
print(probGeneral)
summary(xprs_optimize(probGeneral))
print(data.frame(
  Variable = c("x1", "x2", "x3"),
  Value = getsolution(probGeneral)$x
))
## XPRESS problem object GeneralConstraintExample
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      1 gencons       0 pwlcons
##
## Final MIP objective                   :                   12
## Final MIP bound                       :                   12
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1       x1     1
## 2       x2     4
## 3       x3     2

Piecewise Linear Constraints

Piecewise linear constraints are constraints that define a piecewise linear relationship between two variables. These are defined via a set of breakpoints with linearly interpolated values between and beyond them (with the slope before the first and after the last point continuing the slope between the first/last two points). The piece-wise linear functions are allowed to be discontinuous by defining multiple points with the same value of the input variable x, in which case the output variable y is allowed to take any value between the corresponding y-values of these breakpoints, while the first of them will define the slope before and the last will define the slope after this x-value.

Based on the MIP example, we add a piecewise linear constraint between \( x_2 \) and \( x_3 \): \( x_2 = f(x_3) \), where:

\begin{aligned}[t] x_2 = f\left( x_3 \right)= \left\{ \begin{array}{lr} 2x_3, & \textsf{if}\ 0\leq x_3\leq 2\\ \frac{1}{2}\ x_3+3, & \textsf{if}\ 2< x_3<\infty \\ \end{array} \right. \end{aligned}

This function can be defined using the breakpoints (0, 0), (2, 4), and (4, 5). Note that the last breakpoint could also be replaced, e.g., by (3, 4.5). We will use the function addpwlcons to add this piecewise linear constraint.

# firstly, load the MIP example to a problem 'probPWL'
probPWL <- load_MIP_example()

# set the piecewise linear constraint:
addpwlcons(probPWL,
           colind = 2,
           resultant = 1,
           start = 0,
           xval = c(0, 2, 4),
           yval = c(0, 4, 5))

# specify the name of the problem
setprobname(probPWL, "PWLExample")

Now we solve this example. We see that the solution satisfies the piecewise linear constraint, where \( x_3=2 \) and thus \( x_2=2x_3=4 \).

# show the problem created
print(probPWL)
summary(xprs_optimize(probPWL))
print(data.frame(
  Variable = c("x1", "x2", "x3"),
  Value = getsolution(probPWL)$x
))
## XPRESS problem object PWLExample
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       1 pwlcons
##
## Final MIP objective                   :                   12
## Final MIP bound                       :                   12
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1       x1     1
## 2       x2     4
## 3       x3     2

Special Ordered Set Of Type 1 (SOS1)

SOS1 is a set of decision variables ordered by a set of specified continuous values (or reference values) of which at most one can take a nonzero value. Note that SOS constraints must be input in sparse row representation.

We illustrate the input of SOS1 by an extension of the MIP example. Here we only allow one of the three decision variables to assume a nonzero value by imposing the restriction

\begin{aligned}[t] \text{SOS1}\left(x_1, x_2, x_3\right) \end{aligned} A mathematically equivalent formulation of this SOS1 restriction requires auxiliary binary variables. We introduce one auxiliary binary variables for each decision variable to indicate whether they are 0 or nonzero. We restrict the sum of these auxiliary binary variables to 1 to ensure only one variable takes nonzero value. Since \( x_1 \) itself is a binary variable, we only need two binary variables \( a_2 \) and \( a_3 \) for \( x_2 \) and \( x_3 \) such that:

\begin{aligned}[t] x_j= 0 \Leftrightarrow a_j = 0 \quad \text{ for } j \in \{2, 3\} \end{aligned} Note that this relationship can only be imposed through a linear constraint if the variable \( x_j \) has a finite upper bound.

Finally, we restrict the sum of these binary variables:

\begin{aligned}[t] x_1 + a_2 + a_3 \leq 1 \end{aligned}

If we add this constraint to the original MIP example, the problem will be infeasible. So we change the row coefficient for \( x_3 \) in the second row “row2” from ‘-7’ to ‘7’, and keep everything else the same.

SOS-type constraints are called “Sets” in Xpress terminology. To add this SOS1 restriction, we use the function addsets.

# firstly, load the MIP example to a problem 'probSOS1'
probSOS1 <- load_MIP_example()

# change the row coefficient of x_3 in the second row from '-7' to 7
chgcoef(probSOS1, 1, 2, 7)

# add the SOS1 set
addsets(probSOS1,
        settype = '1',
        start = 0,
        colind = c(0, 1, 2),
        refval = c(1, 2, 3))

# specify the name of the problem
setprobname(probSOS1, "SOS1Example")

Now we solve this example. We see that the solution satisfies the SOS1 restriction, where only \( x_3 \) takes nonzero value 3.

# show the problem created
print(probSOS1)
summary(xprs_optimize(probSOS1))
print(data.frame(
  Variable = c("x1", "x2", "x3"),
  Value = getsolution(probSOS1)$x
))
## XPRESS problem object SOS1Example
##      2 rows         3 cols        4 elems
##      3 entities      1 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
##
## Final MIP objective                   :                    9
## Final MIP bound                       :                    9
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1       x1     0
## 2       x2     0
## 3       x3     3

SOS1 can also be specified when loading the problem at once, so next we will show how to add SOS1 when using function xprs_loadproblemdata.

# create a list to store the problem data
pSOS1data <- list()

# write the constraint coefficients into a dense matrix
coefmatrix <- matrix(c(3, 0, 2, 0, 6, 7), nrow = 2, byrow = TRUE)
# load the matrix into the list as row coefficients
pSOS1data$A <- coefmatrix

# objective coefficients
pSOS1data$objcoef <- c(2, 1, 3)

# right-hand-side of the constraints
pSOS1data$rhs <- c(5, 8)

# row sense
pSOS1data$rowtype <- c("G", "G")

# specify all column types, where 'B' means 'binary' and 'I' means 'integer'
pSOS1data$coltype <- c('B', 'I', 'I')

#specify the indices of the binary and integer variables (use 0-based indexing)
pSOS1data$entind <- 0:2

# specify lower bounds and upper bounds for the columns
pSOS1data$lb <- c(0, 0, 0)
pSOS1data$ub <- c(1, Inf, Inf)

# specify the column names
pSOS1data$colname <- c("x_1", "x_2", "x_3")

# specify the row names
pSOS1data$rowname <- c("row1", "row2")

# problem Name displayed when the solver solves the problem
pSOS1data$probname <- "SOS1Example"

# specify SOS1
pSOS1data$settype <- '1'
pSOS1data$setstart <- c(0, 3)
pSOS1data$setind <- c(0, 1, 2)
pSOS1data$refval <- c(1, 2, 3)

# load the problemdata into a problem 'pSOS1'
pSOS1 <- xprs_loadproblemdata(problemdata=pSOS1data)
print(pSOS1)
## XPRESS problem object SOS1Example
##      2 rows         3 cols        4 elems
##      3 entities      1 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons

We solve this example and we get the same solution as before.

# use `xprs_optimize` to solve the problem and see the results
summary(xprs_optimize(pSOS1))
print(data.frame(Variable = pSOS1data$colname, Value = getsolution(pSOS1)$x))
##
## Final MIP objective                   :                    9
## Final MIP bound                       :                    9
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     0
## 2      x_2     0
## 3      x_3     3

Special Ordered Set Of Type 2 (SOS2)

SOS2 is a set of variables ordered by a set of specified continuous values (or reference values) of which at most two can be nonzero, and if two are nonzero then they must be consecutive in their ordering. Note that SOS constraints must be input in sparse row representation.

We illustrate the input of SOS2 by an extension of the MIP example as well, here we only allow two consecutive decision variables to take nonzero values and we change the upper bounds of \( x_2 \) and \( x_3 \) both from infinity to 10.

As for SOS1, an alternative, mathematically equivalent representation of an SOS2 constraint, requires auxiliary binary variables \( a_2 \) and \( a_3 \), of which at most can be set to 1: \begin{aligned}[t] x_1 + a_2 + a_3 \leq 1 \end{aligned}

But this time, we set the constraints for each variable in this way:

\begin{aligned}[t] && x_1\leq x_1\cdot1 \\ && x_2\leq (x_1 + a_2)\cdot 10 \\ && x_3\leq (a_2 + a_3)\cdot 10 \end{aligned}

So for example, if the constraint enforces \( a_2 \) to be 1, then \( x_1 \) must be 0, and \( x_2 \) and \( x_3 \) are allowed to take nonzero values, which satisfy the SOS2 restriction.

To add this SOS2 restriction, we use again the function addsets with the appropriate set type.

# firstly, load the MIP example to a problem 'probSOS2'
probSOS2 <- load_MIP_example()

# change the upper bounds for both x_2 and x_3
chgbounds(probSOS2,
          colind = c(1, 2),
          bndtype = c('U', 'U'),
          bndval = c(10, 10))

# add the SOS2 set
addsets(probSOS2,
        settype = '2',
        start = 0,
        colind = c(0, 1, 2),
        refval = c(1, 2, 3))

# specify the name of the problem
setprobname(probSOS2, "SOS2Example")

Now we solve this example. We see that the solution satisfies the SOS2 restriction, where only \( x_2 \) and \( x_3 \) take nonzero values 5 and 3.

# show the problem created
print(probSOS2)
summary(xprs_optimize(probSOS2))
print(data.frame(
  Variable = c("x_1", "x_2", "x_3"),
  Value = getsolution(probSOS2)$x
))
## XPRESS problem object SOS2Example
##      2 rows         3 cols        4 elems
##      3 entities      1 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
##
## Final MIP objective                   :                   14
## Final MIP bound                       :                   14
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     0
## 2      x_2     5
## 3      x_3     3

SOS2 can also be specified when loading the problem at once, so next we will show how to add SOS2 when using function xprs_loadproblemdata.

# create a list to store the problem data
pSOS2data <- list()

# write the constraint coefficients into a dense matrix
coefmatrix <- matrix(c(3, 0, 2, 0, 6,-7), nrow = 2, byrow = TRUE)
# load the matrix into the list as row coefficients
pSOS2data$A <- coefmatrix

# objective coefficients
pSOS2data$objcoef <- c(2, 1, 3)

# right-hand-side of the constraints
pSOS2data$rhs <- c(5, 8)

# row sense
pSOS2data$rowtype <- c("G", "G")

# specify all column types, where 'B' means 'binary' and 'I' means 'integer'
pSOS2data$coltype <- c('B', 'I', 'I')

#specify the indices of the binary and integer variables (use 0-based indexing)
pSOS2data$entind <- 0:2

# specify lower bounds and upper bounds for the columns
pSOS2data$lb <- c(0, 1, 1)
pSOS2data$ub <- c(1, 10, 10)

# specify the column names
pSOS2data$colname <- c("x_1", "x_2", "x_3")

# specify the row names
pSOS2data$rowname <- c("row1", "row2")

# problem Name displayed when the solver solves the problem
pSOS2data$probname <- "SOS2Example"

# specify SOS2
pSOS2data$settype <- '2'
pSOS2data$setstart <- c(0, 3)
pSOS2data$setind <- c(0, 1, 2)
pSOS2data$refval <- c(1, 2, 3)

# load the problemdata into a problem 'p'
pSOS2 <- xprs_loadproblemdata(problemdata = pSOS2data)
print(pSOS2)
## XPRESS problem object SOS2Example
##      2 rows         3 cols        4 elems
##      3 entities      1 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons

We solve this example and we get the same solution as before.

# use `xprs_optimize` to solve the problem and see the results
summary(xprs_optimize(pSOS2))
print(data.frame(
  Variable = c("x_1", "x_2", "x_3"),
  Value = getsolution(pSOS2)$x
))
##
## Final MIP objective                   :                   14
## Final MIP bound                       :                   14
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     0
## 2      x_2     5
## 3      x_3     3

Semi-Continuous Variables

Semi-continuous variables are decision variables that either have value 0, or a continuous value above a specified nonnegative limit. To show how we specify semi-continuous variables when formulating problems, we use the MIP example but change the variable type of \( x_3 \) from integer to semi-continuous, and set the nonzero limit of \( x_3 \) as 2:

\begin{aligned}[t] &\min & 2x_1 & +x_2 + 3x_3\\ & \text{s.t.}& 3x_1 & +2x_3 \geq\ 5\\ & & 6x_2 & -7x_3 \geq\ 8\\ & & & x_1\in \{0, 1\}\\ & & & x_2 \in \mathbb{Z}\ , 0 \leq x_2 < \infty \\ & & & x_3\in \mathcal{S}, \ x_3=0 \ \text{or } \ 2\leq x_3 < \infty \ \ and\ \ x_3 \in \mathbb{R}\\ & & \\ \end{aligned}

Firstly we load the MIP example, then use chgcoltype to change the type of \( x_3 \) and use chglblimit to specify the nonzero limit of \( x_3 \).

# firstly, load the MIP example to a problem 'problemS'
problemS <- load_MIP_example()

# change the type of x_3
chgcoltype(problemS, 2, 'S')

# set the lower bound for semi-continuous variable x_3
chgglblimit(problemS, 2, 2)

# specify the name of the problem
setprobname(problemS, "MIPExampleWithS")

Notice that for setting a semi-continuous variable, we can directly declare its type as ‘S’ when adding the column. Or when we incrementally formulate the problem, we can declare a semi-continuous variable by function xprs_addcol or adcols and set ‘coltype’ as ‘S’.

Now we solve this example. We see that the solution satisfies the semi-continuous restriction on \( x_3 \), whose value changed from 1 to 2.

# show the problem created
print(problemS)
summary(xprs_optimize(problemS))
print(data.frame(
  Variable = c("x_1", "x_2", "x_3"),
  Value = getsolution(problemS)$x
))
## XPRESS problem object MIPExampleWithS
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
##
## Final MIP objective                   :                   12
## Final MIP bound                       :                   12
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     1
## 2      x_2     4
## 3      x_3     2

More functions to interact with semi-continuous variables are: chgbounds, chgcoef, chgobj, delcols and getcoltype.

Semi-continuous Integer Variables

Semi-continuous integer variables are decision variables that either have value 0, or an integer value above a specified nonnegative limit. To show how we specify semi-continuous integer when formulating problems, we use the MIP example but change the variable type of \( x_3 \) from integer to semi-continuous integer, and set the nonzero limit of \( x_3 \) as 2:

\begin{aligned}[t] &\min & 2x_1 & +x_2 + 3x_3\\ & \text{s.t.}& 3x_1 & +2x_3 \geq\ 5\\ & & 6x_2 & -7x_3 \geq\ 8\\ & & & x_1\in \{0, 1\}\\ & & & x_2 \in \mathbb{Z}\ , 0 \leq x_2 < \infty \\ & & & x_3\in \mathcal{R}, \ x_3=0 \ \text{or } \ 2\leq x_3 < \infty \ and \ x_3 \in \mathbb{Z}\\ & & \\ \end{aligned}

Firstly, we load the MIP example, then use chgcoltype to change the type of \( x_3 \) and use chglblimit to specify the nonzero limit of \( x_3 \).

# firstly, load the MIP example to a problem 'problemR'
problemR <- load_MIP_example()

# change the type of x_3
chgcoltype(problemR, 2, 'R')

# set the lower bound for semi-continuous integer variable x_3
chgglblimit(problemR, 2, 2)

# specify the name of the problem
setprobname(problemR, "MIPExampleWithR")

Notice that for setting a semi-continuous integer variable, we can directly declare its type as ‘R’ when adding the column. Or when we incrementally formulate the problem, we can declare a semi-continuous variable by function xprs_addcol or adcols and set ‘coltype’ as ‘R’.

Now we solve this example. We see that the solution satisfies the semi-continuous integer restriction on \( x_3 \), whose value changed from 1 to 2.

# show the problem created
print(problemR)
summary(xprs_optimize(problemR))
print(data.frame(
  Variable = c("x_1", "x_2", "x_3"),
  Value = getsolution(problemR)$x
))
## XPRESS problem object MIPExampleWithR
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
##
## Final MIP objective                   :                   12
## Final MIP bound                       :                   12
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         1 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1     1
## 2      x_2     4
## 3      x_3     2

More functions to interact with semi-continuous integer variables are: chgbounds, chgcoef, chgobj, delcols, and getcoltype.

Partial Integer Variables

Partial integer variables are decision variables that have integer values below a specified limit and continuous values above the limit. To show how we specify partial integer variables when formulating problems, we use a modification of the MIP example where we change the variable type of \( x_2 \) from integer to partial integer, and set the specified limit of \( x_2 \) as 2:

\begin{aligned}[t] &\min & 2x_1 & +x_2 + 3x_3\\ & \text{s.t.}& 3x_1 & +2x_3 \geq\ 5\\ & & 6x_2 & -7x_3 \geq\ 8\\ & & & x_1\in \{0, 1\}\\ & & & x_2 \in \mathcal{P}\ , \ \ x_2\in \mathbb{Z} \ \ \ \text{if} \ 0\leq x_2\leq 2, \ \text{and}\ \ x_2\in \mathbb{R}\ \ \text{if} \ x_2> 2\\ & & & x_3\in \mathbb{Z}, \ 0 \leq x_3 < \infty\\ & & \\ \end{aligned}

Firstly, we load the MIP example, then use chgcoltype to change the type of \( x_2 \) and use chglblimit to specify the nonzero limit of \( x_2 \).

# firstly, load the MIP example to a  problem 'problemP'
problemP <- load_MIP_example()

# change the type of x_2
chgcoltype(problemP, 1, 'P')

# set the specified limit for partial integer variable x_2
chgglblimit(problemP, 1, 2)

# specify the name of the problem
setprobname(problemP, "MIPExampleWithP")

Notice that for setting a partial integer variable, we can directly declare its type as ‘P’ when adding the column. Or when we incrementally formulate the problem, we can declare a partial integer variable by function xprs_addcol or adcols and set ‘coltype’ as ‘P’.

Now we solve this example. We see that the solution satisfies the partial integer restriction on \( x_2 \), whose value changed from 3 to 2.5.

# show the problem created
print(problemP)
summary(xprs_optimize(problemP))
print(data.frame(
  Variable = c("x_1", "x_2", "x_3"),
  Value = getsolution(problemP)$x
))
## XPRESS problem object MIPExampleWithP
##      2 rows         3 cols        4 elems
##      3 entities      0 sets        0 indicators
##      0 qelems        0 qcelems
##      0 gencons       0 pwlcons
##
## Final MIP objective                   :                  7.5
## Final MIP bound                       :                  7.5
## Solution time / primaldual integral   :        0s /     0.00%
## Number of solutions found / nodes     :         3 /        1
## MIPSTATUS: 6
##   Variable Value
## 1      x_1   1.0
## 2      x_2   2.5
## 3      x_3   1.0

More functions to interact with partial integer variables are: chgbounds, chgobj, chgcoef, delcols, and getcoltype.


© 2001-2025 Fair Isaac Corporation. All rights reserved. This documentation is the property of Fair Isaac Corporation (“FICO”). Receipt or possession of this documentation does not convey rights to disclose, reproduce, make derivative works, use, or allow others to use it except solely for internal evaluation purposes to determine whether to purchase a license to the software described in this documentation, or as otherwise set forth in a written software license agreement between you and FICO (or a FICO affiliate). Use of this documentation and the software described in it must conform strictly to the foregoing permitted uses, and no other use is permitted.