6.10 Quadratic Optimization

MOSEK can solve quadratic and quadratically constrained problems, as long as they are convex. This class of problems can be formulated as follows:

(6.40)minimize12xTQox+cTx+cfsubject tolkc12xTQkx+j=0n1ak,jxjukc,k=0,,m1,ljxxjujx,j=0,,n1.

Without loss of generality it is assumed that Qo and Qk are all symmetric because

xTQx=12xT(Q+QT)x.

This implies that a non-symmetric Q can be replaced by the symmetric matrix 12(Q+QT).

The problem is required to be convex. More precisely, the matrix Qo must be positive semi-definite and the kth constraint must be of the form

(6.41)lkc12xTQkx+j=0n1ak,jxj

with a negative semi-definite Qk or of the form

12xTQkx+j=0n1ak,jxjukc.

with a positive semi-definite Qk. This implies that quadratic equalities are not allowed. Specifying a non-convex problem will result in an error when the optimizer is called.

A matrix is positive semidefinite if all the eigenvalues of Q are nonnegative. An alternative statement of the positive semidefinite requirement is

xTQx0,x.

If the convexity (i.e. semidefiniteness) conditions are not met MOSEK will not produce reliable results or work at all.

6.10.1 Example: Quadratic Objective

We look at a small problem with linear constraints and quadratic objective:

(6.42)minimizex12+0.1x22+x32x1x3x2subject to1x1+x2+x30x.

The matrix formulation of (6.42) has:

Qo=[20100.20102],c=[010],A=[111],

with the bounds:

lc=1,uc=,lx=[000] and ux=[]

Please note the explicit 12 in the objective function of (6.40) which implies that diagonal elements must be doubled in Q, i.e. Q11=2 even though 1 is the coefficient in front of x12 in (6.42).

Setting up the linear part

The linear parts (constraints, variables, objective) are set up using exactly the same methods as for linear problems, and we refer to Sec. 6.1 (Linear Optimization) for all the details. The same applies to technical aspects such as defining an optimization task, retrieving the solution and so on.

Setting up the quadratic objective

The quadratic objective is specified using the function Task.putqobj. Since Qo is symmetric only the lower triangular part of Qo is inputted. In fact entries from above the diagonal may not appear in the input.

The lower triangular part of the matrix Qo is specified using an unordered sparse triplet format (for details, see Sec. 15.1.4 (Matrix Formats)):

        qsubi = [0, 1, 2, 2]
        qsubj = [0, 1, 0, 2]
        qval = [2.0, 0.2, -1.0, 2.0]

Please note that

  • only non-zero elements are specified (any element not specified is 0 by definition),

  • the order of the non-zero elements is insignificant, and

  • only the lower triangular part should be specified.

Finally, this definition of Qo is loaded into the task:

        task.putqobj(qsubi, qsubj, qval)

Source code

Listing 6.18 Source code implementing problem (6.42). Click here to download.
import sys, os, mosek

# Since the actual value of Infinity is ignored, we define it solely
# for symbolic purposes:
inf = 0.0

# Define a stream printer to grab output from MOSEK
def streamprinter(text):
    sys.stdout.write(text)
    sys.stdout.flush()


def main():
    # Create a task
    with mosek.Task() as task:
        task.set_Stream(mosek.streamtype.log, streamprinter)
        # Set up and input bounds and linear coefficients
        bkc = [mosek.boundkey.lo]
        blc = [1.0]
        buc = [inf]
        numvar = 3
        bkx = [mosek.boundkey.lo] * numvar
        blx = [0.0] * numvar
        bux = [inf] * numvar
        c = [0.0, -1.0, 0.0]
        asub = [[0], [0], [0]]
        aval = [[1.0], [1.0], [1.0]]

        numvar = len(bkx)
        numcon = len(bkc)

        # Append 'numcon' empty constraints.
        # The constraints will initially have no bounds.
        task.appendcons(numcon)

        # Append 'numvar' variables.
        # The variables will initially be fixed at zero (x=0).
        task.appendvars(numvar)

        for j in range(numvar):
            # Set the linear term c_j in the objective.
            task.putcj(j, c[j])
            # Set the bounds on variable j
            # blx[j] <= x_j <= bux[j]
            task.putvarbound(j, bkx[j], blx[j], bux[j])
            # Input column j of A
            task.putacol(j,                  # Variable (column) index.
                         # Row index of non-zeros in column j.
                         asub[j],
                         aval[j])            # Non-zero Values of column j.
        for i in range(numcon):
            task.putconbound(i, bkc[i], blc[i], buc[i])

        # Set up and input quadratic objective
        qsubi = [0, 1, 2, 2]
        qsubj = [0, 1, 0, 2]
        qval = [2.0, 0.2, -1.0, 2.0]

        task.putqobj(qsubi, qsubj, qval)

        # Input the objective sense (minimize/maximize)
        task.putobjsense(mosek.objsense.minimize)

        # Optimize
        task.optimize()
        # Print a summary containing information
        # about the solution for debugging purposes
        task.solutionsummary(mosek.streamtype.msg)

        prosta = task.getprosta(mosek.soltype.itr)
        solsta = task.getsolsta(mosek.soltype.itr)

        # Output a solution
        xx = task.getxx(mosek.soltype.itr)

        if solsta == mosek.solsta.optimal:
            print("Optimal solution: %s" % xx)
        elif solsta == mosek.solsta.dual_infeas_cer:
            print("Primal or dual infeasibility.\n")
        elif solsta == mosek.solsta.prim_infeas_cer:
            print("Primal or dual infeasibility.\n")
        elif mosek.solsta.unknown:
            print("Unknown solution status")
        else:
            print("Other solution status")


