/* CPLEX does not provide a function to directly get the dual multipliers * for second order cone constraint. * Example qcpdual.c illustrates how the dual multipliers for a * quadratic constraint can be computed from that constraint's slack. * However, for SOCP we can do something simpler: we can read those * multipliers directly from the dual slacks for the * cone head variables. For a second order cone constraint * x[1] >= |(x[2], ..., x[n])| * the dual multiplier is the dual slack value for x[1]. */ static int getsocpconstrmultipliers (CPXCENVptr env, CPXCLPptr lp, double *dslack, double *socppi) { int status = 0; int const cols = CPXgetnumcols (env, lp); int const qs = CPXgetnumqconstrs (env, lp); double *dense = NULL, *val = NULL; int *ind = NULL; int j, q; qbuf_type qbuf; qbuf_init (&qbuf); if ( (dense = malloc (sizeof (*dense) * cols)) == NULL ) { status = CPXERR_NO_MEMORY; goto TERMINATE; } /* First of all get the dual slack vector. This is the sum of * dual multipliers for bound constraints (as returned by CPXgetdj()) * and the per-constraint dual slack vectors (as returned by * CPXgetqconstrdslack()). */ /* Get dual multipliers for bound constraints. */ if ( (status = CPXgetdj (env, lp, dense, 0, cols - 1)) != 0 ) goto TERMINATE; /* Create a dense vector that holds the sum of dual slacks for all * quadratic constraints. */ if ( (val = malloc (cols * sizeof (val))) == NULL || (ind = malloc (cols * sizeof (ind))) == NULL ) { status = CPXERR_NO_MEMORY; goto TERMINATE; } for (q = 0; q < qs; ++q) { int len = 0, surp; if ( (status = CPXgetqconstrdslack (env, lp, q, &len, ind, val, cols, &surp)) != 0 ) goto TERMINATE; for (j = 0; j < len; ++j) { dense[ind[j]] += val[j]; } } /* Now go through the socp constraints. For each constraint find the * cone head variable (the one variable that has a negative coefficient) * and pick up its dual slack. This is the dual multiplier for the * constraint. */ for (q = 0; q < qs; ++q) { if ( !getqconstr (env, lp, q, &qbuf) ) { status = CPXERR_BAD_ARGUMENT; goto TERMINATE; } for (j = 0; j < qbuf.qnz; ++j) { if ( qbuf.qval[j] < 0.0 ) { /* This is the cone head variable. */ socppi[q] = dense[qbuf.qcol[j]]; break; } } } /* If the caller wants to have the dual slack vector then copy it * to the output array. */ if ( dslack != NULL ) memcpy (dslack, dense, sizeof (*dslack) * cols); TERMINATE: free (ind); free (val); free (dense); qbuf_clear (&qbuf); return status; }
/* * The function returns a true value if the tested KKT conditions are * satisfied and false otherwise. */ static int checkkkt (CPXCENVptr env, CPXLPptr lp, int const *cone, double tol) { int cols = CPXgetnumcols (env, lp); int rows = CPXgetnumrows (env, lp); int qcons = CPXgetnumqconstrs (env, lp); double *dslack = NULL, *pi = NULL, *socppi = NULL; double *val = NULL, *rhs = NULL; int *ind = NULL; char *sense = NULL; double *x = NULL, *slack = NULL, *qslack = NULL; double *sum = NULL; qbuf_type qbuf; CPXCHANNELptr resc, warnc, errc, logc; int ok = 0, skip = 0; int status; int i, j, q; qbuf_init (&qbuf); /* Get the channels on which we may report. */ if ( (status = CPXgetchannels (env, &resc, &warnc, &errc, &logc)) != 0 ) goto TERMINATE; /* Fetch results and problem data that we need to check the KKT * conditions. */ CPXmsg (logc, "Fetching results ... "); if ( (cols > 0 && (dslack = malloc (cols * sizeof (*dslack))) == NULL) || (rows > 0 && (pi = malloc (rows * sizeof (*pi))) == NULL) || (qcons > 0 && (socppi = malloc (qcons * sizeof (*socppi))) == NULL) || (cols > 0 && (x = malloc (cols * sizeof (*x))) == NULL) || (rows > 0 && (sense = malloc (rows * sizeof (*sense))) == NULL ) || (rows > 0 && (slack = malloc (rows * sizeof (*slack))) == NULL ) || (qcons > 0 && (qslack = malloc (qcons * sizeof (*qslack))) == NULL) || (cols > 0 && (sum = malloc (cols * sizeof (*sum))) == NULL) || (cols > 0 && (val = malloc (cols * sizeof (*val))) == NULL) || (cols > 0 && (ind = malloc (cols * sizeof (*ind))) == NULL) || (rows > 0 && (rhs = malloc (rows * sizeof (*rhs))) == NULL) ) { CPXmsg (errc, "Out of memory!\n"); goto TERMINATE; } /* Fetch problem data. */ if ( (status = CPXgetsense (env, lp, sense, 0, rows - 1)) != 0 ) goto TERMINATE; if ( (status = CPXgetrhs (env, lp, rhs, 0, rows - 1)) != 0 ) goto TERMINATE; /* Fetch solution information. */ if ( (status = CPXgetx (env, lp, x, 0, cols - 1)) != 0 ) goto TERMINATE; if ( (status = CPXgetpi (env, lp, pi, 0, rows - 1)) != 0 ) goto TERMINATE; if ( (status = getsocpconstrmultipliers (env, lp, dslack, socppi)) != 0 ) goto TERMINATE; if ( (status = CPXgetslack (env, lp, slack, 0, rows - 1)) != 0 ) goto TERMINATE; if ( (status = CPXgetqconstrslack (env, lp, qslack, 0, qcons - 1)) != 0 ) goto TERMINATE; CPXmsg (logc, "ok.\n"); /* Print out the solution data we just fetched. */ CPXmsg (resc, "x = ["); for (j = 0; j < cols; ++j) CPXmsg (resc, " %+7.3f", x[j]); CPXmsg (resc, " ]\n"); CPXmsg (resc, "dslack = ["); for (j = 0; j < cols; ++j) CPXmsg (resc, " %+7.3f", dslack[j]); CPXmsg (resc, " ]\n"); CPXmsg (resc, "pi = ["); for (i = 0; i < rows; ++i) CPXmsg (resc, " %+7.3f", pi[i]); CPXmsg (resc, " ]\n"); CPXmsg (resc, "slack = ["); for (i = 0; i < rows; ++i) CPXmsg (resc, " %+7.3f", slack[i]); CPXmsg (resc, " ]\n"); CPXmsg (resc, "socppi = ["); for (q = 0; q < qcons; ++q) CPXmsg (resc, " %+7.3f", socppi[q]); CPXmsg (resc, " ]\n"); CPXmsg (resc, "qslack = ["); for (q = 0; q < qcons; ++q) CPXmsg (resc, " %+7.3f", qslack[q]); CPXmsg (resc, " ]\n"); /* Test primal feasibility. */ CPXmsg (logc, "Testing primal feasibility ... "); /* This example illustrates the use of dual vectors returned by CPLEX * to verify dual feasibility, so we do not test primal feasibility * here. */ CPXmsg (logc, "ok.\n"); /* Test dual feasibility. * We must have * - for all <= constraints the respective pi value is non-negative, * - for all >= constraints the respective pi value is non-positive, * - since all quadratic constraints are <= constraints the socppi * value must be non-negative for all quadratic constraints, * - the dslack value for all non-cone variables must be non-negative. * Note that we do not support ranged constraints here. */ CPXmsg (logc, "Testing dual feasibility ... "); for (i = 0; i < rows; ++i) { switch (sense[i]) { case 'L': if ( pi[i] < -tol ) { CPXmsg (errc, "<= row %d has invalid dual multiplier %f.\n", i, pi[i]); goto TERMINATE; } break; case 'G': if ( pi[i] > tol ) { CPXmsg (errc, ">= row %d has invalid dual multiplier %f.\n", i, pi[i]); goto TERMINATE; } break; case 'E': /* Nothing to check here. */ break; } } for (q = 0; q < qcons; ++q) { if ( socppi[q] < -tol ) { CPXmsg (errc, "Quadratic constraint %d has invalid dual multiplier %f.\n", q, socppi[q]); goto TERMINATE; } } for (j = 0; j < cols; ++j) { if ( cone[j] == NOT_IN_CONE && dslack[j] < -tol ) { CPXmsg (errc, "dslack value for column %d is invalid: %f\n", j, dslack[j]); goto TERMINATE; } } CPXmsg (logc, "ok.\n"); /* Test complementary slackness. * For each constraint either the constraint must have zero slack or * the dual multiplier for the constraint must be 0. Again, we must * consider the special case in which a variable is not explicitly * contained in a second order cone constraint (conestat[j] == 0). */ CPXmsg (logc, "Testing complementary slackness ... "); for (i = 0; i < rows; ++i) { if ( fabs (slack[i]) > tol && fabs (pi[i]) > tol ) { CPXmsg (errc, "Complementary slackness not satisfied for row %d (%f, %f)\n", i, slack[i], pi[i]); goto TERMINATE; } } for (q = 0; q < qcons; ++q) { if ( fabs (qslack[q]) > tol && fabs (socppi[q]) > tol ) { CPXmsg (errc, "Complementary slackness not satisfied for cone %d (%f, %f).\n", q, qslack[q], socppi[q]); goto TERMINATE; } } for (j = 0; j < cols; ++j) { if ( cone[j] == NOT_IN_CONE ) { if ( fabs (x[j]) > tol && fabs (dslack[j]) > tol ) { CPXmsg (errc, "Complementary slackness not satisfied for non-cone variable %f (%f, %f).\n", j, x[j], dslack[j]); goto TERMINATE; } } } CPXmsg (logc, "ok.\n"); /* Test stationarity. * We must have * c - g[i]'(X)*pi[i] = 0 * where c is the objective function, g[i] is the i-th constraint of the * problem, g[i]'(x) is the derivate of g[i] with respect to x and X is the * optimal solution. * We need to distinguish the following cases: * - linear constraints g(x) = ax - b. The derivative of such a * constraint is g'(x) = a. * - second order constraints g(x[1],...,x[n]) = -x[1] + |(x[2],...,x[n])| * the derivative of such a constraint is * g'(x) = (-1, x[2]/|(x[2],...,x[n])|, ..., x[n]/|(x[2],...,x[n])| * (here |.| denotes the Euclidean norm). * - bound constraints g(x) = -x for variables that are not explicitly * contained in any second order cone constraint. The derivative for * such a constraint is g'(x) = -1. * Note that it may happen that the derivative of a second order cone * constraint is not defined at the optimal solution X (this happens if * X=0). In this case we just skip the stationarity test. */ CPXmsg (logc, "Testing stationarity ... "); /* Initialize sum = c. */ if ( (status = CPXgetobj (env, lp, sum, 0, cols - 1)) != 0 ) goto TERMINATE; /* Handle linear constraints. */ for (i = 0; i < rows; ++i) { int nz, surplus, beg; int n; status = CPXgetrows (env, lp, &nz, &beg, ind, val, cols, &surplus, i, i); if ( status != 0 ) goto TERMINATE; for (n = 0; n < nz; ++n) { sum[ind[n]] -= pi[i] * val[n]; } } /* Handle second order cone constraints. */ for (q = 0; q < qcons; ++q) { double norm = 0.0; int n; if ( !getqconstr (env, lp, q, &qbuf) ) goto TERMINATE; for (n = 0; n < qbuf.qnz; ++n) { if ( qbuf.qval[n] > 0 ) norm += x[qbuf.qcol[n]] * x[qbuf.qcol[n]]; } norm = sqrt (norm); if ( fabs (norm) <= tol ) { CPXmsg (warnc, "WARNING: Cannot test stationarity at non-differentiable point.\n"); skip = 1; break; } for (n = 0; n < qbuf.qnz; ++n) { if ( qbuf.qval[n] < 0 ) sum[qbuf.qcol[n]] -= socppi[q]; else sum[qbuf.qcol[n]] += socppi[q] * x[qbuf.qcol[n]] / norm; } } /* Handle variables that do not appear in any second order cone constraint. */ for (j = 0; !skip && j < cols; ++j) { if ( cone[j] == NOT_IN_CONE ) { sum[j] -= dslack[j]; } } /* Now test that all the entries in sum[] are 0. */ for (j = 0; !skip && j < cols; ++j) { if ( fabs (sum[j]) > tol ) { CPXmsg (errc, "Stationarity not satisfied at index %d: %f\n", j, sum[j]); goto TERMINATE; } } CPXmsg (logc, "ok.\n"); CPXmsg (logc, "KKT conditions are satisfied.\n"); ok = 1; TERMINATE: if ( !ok ) CPXmsg (logc, "failed.\n"); qbuf_clear (&qbuf); free (rhs); free (ind); free (val); free (sum); free (qslack); free (slack); free (sense); free (x); free (socppi); free (pi); free (dslack); return ok; }
/* ********************************************************************** * * * * C A L C U L A T E D U A L S F O R Q U A D C O N S T R S * * * * CPLEX does not give us the dual multipliers for quadratic * * constraints directly. This is because they may not be properly * * defined at the cone top and deciding whether we are at the cone * * top or not involves (problem specific) tolerance issues. CPLEX * * instead gives us all the values we need in order to compute the * * dual multipliers if we are not at the cone top. * * Function getqconstrmultipliers() takes the following arguments: * * env CPLEX environment that was used to create LP. * * lp CPLEX problem object for which the dual multipliers are * * to be calculated. * * x The optimal solution. * * qpi Array that stores the dual multipliers upon success. * * zerotol The tolerance that is used to decide whether a value * * if zero or not (used to decide about "cone top" or not). * * * * ********************************************************************** */ static int getqconstrmultipliers (CPXCENVptr env, CPXCLPptr lp, double const *x, double *qpi, double zerotol) { int const cols = CPXgetnumcols (env, lp); int const numqs = CPXgetnumqconstrs (env, lp); double *dense = NULL; double *grad = NULL; int *slackind = NULL; double *slackval = NULL; int status = 0; int i; if ( (dense = malloc (sizeof (*dense) * cols)) == NULL || (grad = malloc (sizeof (*grad) * cols)) == NULL || (slackind = malloc (sizeof (*slackind) * cols)) == NULL || (slackval = malloc (sizeof (*slackval) * cols)) == NULL ) { status = CPXERR_NO_MEMORY; goto TERMINATE; } /* Calculate dual multipliers for quadratic constraints from dual * slack vectors and optimal solutions. * The dual multiplier is essentially the dual slack divided * by the derivative evaluated at the optimal solution. If the optimal * solution is 0 then the derivative at this point is not defined (we are * at the cone top) and we cannot compute a dual multiplier. */ for (i = 0; i < numqs; ++i) { int j, k; int surplus, len; int conetop; /* Clear out dense slack and gradient vector. */ for (j = 0; j < cols; ++j) { dense[j] = 0.0; grad[j] = 0; } /* Get dual slack vector and expand it to a dense vector. */ status = CPXgetqconstrdslack (env, lp, i, &len, slackind, slackval, cols, &surplus); if ( status != 0 ) goto TERMINATE; for (k = 0; k < len; ++k) dense[slackind[k]] = slackval[k]; /* Compute value of derivative at optimal solution. */ /* The derivative of a quadratic constraint x^TQx + a^Tx + b <= 0 * is Q^Tx + Qx + a. */ conetop = 1; for (k = quadbeg[i]; k < quadbeg[i] + quadnzcnt[i]; ++k) { if ( fabs (x[quadrow[k]]) > zerotol || fabs (x[quadcol[k]]) > zerotol ) conetop = 0; grad[quadcol[k]] += quadval[k] * x[quadrow[k]]; grad[quadrow[k]] += quadval[k] * x[quadcol[k]]; } for (j = linbeg[i]; j < linbeg[i] + linnzcnt[i]; ++j) { grad[linind[j]] += linval[j]; if ( fabs (x[linind[j]]) > zerotol ) conetop = 0; } if ( conetop ) { fprintf (stderr, "#### WARNING: Cannot compute dual multipler at cone top!\n"); status = CPXERR_BAD_ARGUMENT; goto TERMINATE; } else { int ok = 0; double maxabs = -1.0; /* Compute qpi[i] as slack/gradient. * We may have several indices to choose from and use the one * with largest absolute value in the denominator. */ for (j = 0; j < cols; ++j) { if ( fabs (grad[j]) > zerotol ) { if ( fabs (grad[j]) > maxabs ) { qpi[i] = dense[j] / grad[j]; maxabs = fabs (grad[j]); } ok = 1; } } if ( !ok ) { /* Dual slack is all 0. qpi[i] can be anything, just set * to 0. */ qpi[i] = 0.0; } } } TERMINATE: free (slackval); free (slackind); free (grad); free (dense); return status; }