6.2 From Linear to Conic Optimization¶
In Sec. 6.1 (Linear Optimization) we demonstrated setting up the linear part of an optimization problem, that is the objective, linear bounds, linear equalities and inequalities. In this tutorial we show how to define conic constraints. We recommend going through this general conic tutorial before proceeding to examples with specific cone types.
MOSEK accepts conic constraints in the form
where
\(x\in\real^n`\) is the optimization variable,
\(D\subseteq \real^k\) is a conic domain of some dimension \(k\), representing one of the cone types supported by MOSEK,
\(F\in\real^{k\times n}\) and \(g\in \real^k\) are data which constitute the sequence of \(k\) affine expressions appearing in the rows of \(Fx+g\).
Constraints of this form will be called affine conic constraints, or ACC for short. Therefore in this section we show how to set up a problem of the form
with some number \(p\) of affine conic constraints.
Note that conic constraints are a natural generalization of linear constraints to the general nonlinear case. For example, a typical linear constraint of the form
can be also written as membership in the cone of nonnegative real numbers:
and that naturally generalizes to
for more complicated domains \(\D\) from Sec. 15.11 (Supported domains) of which \(\D=\real_{\geq 0}^d\) is a special case.
6.2.1 Running example¶
In this tutorial we will consider a sample problem of the form
where \(x\in \real^n\) is the optimization variable and \(G\in\real^{k\times n}\), \(h\in\real^k\), \(c\in\real^n\) and \(\gamma\in\real\). We will use the following sample data:
To be explicit, the problem we are going to solve is therefore:
Consulting the definition of a quadratic cone \(\Q\) we see that the conic form of this problem is:
The conic constraint has an affine conic representation \(Fx+g\in\D\) as follows:
Of course by the same logic in the general case the conic form of the problem (6.2) would be
and the ACC representation of the constraint \((\gamma, Gx+h)\in\Q^{k+1}\) would be
Now we show how to add the ACC (6.5). This involves three steps:
storing the affine expressions which appear in the constraint,
creating a domain, and
combining the two into an ACC.
6.2.2 Step 1: add affine expressions¶
To store affine expressions (AFE for short) MOSEK provides a matrix \(\afef\) and a vector \(\afeg\) with the understanding that every row of
defines one affine expression. The API functions with infix afe
are used to operate on \(\afef\) and \(\afeg\), add rows, add columns, set individual elements, set blocks etc. similarly to the methods for operating on the \(A\) matrix of linear constraints. The storage matrix \(\afef\) is a sparse matrix, therefore only nonzero elements have to be explicitly added.
Remark: the storage \(\afef,\afeg\) may, but does not have to be, equal to the pair \(F,g\) appearing in the expression \(Fx+g\). It is possible to store the AFEs in different order than the order they will be used in \(F,g\), as well as store some expressions only once if they appear multiple times in \(Fx+g\). In this first turorial, however, we will for simplicity store all expressions in the same order we will later use them, so that \((\afef,\afeg)=(F,g)\).
In our example we create only one conic constraint (6.5) with three (in general \(k+1\)) affine expressions
Given the previous remark, we initialize the AFE storage as:
Initially \(\afef\) and \(\afeg\) are empty (have 0 rows). We construct them as follows. First, we append a number of empty rows:
// Append empty AFE rows for affine expression storage
task.appendafes(k + 1);
We now have \(\afef\) and \(\afeg\) with 3 rows of zeros and we fill them up to obtain (6.7).
// F matix in sparse form
long[] Fsubi = {1, 1, 2, 2}; // The G matrix starts in F from row 1
int[] Fsubj = {0, 1, 0, 2};
double[] Fval = {1.5, 0.1, 0.3, 2.1};
// Other data
double[] h = {0, 0.1};
double gamma = 0.03;
// Fill in F storage
task.putafefentrylist(Fsubi, Fsubj, Fval);
// Fill in g storage;
task.putafeg(0, gamma);
task.putafegslice(1, k+1, h);
We have now created the matrices from (6.7). Note that at this point we have not defined any ACC yet. All we did was define some affine expressions and place them in a generic AFE storage facility to be used later.
6.2.3 Step 2: create a domain¶
Next, we create the domain to which the ACC belongs. Domains are created with functions with infix domain
. In the case of (6.5) we need a quadratic cone domain of dimension 3 (in general \(k+1\)), which we create with:
// Define a conic quadratic domain
quadDom = task.appendquadraticconedomain(k + 1);
The function returns a domain index, which is just the position in the list of all domains (potentially) created for the problem. At this point the domain is just stored in the list of domains, but not yet used for anything.
6.2.4 Step 3: create the actual constraint¶
We are now in position to create the affine conic constraint. ACCs are created with functions with infix acc
. The most basic variant, Task.appendacc
will append an affine conic constraint based on the following data:
the list
afeidx
of indices of AFEs to be used in the constraint. These are the row numbers in \(\afef,\afeg\) which contain the required affine expressions.the index
domidx
of the domain to which the constraint belongs.
Note that number of AFEs used in afeidx
must match the dimension of the domain.
In case of (6.5) we have already arranged \(\afef,\afeg\) in such a way that their (only) three rows contain the three affine expressions we need (in the correct order), and we already defined the quadratic cone domain of matching dimension 3. The ACC is now constructed with the following call:
// Create the ACC
long[] afeidx = {0, 1, 2};
task.appendacc(quadDom, // Domain index
afeidx, // Indices of AFE rows [0,...,k]
null); // Ignored
This completes the setup of the affine conic constraint.
6.2.5 Example ACC1¶
We refer to Sec. 6.1 (Linear Optimization) for instructions how to set up the objective and linear constraint \(x_0+x_1+x_2=1\). All else that remains is to set up the MOSEK environment, task, add variables, call the solver with Task.optimize
and retrieve the solution with Task.getxx
. Since our problem contains a nonlinear constraint we fetch the interior-point solution. The full code solving problem (6.3) is shown below.
using System;
namespace mosek.example
{
class msgclass : mosek.Stream
{
string prefix;
public msgclass (string prfx)
{
prefix = prfx;
}
public override void streamCB (string msg)
{
Console.Write ("{0}{1}", prefix, msg);
}
}
public class acc1
{
public static void Main ()
{
/* Problem dimensions */
const int n = 3;
const int k = 2;
int i,j;
long quadDom;
// Since the value infinity is never used, we define
// 'infinity' symbolic purposes only
double infinity = 0;
// Create a task object.
using (mosek.Task task = new mosek.Task()) {
// Directs the log task stream to the user specified
// method msgclass.streamCB
task.set_Stream (mosek.streamtype.log, new msgclass (""));
// Create n free variables
task.appendvars(n);
task.putvarboundsliceconst(0, n, mosek.boundkey.fr, -infinity, infinity);
// Set up the objective
double[] c = {2, 3, -1};
int[] cind = {0, 1, 2};
task.putobjsense(mosek.objsense.maximize);
task.putclist(cind, c);
// One linear constraint - sum(x) = 1
task.appendcons(1);
task.putconbound(0, mosek.boundkey.fx, 1.0, 1.0);
for(i = 0; i < n; i++) task.putaij(0, i, 1.0);
// Append empty AFE rows for affine expression storage
task.appendafes(k + 1);
// F matix in sparse form
long[] Fsubi = {1, 1, 2, 2}; // The G matrix starts in F from row 1
int[] Fsubj = {0, 1, 0, 2};
double[] Fval = {1.5, 0.1, 0.3, 2.1};
// Other data
double[] h = {0, 0.1};
double gamma = 0.03;
// Fill in F storage
task.putafefentrylist(Fsubi, Fsubj, Fval);
// Fill in g storage;
task.putafeg(0, gamma);
task.putafegslice(1, k+1, h);
// Define a conic quadratic domain
quadDom = task.appendquadraticconedomain(k + 1);
// Create the ACC
long[] afeidx = {0, 1, 2};
task.appendacc(quadDom, // Domain index
afeidx, // Indices of AFE rows [0,...,k]
null); // Ignored
// Solve the problem
task.optimize();
// Print a summary containing information
// about the solution for debugging purposes
task.solutionsummary(mosek.streamtype.msg);
/* Get status information about the solution */
mosek.solsta solsta = task.getsolsta(mosek.soltype.itr);
switch (solsta)
{
case mosek.solsta.optimal:
// Fetch solution
double[] xx = task.getxx(mosek.soltype.itr); // Interior-point solution.
Console.WriteLine ("Optimal primal solution");
for (j = 0; j < n; ++j)
Console.WriteLine ("x[{0}]: {1}", j, xx[j]);
// Fetch doty dual of the ACC
double[] doty = task.getaccdoty(mosek.soltype.itr, // Interior-point solution.
0); // ACC index
Console.WriteLine ("Dual doty of ACC");
for (j = 0; j < k+1; ++j)
Console.WriteLine ("doty[{0}]: {1}", j, doty[j]);
// Fetch activity of the ACC
double[] activity = task.evaluateacc(mosek.soltype.itr, // Interior-point solution.
0); // ACC index
Console.WriteLine ("Activity of ACC");
for (j = 0; j < n; ++j)
Console.WriteLine ("activity[{0}]: {1}", j, activity[j]);
break;
case mosek.solsta.dual_infeas_cer:
case mosek.solsta.prim_infeas_cer:
Console.WriteLine("Primal or dual infeasibility.\n");
break;
case mosek.solsta.unknown:
Console.WriteLine("Unknown solution status.\n");
break;
default:
Console.WriteLine("Other solution status");
break;
}
}
}
}
}
The answer is
[-0.07838011145615721, 1.1289128998004547, -0.0505327883442975]
The dual values \(\doty\) of an ACC can be obtained with Task.getaccdoty
if required.
// Fetch doty dual of the ACC
double[] doty = task.getaccdoty(mosek.soltype.itr, // Interior-point solution.
0); // ACC index
Console.WriteLine ("Dual doty of ACC");
for (j = 0; j < k+1; ++j)
Console.WriteLine ("doty[{0}]: {1}", j, doty[j]);
6.2.6 Example ACC2 - more conic constraints¶
Now that we know how to enter one affine conic constraint (ACC) we will demonstrate a problem with two ACCs. From there it should be clear how to add multiple ACCs. To keep things familiar we will reuse the previous problem, but this time cast it into a conic optimization problem with two ACCs as follows:
or, using the data from the example:
In other words, we transformed the linear constraint into an ACC with the one-point zero domain.
As before, we proceed in three steps. First, we add the variables and create the storage \(\afef\), \(\afeg\) containing all affine expressions that appear throughout all off the ACCs. It means we will require 4 rows:
// Set AFE rows representing the linear constraint
task.appendafes(1);
task.putafeg(0, -1.0);
for(i = 0; i < n; i++) task.putafefentry(0, i, 1.0);
// F matix in sparse form
long[] Fsubi = {2, 2, 3, 3}; // The G matrix starts in F from row 2
int[] Fsubj = {0, 1, 0, 2};
double[] Fval = {1.5, 0.1, 0.3, 2.1};
// Other data
double[] h = {0, 0.1};
double gamma = 0.03;
task.appendafes(k + 1);
task.putafefentrylist(Fsubi, Fsubj, Fval);
task.putafeg(1, gamma);
task.putafegslice(2, k+2, h);
Next, we add the required domains: the zero domain of dimension 1, and the quadratic cone domain of dimension 3.
// Define domains
zeroDom = task.appendrzerodomain(1);
quadDom = task.appendquadraticconedomain(k + 1);
Finally, we create both ACCs. The first ACCs picks the 0-th row of \(\afef,\afeg\) and places it in the zero domain:
// Create the linear ACC
long[] afeidxZero = {0};
task.appendacc(zeroDom, // Domain index
afeidxZero,// Indices of AFE rows
null); // Ignored
The second ACC picks rows \(1,2,3\) in \(\afef,\afeg\) and places them in the quadratic cone domain:
// Create the quadratic ACC
long[] afeidxQuad = {1, 2, 3};
task.appendacc(quadDom, // Domain index
afeidxQuad, // Indices of AFE rows
null); // Ignored
The completes the construction and we can solve the problem like before:
using System;
namespace mosek.example
{
class msgclass : mosek.Stream
{
string prefix;
public msgclass (string prfx)
{
prefix = prfx;
}
public override void streamCB (string msg)
{
Console.Write ("{0}{1}", prefix, msg);
}
}
public class acc1
{
public static void Main ()
{
/* Problem dimensions */
const int n = 3;
const int k = 2;
int i,j;
long quadDom, zeroDom;
// Since the value infinity is never used, we define
// 'infinity' symbolic purposes only
double infinity = 0;
// Create a task object.
using (mosek.Task task = new mosek.Task()) {
// Directs the log task stream to the user specified
// method msgclass.streamCB
task.set_Stream (mosek.streamtype.log, new msgclass (""));
// Create n free variables
task.appendvars(n);
task.putvarboundsliceconst(0, n, mosek.boundkey.fr, -infinity, infinity);
// Set up the objective
double[] c = {2, 3, -1};
int[] cind = {0, 1, 2};
task.putobjsense(mosek.objsense.maximize);
task.putclist(cind, c);
// Set AFE rows representing the linear constraint
task.appendafes(1);
task.putafeg(0, -1.0);
for(i = 0; i < n; i++) task.putafefentry(0, i, 1.0);
// F matix in sparse form
long[] Fsubi = {2, 2, 3, 3}; // The G matrix starts in F from row 2
int[] Fsubj = {0, 1, 0, 2};
double[] Fval = {1.5, 0.1, 0.3, 2.1};
// Other data
double[] h = {0, 0.1};
double gamma = 0.03;
task.appendafes(k + 1);
task.putafefentrylist(Fsubi, Fsubj, Fval);
task.putafeg(1, gamma);
task.putafegslice(2, k+2, h);
// Define domains
zeroDom = task.appendrzerodomain(1);
quadDom = task.appendquadraticconedomain(k + 1);
// Create the linear ACC
long[] afeidxZero = {0};
task.appendacc(zeroDom, // Domain index
afeidxZero,// Indices of AFE rows
null); // Ignored
// Create the quadratic ACC
long[] afeidxQuad = {1, 2, 3};
task.appendacc(quadDom, // Domain index
afeidxQuad, // Indices of AFE rows
null); // Ignored
// Solve the problem
task.optimize();
// Print a summary containing information
// about the solution for debugging purposes
task.solutionsummary(mosek.streamtype.msg);
/* Get status information about the solution */
mosek.solsta solsta = task.getsolsta(mosek.soltype.itr);
switch (solsta)
{
case mosek.solsta.optimal:
// Fetch solution
double[] xx = task.getxx(mosek.soltype.itr); // Interior-point solution.
Console.WriteLine ("Optimal primal solution");
for (j = 0; j < n; ++j)
Console.WriteLine ("x[{0}]: {1}", j, xx[j]);
// Fetch doty dual of the ACC
double[] doty = task.getaccdoty(mosek.soltype.itr, // Interior-point solution.
1); // ACC index
Console.WriteLine ("Dual doty of ACC");
for (j = 0; j < k+1; ++j)
Console.WriteLine ("doty[{0}]: {1}", j, doty[j]);
// Fetch activity of the ACC
double[] activity = task.evaluateacc(mosek.soltype.itr, // Interior-point solution.
1); // ACC index
Console.WriteLine ("Activity of ACC");
for (j = 0; j < n; ++j)
Console.WriteLine ("activity[{0}]: {1}", j, activity[j]);
break;
case mosek.solsta.dual_infeas_cer:
case mosek.solsta.prim_infeas_cer:
Console.WriteLine("Primal or dual infeasibility.\n");
break;
case mosek.solsta.unknown:
Console.WriteLine("Unknown solution status.\n");
break;
default:
Console.WriteLine("Other solution status");
break;
}
}
}
}
}
We obtain the same result:
[-0.07838011145615721, 1.1289128998004547, -0.0505327883442975]
6.2.7 Summary and extensions¶
In this section we presented the most basic usage of the affine expression storage \(\afef,\afeg\) to input affine expressions used together with domains to create affine conic constraints. Now we briefly point out additional features of his interface which can be useful in some situations for more demanding users. They will be demonstrated in various examples in other tutorials and case studies in this manual.
It is important to remember that \(\afef,\afeg\) has only a storage function and during the ACC construction we can pick an arbitrary list of row indices and place them in a conic domain. It means for example that:
It is not necessary to store the AFEs in the same order they will appear in ACCs.
The same AFE index can appear more than once in one and/or more conic constraints (this can be used to reduce storage if the same affine expression is used in multiple ACCs).
The \(\afef,\afeg\) storage can even include rows that are not presently used in any ACC.
Domains can be reused: multiple ACCs can use the same domain. On the other hand the same type of domain can appear under many
domidx
positions. In this sense the list of created domains also plays only a storage role: the domains are only used when they enter an ACC.Affine expressions can also contain semidefinite terms, ie. the most general form of an ACC is in fact
\[Fx + \langle \bar{F},\barX\rangle + g \in \D\]These terms are input into the rows of AFE storage using the functions with infix
afebarf
, creating an additional storage structure \(\bar{\afef}\).The same affine expression storage \(\afef,\afeg\) is shared between affine conic and disjunctive constraints (see Sec. 6.9 (Disjunctive constraints)).
If, on the other hand, the user chooses to always store the AFEs one by one sequentially in the same order as they appear in ACCs then sequential functions such as
Task.appendaccseq
andTask.appendaccsseq
make it easy to input one or more ACCs by just specifying the starting AFE index and dimension.It is possible to add a number of ACCs in one go using
Task.appendaccs
.When defining an ACC an additional constant vector \(b\) can be provided to modify the constant terms coming from \(\afeg\) but only for this particular ACC. This could be useful to reduce \(\afef\) storage space if, for example, many expressions \(f^Tx-b_i\) with the same linear part \(f^Tx\), but varying constant terms \(b_i\), are to be used throughout ACCs.