# call the main function
try:
    main()
except mosek.MosekException as e:
    print("ERROR: %s" % str(e.errno))
    if e.msg is not None:
        import traceback
        traceback.print_exc()
        print("\t%s" % e.msg)
    sys.exit(1)
except:
    import traceback
    traceback.print_exc()
    sys.exit(1)

6.10.2 Example: Quadratic constraints

In this section we show how to solve a problem with quadratic constraints. Please note that quadratic constraints are subject to the convexity requirement (6.41).

Consider the problem:

minimizex12+0.1x22+x32x1x3x2subject to1x1+x2+x3x12x220.1x32+0.2x1x3,x0.

This is equivalent to

(6.43)minimize12xTQox+cTxsubject to12xTQ0x+Axb,x0,

where

Qo=[20100.20102],c=[010]T,A=[111],b=1.
Q0=[200.20200.200.2].

The linear parts and quadratic objective are set up the way described in the previous tutorial.

Setting up quadratic constraints

To add quadratic terms to the constraints we use the function Task.putqconk.

        qsubi = [0, 1, 2, 2]
        qsubj = [0, 1, 2, 0]
        qval = [-2.0, -2.0, -0.2, 0.2]

        # put Q^0 in constraint with index 0.

        task.putqconk(0, qsubi, qsubj, qval)

While Task.putqconk adds quadratic terms to a specific constraint, it is also possible to input all quadratic terms in one chunk using the Task.putqcon function.

Source code

Listing 6.19 Implementation of the quadratically constrained problem (6.43). Click here to download.
import sys
import mosek

# Since the actual value of Infinity is ignores, we define it solely
# for symbolic purposes:
inf = 0.0

# Define a stream printer to grab output from MOSEK
def streamprinter(text):
    sys.stdout.write(text)
    sys.stdout.flush()


def main():
    # Create a task
    with mosek.Task() as task:
        # Attach a printer to the task
        task.set_Stream(mosek.streamtype.log, streamprinter)

        # Set up and input bounds and linear coefficients
        bkc = [mosek.boundkey.lo]
        blc = [1.0]
        buc = [inf]

        bkx = [mosek.boundkey.lo,
               mosek.boundkey.lo,
               mosek.boundkey.lo]
        blx = [0.0, 0.0, 0.0]
        bux = [inf, inf, inf]

        c = [0.0, -1.0, 0.0]

        asub = [[0], [0], [0]]
        aval = [[1.0], [1.0], [1.0]]

        numvar = len(bkx)
        numcon = len(bkc)
        NUMANZ = 3
        # Append 'numcon' empty constraints.
        # The constraints will initially have no bounds.
        task.appendcons(numcon)

        #Append 'numvar' variables.
        # The variables will initially be fixed at zero (x=0).
        task.appendvars(numvar)

        #Optionally add a constant term to the objective.
        task.putcfix(0.0)

        for j in range(numvar):
            # Set the linear term c_j in the objective.
            task.putcj(j, c[j])
            # Set the bounds on variable j
            # blx[j] <= x_j <= bux[j]
            task.putvarbound(j, bkx[j], blx[j], bux[j])
            # Input column j of A
            task.putacol(j,                  # Variable (column) index.
                         # Row index of non-zeros in column j.
                         asub[j],
                         aval[j])            # Non-zero Values of column j.

        for i in range(numcon):
            task.putconbound(i, bkc[i], blc[i], buc[i])

        # Set up and input quadratic objective

        qsubi = [0, 1, 2, 2]
        qsubj = [0, 1, 0, 2]
        qval = [2.0, 0.2, -1.0, 2.0]

        task.putqobj(qsubi, qsubj, qval)

        # The lower triangular part of the Q^0
        # matrix in the first constraint is specified.
        # This corresponds to adding the term
        # - x0^2 - x1^2 - 0.1 x2^2 + 0.2 x0 x2

        qsubi = [0, 1, 2, 2]
        qsubj = [0, 1, 2, 0]
        qval = [-2.0, -2.0, -0.2, 0.2]

        # put Q^0 in constraint with index 0.

        task.putqconk(0, qsubi, qsubj, qval)

        # Input the objective sense (minimize/maximize)
        task.putobjsense(mosek.objsense.minimize)

        # Optimize the task
        task.optimize()

        # Print a summary containing information
        # about the solution for debugging purposes
        task.solutionsummary(mosek.streamtype.msg)

        prosta = task.getprosta(mosek.soltype.itr)
        solsta = task.getsolsta(mosek.soltype.itr)

        # Output a solution
        xx = task.getxx(mosek.soltype.itr)

        if solsta == mosek.solsta.optimal:
            print("Optimal solution: %s" % xx)
        elif solsta == mosek.solsta.dual_infeas_cer:
            print("Primal or dual infeasibility.\n")
        elif solsta == mosek.solsta.prim_infeas_cer:
            print("Primal or dual infeasibility.\n")
        elif mosek.solsta.unknown:
            print("Unknown solution status")
        else:
            print("Other solution status")


# call the main function
try:
    main()
except mosek.MosekException as e:
    print("ERROR: %s" % str(e.errno))
    print("\t%s" % e.msg)
    sys.exit(1)
except:
    import traceback
    traceback.print_exc()
    sys.exit(1)