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.10 (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 */
if (r == MSK_RES_OK)
r = MSK_appendafes(task, k + 1);
We now have \(\afef\) and \(\afeg\) with 3 rows of zeros and we fill them up to obtain (6.7).
/* Fill in the affine expression storage with data */
/* F matrix in sparse form */
MSKint64t Fsubi[] = {1, 1, 2, 2}; /* G is placed from row 1 of F */
MSKint32t Fsubj[] = {0, 1, 0, 2};
double Fval[] = {1.5, 0.1, 0.3, 2.1};
int numEntries = 4;
/* Other data */
double h[] = {0, 0.1};
double gamma = 0.03;
/* Fill in F storage */
if (r == MSK_RES_OK)
r = MSK_putafefentrylist(task, numEntries, Fsubi, Fsubj, Fval);
/* Fill in g storage */
if (r == MSK_RES_OK)
r = MSK_putafeg(task, 0, gamma);
if (r == MSK_RES_OK)
r = MSK_putafegslice(task, 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 */
if (r == MSK_RES_OK)
r = MSK_appendquadraticconedomain(task, k + 1, &quadDom);
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, MSK_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 */
MSKint64t afeidx[] = {0, 1, 2};
if (r == MSK_RES_OK)
r = MSK_appendacc(task,
quadDom, /* Domain index */
k + 1, /* Dimension */
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 MSK_optimize
and retrieve the solution with MSK_getxx
. Since our problem contains a nonlinear constraint we fetch the interior-point solution. The full code solving problem (6.3) is shown below.
#include <stdio.h>
#include "mosek.h" /* Include the MOSEK definition file. */
static void MSKAPI printstr(void *handle,
const char str[])
{
printf("%s", str);
} /* printstr */
int main(int argc, const char *argv[])
{
MSKrescodee r;
MSKint32t i, j;
MSKenv_t env = NULL;
MSKtask_t task = NULL;
MSKint64t quadDom;
/* Input data dimensions */
const MSKint32t n = 3,
k = 2;
/* Create the mosek environment. */
r = MSK_makeenv(&env, NULL);
if (r == MSK_RES_OK)
{
/* Create the optimization task. */
r = MSK_maketask(env, 0, 0, &task);
if (r == MSK_RES_OK)
{
MSK_linkfunctotaskstream(task, MSK_STREAM_LOG, NULL, printstr);
/* Create n free variables */
if (r == MSK_RES_OK)
r = MSK_appendvars(task, n);
if (r == MSK_RES_OK)
r = MSK_putvarboundsliceconst(task, 0, n, MSK_BK_FR, -MSK_INFINITY, +MSK_INFINITY);
/* Set up the objective */
{
double c[] = {2.0, 3.0, -1.0};
if (r == MSK_RES_OK)
r = MSK_putcslice(task, 0, n, c);
if (r == MSK_RES_OK)
r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MAXIMIZE);
}
/* One linear constraint sum(x) == 1 */
if (r == MSK_RES_OK)
r = MSK_appendcons(task, 1);
if (r == MSK_RES_OK)
r = MSK_putconbound(task, 0, MSK_BK_FX, 1.0, 1.0);
for(i = 0; i < n && r == MSK_RES_OK; i++)
r = MSK_putaij(task, 0, i, 1.0);
/* Append empty AFE rows for affine expression storage */
if (r == MSK_RES_OK)
r = MSK_appendafes(task, k + 1);
{
/* Fill in the affine expression storage with data */
/* F matrix in sparse form */
MSKint64t Fsubi[] = {1, 1, 2, 2}; /* G is placed from row 1 of F */
MSKint32t Fsubj[] = {0, 1, 0, 2};
double Fval[] = {1.5, 0.1, 0.3, 2.1};
int numEntries = 4;
/* Other data */
double h[] = {0, 0.1};
double gamma = 0.03;
/* Fill in F storage */
if (r == MSK_RES_OK)
r = MSK_putafefentrylist(task, numEntries, Fsubi, Fsubj, Fval);
/* Fill in g storage */
if (r == MSK_RES_OK)
r = MSK_putafeg(task, 0, gamma);
if (r == MSK_RES_OK)
r = MSK_putafegslice(task, 1, k+1, h);
}
/* Define a conic quadratic domain */
if (r == MSK_RES_OK)
r = MSK_appendquadraticconedomain(task, k + 1, &quadDom);
{
/* Create the ACC */
MSKint64t afeidx[] = {0, 1, 2};
if (r == MSK_RES_OK)
r = MSK_appendacc(task,
quadDom, /* Domain index */
k + 1, /* Dimension */
afeidx, /* Indices of AFE rows [0,...,k] */
NULL); /* Ignored */
}
/* Begin optimization and fetching the solution */
if (r == MSK_RES_OK)
{
MSKrescodee trmcode;
/* Run optimizer */
r = MSK_optimizetrm(task, &trmcode);
/* Print a summary containing information
about the solution for debugging purposes*/
MSK_solutionsummary(task, MSK_STREAM_MSG);
if (r == MSK_RES_OK)
{
MSKsolstae solsta;
MSK_getsolsta(task, MSK_SOL_ITR, &solsta);
switch (solsta)
{
case MSK_SOL_STA_OPTIMAL:
{
double *xx, *doty, *activity = NULL;
/* Fetch the solution */
xx = calloc(n, sizeof(double));
MSK_getxx(task,
MSK_SOL_ITR, /* Request the interior solution. */
xx);
printf("Optimal primal solution\n");
for (j = 0; j < n; ++j)
printf("x[%d]: %e\n", j, xx[j]);
free(xx);
/* Fetch the doty dual of the ACC */
doty = calloc(k + 1, sizeof(double));
MSK_getaccdoty(task,
MSK_SOL_ITR, /* Request the interior solution. */
0, /* ACC index. */
doty);
printf("Dual doty of the ACC\n");
for (j = 0; j < k + 1; ++j)
printf("doty[%d]: %e\n", j, doty[j]);
free(doty);
/* Fetch the activity of the ACC */
activity = calloc(k + 1, sizeof(double));
MSK_evaluateacc(task,
MSK_SOL_ITR, /* Request the interior solution. */
0, /* ACC index. */
activity);
printf("Activity of the ACC\n");
for (j = 0; j < k + 1; ++j)
printf("activity[%d]: %e\n", j, activity[j]);
free(activity);
}
break;
case MSK_SOL_STA_DUAL_INFEAS_CER:
case MSK_SOL_STA_PRIM_INFEAS_CER:
printf("Primal or dual infeasibility certificate found.\n");
break;
case MSK_SOL_STA_UNKNOWN:
printf("The status of the solution could not be determined. Termination code: %d.\n", trmcode);
break;
default:
printf("Other solution status.");
break;
}
}
else
{
printf("Error while optimizing.\n");
}
}
if (r != MSK_RES_OK)
{
/* In case of an error print error code and description. */
char symname[MSK_MAX_STR_LEN];
char desc[MSK_MAX_STR_LEN];
printf("An error occurred while optimizing.\n");
MSK_getcodedesc(r,
symname,
desc);
printf("Error %s - '%s'\n", symname, desc);
}
}
/* Delete the task and the associated data. */
MSK_deletetask(&task);
}
/* Delete the environment and the associated data. */
MSK_deleteenv(&env);
return (r);
} /* main */
The answer is
[-0.07838011145615721, 1.1289128998004547, -0.0505327883442975]
The dual values \(\doty\) of an ACC can be obtained with MSK_getaccdoty
if required.
/* Fetch the doty dual of the ACC */
doty = calloc(k + 1, sizeof(double));
MSK_getaccdoty(task,
MSK_SOL_ITR, /* Request the interior solution. */
0, /* ACC index. */
doty);
printf("Dual doty of the ACC\n");
for (j = 0; j < k + 1; ++j)
printf("doty[%d]: %e\n", j, doty[j]);
free(doty);
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 */
if (r == MSK_RES_OK)
r = MSK_appendafes(task, 1);
for(i = 0; i < n && r == MSK_RES_OK; i++)
r = MSK_putafefentry(task, 0, i, 1.0);
if (r == MSK_RES_OK)
r = MSK_putafeg(task, 0, -1.0);
}
{
/* Set AFE rows representing the quadratic constraint */
/* F matrix data in sparse form */
MSKint64t Fsubi[] = {2, 2, 3, 3}; /* G is placed from row 2 of F */
MSKint32t Fsubj[] = {0, 1, 0, 2};
double Fval[] = {1.5, 0.1, 0.3, 2.1};
int numEntries = 4;
/* Other data */
double h[] = {0, 0.1};
double gamma = 0.03;
if (r == MSK_RES_OK)
r = MSK_appendafes(task, k + 1);
if (r == MSK_RES_OK)
r = MSK_putafefentrylist(task, numEntries, Fsubi, Fsubj, Fval);
if (r == MSK_RES_OK)
r = MSK_putafeg(task, 1, gamma);
if (r == MSK_RES_OK)
r = MSK_putafegslice(task, 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 a conic quadratic domain */
if (r == MSK_RES_OK)
r = MSK_appendrzerodomain(task, 1, &zeroDom);
if (r == MSK_RES_OK)
r = MSK_appendquadraticconedomain(task, k + 1, &quadDom);
Finally, we create both ACCs. The first ACCs picks the 0-th row of \(\afef,\afeg\) and places it in the zero domain:
/* Linear constraint */
MSKint64t afeidx[] = {0};
if (r == MSK_RES_OK)
r = MSK_appendacc(task,
zeroDom, /* Domain index */
1, /* Dimension */
afeidx, /* 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:
/* Quadratic constraint */
MSKint64t afeidx[] = {1, 2, 3};
if (r == MSK_RES_OK)
r = MSK_appendacc(task,
quadDom, /* Domain index */
k + 1, /* Dimension */
afeidx, /* Indices of AFE rows */
NULL); /* Ignored */
The completes the construction and we can solve the problem like before:
#include <stdio.h>
#include "mosek.h" /* Include the MOSEK definition file. */
static void MSKAPI printstr(void *handle,
const char str[])
{
printf("%s", str);
} /* printstr */
int main(int argc, const char *argv[])
{
MSKrescodee r;
MSKint32t i, j;
MSKenv_t env = NULL;
MSKtask_t task = NULL;
MSKint64t zeroDom, quadDom;
/* Input data dimensions */
const MSKint32t n = 3,
k = 2;
/* Create the mosek environment. */
r = MSK_makeenv(&env, NULL);
if (r == MSK_RES_OK)
{
/* Create the optimization task. */
r = MSK_maketask(env, 0, 0, &task);
if (r == MSK_RES_OK)
{
MSK_linkfunctotaskstream(task, MSK_STREAM_LOG, NULL, printstr);
/* Create n free variables */
if (r == MSK_RES_OK)
r = MSK_appendvars(task, n);
if (r == MSK_RES_OK)
r = MSK_putvarboundsliceconst(task, 0, n, MSK_BK_FR, -MSK_INFINITY, +MSK_INFINITY);
/* Set up the objective */
{
double c[] = {2.0, 3.0, -1.0};
if (r == MSK_RES_OK)
r = MSK_putcslice(task, 0, n, c);
if (r == MSK_RES_OK)
r = MSK_putobjsense(task, MSK_OBJECTIVE_SENSE_MAXIMIZE);
}
{
/* Set AFE rows representing the linear constraint */
if (r == MSK_RES_OK)
r = MSK_appendafes(task, 1);
for(i = 0; i < n && r == MSK_RES_OK; i++)
r = MSK_putafefentry(task, 0, i, 1.0);
if (r == MSK_RES_OK)
r = MSK_putafeg(task, 0, -1.0);
}
{
/* Set AFE rows representing the quadratic constraint */
/* F matrix data in sparse form */
MSKint64t Fsubi[] = {2, 2, 3, 3}; /* G is placed from row 2 of F */
MSKint32t Fsubj[] = {0, 1, 0, 2};
double Fval[] = {1.5, 0.1, 0.3, 2.1};
int numEntries = 4;
/* Other data */
double h[] = {0, 0.1};
double gamma = 0.03;
if (r == MSK_RES_OK)
r = MSK_appendafes(task, k + 1);
if (r == MSK_RES_OK)
r = MSK_putafefentrylist(task, numEntries, Fsubi, Fsubj, Fval);
if (r == MSK_RES_OK)
r = MSK_putafeg(task, 1, gamma);
if (r == MSK_RES_OK)
r = MSK_putafegslice(task, 2, k+2, h);
}
/* Define a conic quadratic domain */
if (r == MSK_RES_OK)
r = MSK_appendrzerodomain(task, 1, &zeroDom);
if (r == MSK_RES_OK)
r = MSK_appendquadraticconedomain(task, k + 1, &quadDom);
/* Append affine conic constraints */
{
/* Linear constraint */
MSKint64t afeidx[] = {0};
if (r == MSK_RES_OK)
r = MSK_appendacc(task,
zeroDom, /* Domain index */
1, /* Dimension */
afeidx, /* Indices of AFE rows */
NULL); /* Ignored */
}
{
/* Quadratic constraint */
MSKint64t afeidx[] = {1, 2, 3};
if (r == MSK_RES_OK)
r = MSK_appendacc(task,
quadDom, /* Domain index */
k + 1, /* Dimension */
afeidx, /* Indices of AFE rows */
NULL); /* Ignored */
}
/* Begin optimization and fetching the solution */
if (r == MSK_RES_OK)
{
MSKrescodee trmcode;
/* Run optimizer */
r = MSK_optimizetrm(task, &trmcode);
/* Print a summary containing information
about the solution for debugging purposes*/
MSK_solutionsummary(task, MSK_STREAM_MSG);
if (r == MSK_RES_OK)
{
MSKsolstae solsta;
MSK_getsolsta(task, MSK_SOL_ITR, &solsta);
MSK_getsolsta(task, MSK_SOL_ITR, &solsta);
switch (solsta)
{
case MSK_SOL_STA_OPTIMAL:
{
double *xx, *doty, *activity = NULL;
/* Fetch the primal solution */
xx = calloc(n, sizeof(double));
MSK_getxx(task,
MSK_SOL_ITR, /* Request the interior solution. */
xx);
printf("Optimal primal solution\n");
for (j = 0; j < n; ++j)
printf("x[%d]: %e\n", j, xx[j]);
free(xx);
/* Fetch the dual doty solution for the ACC */
doty = calloc(k + 1, sizeof(double));
MSK_getaccdoty(task,
MSK_SOL_ITR, /* Request the interior solution. */
1, /* ACC index of quadratic ACC. */
doty);
printf("Dual doty of the quadratic ACC\n");
for (j = 0; j < k + 1; ++j)
printf("doty[%d]: %e\n", j, doty[j]);
free(doty);
/* Fetch the activity of the ACC */
activity = calloc(k + 1, sizeof(double));
MSK_evaluateacc(task,
MSK_SOL_ITR, /* Request the interior solution. */
0, /* ACC index. */
activity);
printf("Activity of the ACC\n");
for (j = 0; j < k + 1; ++j)
printf("activity[%d]: %e\n", j, activity[j]);
free(activity);
}
break;
case MSK_SOL_STA_DUAL_INFEAS_CER:
case MSK_SOL_STA_PRIM_INFEAS_CER:
printf("Primal or dual infeasibility certificate found.\n");
break;
case MSK_SOL_STA_UNKNOWN:
printf("The status of the solution could not be determined. Termination code: %d.\n", trmcode);
break;
default:
printf("Other solution status.");
break;
}
}
else
{
printf("Error while optimizing.\n");
}
}
if (r != MSK_RES_OK)
{
/* In case of an error print error code and description. */
char symname[MSK_MAX_STR_LEN];
char desc[MSK_MAX_STR_LEN];
printf("An error occurred while optimizing.\n");
MSK_getcodedesc(r,
symname,
desc);
printf("Error %s - '%s'\n", symname, desc);
}
}
/* Delete the task and the associated data. */
MSK_deletetask(&task);
}
/* Delete the environment and the associated data. */
MSK_deleteenv(&env);
return (r);
} /* main */
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
MSK_appendaccseq
andMSK_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
MSK_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.