void MILPSolverCPX::getRow(const int & i, vector<pair<int,double> > & entries) { const int colCount = getNumCols(); IloRange & currRow = (modelcon->data)[i]; IloNumExprArg ne = currRow.getExpr(); for(IloExpr::LinearIterator itr = static_cast<IloExpr>(ne).getLinearIterator(); itr.ok(); ++itr) { const int colID = itr.getVar().getId(); for (int c = 0; c < colCount; ++c) { if ((*modelvar)[c].getId() == colID) { entries.push_back(make_pair(c, itr.getCoef())); break; } } } }
// Test KKT conditions on the solution. // The function returns true if the tested KKT conditions are satisfied // and false otherwise. // The function assumes that the model currently extracted to CPLEX is fully // described by obj, vars and rngs. static bool checkkkt (IloCplex& cplex, IloObjective const& obj, IloNumVarArray const& vars, IloRangeArray const& rngs, IloIntArray const& cone, double tol) { IloEnv env = cplex.getEnv(); IloModel model = cplex.getModel(); IloNumArray x(env), dslack(env); IloNumArray pi(env, rngs.getSize()), slack(env); // Read primal and dual solution information. cplex.getValues(x, vars); cplex.getSlacks(slack, rngs); // pi for second order cone constraints. getsocpconstrmultipliers(cplex, vars, rngs, pi, dslack); // pi for linear constraints. for (IloInt i = 0; i < rngs.getSize(); ++i) { IloRange r = rngs[i]; if ( !r.getQuadIterator().ok() ) pi[idx(r)] = cplex.getDual(r); } // Print out the data we just fetched. streamsize oprec = env.out().precision(3); ios_base::fmtflags oflags = env.out().setf(ios::fixed | ios::showpos); env.out() << "x = ["; for (IloInt i = 0; i < x.getSize(); ++i) env.out() << " " << x[i]; env.out() << " ]" << endl; env.out() << "dslack = ["; for (IloInt i = 0; i < dslack.getSize(); ++i) env.out() << " " << dslack[i]; env.out() << " ]" << endl; env.out() << "pi = ["; for (IloInt i = 0; i < rngs.getSize(); ++i) env.out() << " " << pi[i]; env.out() << " ]" << endl; env.out() << "slack = ["; for (IloInt i = 0; i < rngs.getSize(); ++i) env.out() << " " << slack[i]; env.out() << " ]" << endl; env.out().precision(oprec); env.out().flags(oflags); // Test 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. // 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, // - the dslack value for all non-cone variables must be non-negative. // Note that we do not support ranged constraints here. for (IloInt i = 0; i < vars.getSize(); ++i) { IloNumVar v = vars[i]; if ( cone[i] == NOT_IN_CONE && dslack[i] < -tol ) { env.error() << "Dual multiplier for " << v << " is not feasible: " << dslack[i] << endl; return false; } } for (IloInt i = 0; i < rngs.getSize(); ++i) { IloRange r = rngs[i]; if ( fabs (r.getLB() - r.getUB()) <= tol ) { // Nothing to check for equality constraints. } else if ( r.getLB() > -IloInfinity && pi[i] > tol ) { env.error() << "Dual multiplier " << pi[i] << " for >= constraint" << endl << r << endl << "not feasible" << endl; return false; } else if ( r.getUB() < IloInfinity && pi[i] < -tol ) { env.error() << "Dual multiplier " << pi[i] << " for <= constraint" << endl << r << endl << "not feasible" << endl; return false; } } // Test complementary slackness. // For each constraint either the constraint must have zero slack or // the dual multiplier for the constraint must be 0. We must also // consider the special case in which a variable is not explicitly // contained in a second order cone constraint. for (IloInt i = 0; i < vars.getSize(); ++i) { if ( cone[i] == NOT_IN_CONE ) { if ( fabs(x[i]) > tol && dslack[i] > tol ) { env.error() << "Invalid complementary slackness for " << vars[i] << ":" << endl << " " << x[i] << " and " << dslack[i] << endl; return false; } } } for (IloInt i = 0; i < rngs.getSize(); ++i) { if ( fabs(slack[i]) > tol && fabs(pi[i]) > tol ) { env.error() << "Invalid complementary slackness for " << endl << rngs[i] << ":" << endl << " " << slack[i] << " and " << pi[i] << endl; return false; } } // 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. IloNumArray sum(env, vars.getSize()); for (IloExpr::LinearIterator it = obj.getLinearIterator(); it.ok(); ++it) sum[idx(it.getVar())] = it.getCoef(); for (IloInt i = 0; i < vars.getSize(); ++i) { IloNumVar v = vars[i]; if ( cone[i] == NOT_IN_CONE ) sum[i] -= dslack[i]; } for (IloInt i = 0; i < rngs.getSize(); ++i) { IloRange r = rngs[i]; if ( r.getQuadIterator().ok() ) { // Quadratic (second order cone) constraint. IloNum norm = 0.0; for (IloExpr::QuadIterator q = r.getQuadIterator(); q.ok(); ++q) { if ( q.getCoef() > 0 ) norm += x[idx(q.getVar1())] * x[idx(q.getVar1())]; } norm = sqrt(norm); if ( fabs(norm) <= tol ) { // Derivative is not defined. Skip test. env.warning() << "Cannot test stationarity at non-differentiable point." << endl; return true; } else { for (IloExpr::QuadIterator q = r.getQuadIterator(); q.ok(); ++q) { if ( q.getCoef() < 0 ) sum[idx(q.getVar1())] -= pi[i]; else sum[idx(q.getVar1())] += pi[i] * x[idx(q.getVar1())] / norm; } } } else { // Linear constraint. for (IloExpr::LinearIterator l = r.getLinearIterator(); l.ok(); ++l) sum[idx(l.getVar())] -= pi[i] * l.getCoef(); } } // Now test that all elements in sum[] are 0. for (IloInt i = 0; i < vars.getSize(); ++i) { if ( fabs(sum[i]) > tol ) { env.error() << "Invalid stationarity " << sum[i] << " for " << vars[i] << endl; return false; } } return true; }
/** Create the dual of a linear program. * The function can only dualize programs of the form * <code>Ax <= b, x >= 0</code>. The data in <code>primalVars</code> and * <code>dualRows</code> as well as in <code>primalRows</code> and * <code>dualVars</code> is in 1-to-1-correspondence. * @param primalObj Objective function of primal problem. * @param primalVars Variables in primal problem. * @param primalRows Rows in primal problem. * @param dualObj Objective function of dual will be stored here. * @param dualVars All dual variables will be stored here. * @param dualRows All dual rows will be stored here. */ void BendersOpt::makeDual(IloObjective const &primalObj, IloNumVarArray const &primalVars, IloRangeArray const &primalRows, IloObjective *dualObj, IloNumVarArray *dualVars, IloRangeArray *dualRows) { // To keep the code simple we only support problems // of the form Ax <= b, b >= 0 here. We leave it as a reader's // exercise to extend the function to something that can handle // any kind of linear model. for (IloInt j = 0; j < primalVars.getSize(); ++j) if ( primalVars[j].getLB() != 0 || primalVars[j].getUB() < IloInfinity ) { std::stringstream s; s << "Cannot dualize variable " << primalVars[j]; throw s.str(); } for (IloInt i = 0; i < primalRows.getSize(); ++i) if ( primalRows[i].getLB() > -IloInfinity || primalRows[i].getUB() >= IloInfinity ) { std::stringstream s; s << "Cannot dualize constraint " << primalRows[i]; std::cerr << s.str() << std::endl; throw s.str(); } // The dual of // min/max c^T x // Ax <= b // x >= 0 // is // max/min y^T b // y^T A <= c // y <= 0 // We scale y by -1 to get >= 0 IloEnv env = primalVars.getEnv(); IloObjective obj(env, 0.0, primalObj.getSense() == IloObjective::Minimize ? IloObjective::Maximize : IloObjective::Minimize); IloRangeArray rows(env); IloNumVarArray y(env); std::map<IloNumVar,IloInt,ExtractableLess<IloNumVar> > v2i; for (IloInt j = 0; j < primalVars.getSize(); ++j) { IloNumVar x = primalVars[j]; v2i.insert(std::map<IloNumVar,IloInt,ExtractableLess<IloNumVar> >::value_type(x, j)); rows.add(IloRange(env, -IloInfinity, 0, x.getName())); } for (IloExpr::LinearIterator it = primalObj.getLinearIterator(); it.ok(); ++it) rows[v2i[it.getVar()]].setUB(it.getCoef()); for (IloInt i = 0; i < primalRows.getSize(); ++i) { IloRange r = primalRows[i]; IloNumColumn col(env); col += obj(-r.getUB()); for (IloExpr::LinearIterator it = r.getLinearIterator(); it.ok(); ++it) col += rows[v2i[it.getVar()]](-it.getCoef()); y.add(IloNumVar(col, 0, IloInfinity, IloNumVar::Float, r.getName())); } *dualObj = obj; *dualVars = y; *dualRows = rows; }
/** Extract the master block from <code>problem</code>. * The constructor also sets up the solver for the newly created master * block. The master block can only be extracted if all sub-blocks have * already been extracted. * @param problem The problem from which to extract the master. * @param blocks The sub blocks that have already been extracted. */ BendersOpt::Block::Block(Problem const *problem, BlockVector const &blocks) : env(), number(-1), vars(0), rows(0), cplex(0), cb(0) { IloNumVarArray problemVars = problem->getVariables(); IloRangeArray problemRanges = problem->getRows(); IloExpr masterObj(env); IloNumVarArray masterVars(env); IloRangeArray masterRows(env); // Find columns that do not intersect block variables and // copy them to the master block. IdxMap idxMap; RowSet rowSet; for (IloInt j = 0; j < problemVars.getSize(); ++j) { IloNumVar x = problemVars[j]; if ( problem->getBlock(x) < 0 ) { // Column is not in a block. Copy it to the master. IloNumVar v(env, x.getLB(), x.getUB(), x.getType(), x.getName()); varMap.insert(VarMap::value_type(v, x)); masterObj += problem->getObjCoef(x) * v; idxMap[x] = masterVars.getSize(); masterVars.add(v); } else { // Column is in a block. Collect all rows that intersect // this column. RowSet const &intersected = problem->getIntersectedRows(x); for (RowSet::const_iterator it = intersected.begin(); it != intersected.end(); ++it) rowSet.insert(*it); idxMap[x] = -1; } } // Pick up the rows that we need to copy. // These are the rows that are only intersected by master variables, // that is, the rows that are not in any block's rowset. for (IloInt i = 0; i < problemRanges.getSize(); ++i) { IloRange r = problemRanges[i]; if ( rowSet.find(r) == rowSet.end() ) { IloRange masterRow(env, r.getLB(), r.getUB(), r.getName()); IloExpr lhs(env); for (IloExpr::LinearIterator it = r.getLinearIterator(); it.ok(); ++it) { lhs += it.getCoef() * masterVars[idxMap[it.getVar()]]; } masterRow.setExpr(lhs); masterRows.add(masterRow); } } // Adjust variable indices in blocks so that reference to variables // in the original problem become references to variables in the master. for (BlockVector::const_iterator b = blocks.begin(); b != blocks.end(); ++b) { for (std::vector<FixData>::iterator it = (*b)->fixed.begin(); it != (*b)->fixed.end(); ++it) it->col = idxMap[problemVars[it->col]]; } // Create the eta variables, one for each block. // See the comments at the top of this file for details about the // eta variables. IloInt const firsteta = masterVars.getSize(); for (BlockVector::size_type i = 0; i < blocks.size(); ++i) { std::stringstream s; s << "_eta" << i; IloNumVar eta(env, 0.0, IloInfinity, s.str().c_str()); masterObj += eta; masterVars.add(eta); } // Create model and solver instance vars = masterVars; rows = masterRows; IloModel model(env); model.add(obj = IloObjective(env, masterObj, problem->getObjSense())); model.add(vars); model.add(rows); cplex = IloCplex(model); cplex.use(cb = new (env) LazyConstraintCallback(env, this, blocks, firsteta)); for (IloExpr::LinearIterator it = obj.getLinearIterator(); it.ok(); ++it) objMap.insert(ObjMap::value_type(it.getVar(), it.getCoef())); }
/** Extract sub block number <code>n</code> from <code>problem</code>. * The constructor creates a representation of block number <code>n</code> * as described in <code>problem</code>. * The constructor will also connect the newly created block to a remote * object solver instance. * @param problem The problem from which the block is to be extracted. * @param n Index of the block to be extracted. * @param argc Argument for IloCplex constructor. * @param argv Argument for IloCplex constructor. * @param machines List of machines to which to connect. If the code is * compiled for the TCP/IP transport then the block will * be connected to <code>machines[n]</code>. */ BendersOpt::Block::Block(Problem const *problem, IloInt n, int argc, char const *const *argv, std::vector<char const *> const &machines) : env(), number(n), vars(0), rows(0), cplex(0), cb(0) { IloNumVarArray problemVars = problem->getVariables(); IloRangeArray problemRanges = problem->getRows(); // Create a map that maps variables in the original model to their // respective index in problemVars. std::map<IloNumVar,IloInt,ExtractableLess<IloNumVar> > origIdxMap; for (IloInt j = 0; j < problemVars.getSize(); ++j) origIdxMap.insert(std::map<IloNumVar,IloInt,ExtractableLess<IloNumVar> >::value_type(problemVars[j], j)); // Copy non-fixed variables from original problem into primal problem. IloExpr primalObj(env); IloNumVarArray primalVars(env); IloRangeArray primalRows(env); IdxMap idxMap; // Index of original variable in block's primal model RowSet rowSet; for (IloInt j = 0; j < problemVars.getSize(); ++j) { IloNumVar x = problemVars[j]; if ( problem->getBlock(x) == number ) { // Create column in block LP with exactly the same data. if ( x.getType() != IloNumVar::Float ) { std::stringstream s; s << "Cannot create non-continuous block variable " << x; std::cerr << s.str() << std::endl; throw s.str(); } IloNumVar v(env, x.getLB(), x.getUB(), x.getType(), x.getName()); // Normalize objective function to 'minimize' double coef = problem->getObjCoef(x); if ( problem->getObjSense() != IloObjective::Minimize ) coef *= -1.0; primalObj += coef * v; // Record the index that the copied variable has in the // block model. idxMap.insert(IdxMap::value_type(x, primalVars.getSize())); primalVars.add(v); // Mark the rows that are intersected by this column // so that we can collect them later. RowSet const &intersected = problem->getIntersectedRows(x); for (RowSet::const_iterator it = intersected.begin(); it != intersected.end(); ++it) rowSet.insert(*it); } else idxMap.insert(IdxMap::value_type(x, -1)); } // Now copy all rows that intersect block variables. for (IloInt i = 0; i < problemRanges.getSize(); ++i) { IloRange r = problemRanges[i]; if ( rowSet.find(r) == rowSet.end() ) continue; // Create a copy of the row, normalizing it to '<=' double factor = 1.0; if ( r.getLB() > -IloInfinity ) factor = -1.0; IloRange primalR(env, factor < 0 ? -r.getUB() : r.getLB(), factor < 0 ? -r.getLB() : r.getUB(), r.getName()); IloExpr lhs(env); for (IloExpr::LinearIterator it = r.getLinearIterator(); it.ok(); ++it) { IloNumVar v = it.getVar(); double const val = factor * it.getCoef(); if ( problem->getBlock(v) != number ) { // This column is not explicitly in this block. This means // that it is a column that will be fixed by the master. // We collect all such columns so that we can adjust the // dual objective function according to concrete fixings. // Store information about variables in this block that // will be fixed by master solves. fixed.push_back(FixData(primalRows.getSize(), origIdxMap[v], -val)); } else { // The column is an ordinary in this block. Just copy it. lhs += primalVars[idxMap[v]] * val; } } primalR.setExpr(lhs); primalRows.add(primalR); lhs.end(); } // Create the dual of the primal model we just created. // Note that makeDual _always_ returns a 'maximize' objective. IloObjective objective(env, primalObj, IloObjective::Minimize); makeDual(objective, primalVars, primalRows, &obj, &vars, &rows); objective.end(); primalRows.endElements(); primalRows.end(); primalVars.endElements(); primalVars.end(); primalObj.end(); // Create a model. IloModel model(env); model.add(obj); model.add(vars); model.add(rows); for (IloExpr::LinearIterator it = obj.getLinearIterator(); it.ok(); ++it) objMap.insert(ObjMap::value_type(it.getVar(), it.getCoef())); // Finally create the IloCplex instance that will solve // the problems associated with this block. char const **transargv = new char const *[argc + 3]; for (int i = 0; i < argc; ++i) transargv[i] = argv[i]; #if defined(USE_MPI) char extra[128]; sprintf (extra, "-remoterank=%d", static_cast<int>(number + 1)); transargv[argc++] = extra; (void)machines; #elif defined(USE_PROCESS) char extra[128]; sprintf (extra, "-logfile=block%04d.log", static_cast<int>(number)); transargv[argc++] = extra; (void)machines; #elif defined(USE_TCPIP) transargv[argc++] = machines[number]; #endif cplex = IloCplex(model, TRANSPORT, argc, transargv); delete[] transargv; // Suppress output from this block's solver. cplex.setOut(env.getNullStream()); cplex.setWarning(env.getNullStream()); }
int main (int argc, char **argv) { int result = 0; IloEnv env; try { static int indices[] = { 0, 1, 2, 3, 4, 5, 6 }; IloModel model(env); /* ***************************************************************** * * * * S E T U P P R O B L E M * * * * The model we setup here is * * Minimize * * obj: 3x1 - x2 + 3x3 + 2x4 + x5 + 2x6 + 4x7 * * Subject To * * c1: x1 + x2 = 4 * * c2: x1 + x3 >= 3 * * c3: x6 + x7 <= 5 * * c4: -x1 + x7 >= -2 * * q1: [ -x1^2 + x2^2 ] <= 0 * * q2: [ 4.25x3^2 -2x3*x4 + 4.25x4^2 - 2x4*x5 + 4x5^2 ] + 2 x1 <= 9.0 * q3: [ x6^2 - x7^2 ] >= 4 * * Bounds * * 0 <= x1 <= 3 * * x2 Free * * 0 <= x3 <= 0.5 * * x4 Free * * x5 Free * * x7 Free * * End * * * * ***************************************************************** */ IloNumVarArray x(env); x.add(IloNumVar(env, 0, 3, "x1")); x.add(IloNumVar(env, -IloInfinity, IloInfinity, "x2")); x.add(IloNumVar(env, 0, 0.5, "x3")); x.add(IloNumVar(env, -IloInfinity, IloInfinity, "x4")); x.add(IloNumVar(env, -IloInfinity, IloInfinity, "x5")); x.add(IloNumVar(env, 0, IloInfinity, "x6")); x.add(IloNumVar(env, -IloInfinity, IloInfinity, "x7")); for (IloInt i = 0; i < x.getSize(); ++i) x[i].setObject(&indices[i]); IloObjective obj = IloMinimize(env, 3*x[0] - x[1] + 3*x[2] + 2*x[3] + x[4] + 2*x[5] + 4*x[6], "obj"); model.add(obj); IloRangeArray linear(env); linear.add(IloRange(env, 4.0, x[0] + x[1], 4.0, "c1")); linear.add(IloRange(env, 3.0, x[0] + x[2], IloInfinity, "c2")); linear.add(IloRange(env, -IloInfinity, x[5] + x[6], 5.0, "c3")); linear.add(IloRange(env, -2.0, -x[0] + x[6], IloInfinity, "c4")); for (IloInt i = 0; i < linear.getSize(); ++i) linear[i].setObject(&indices[i]); model.add(linear); IloRangeArray quad(env); quad.add(IloRange(env, -IloInfinity, -x[0]*x[0] + x[1] * x[1], 0, "q1")); quad.add(IloRange(env, -IloInfinity, 4.25*x[2]*x[2] - 2*x[2]*x[3] + 4.25*x[3]*x[3] + -2*x[3]*x[4] + 4*x[4]*x[4] + 2*x[0], 9.0, "q2")); quad.add(IloRange(env, 4.0, x[5]*x[5] - x[6]*x[6], IloInfinity, "q3")); for (IloInt i = 0; i < quad.getSize(); ++i) quad[i].setObject(&indices[i]); model.add(quad); /* ***************************************************************** * * * * O P T I M I Z E P R O B L E M * * * * ***************************************************************** */ IloCplex cplex(model); cplex.setParam(IloCplex::Param::Barrier::QCPConvergeTol, 1e-10); cplex.solve(); /* ***************************************************************** * * * * Q U E R Y S O L U T I O N * * * * ***************************************************************** */ IloNumArray xval(env); IloNumArray slack(env); IloNumArray qslack(env); IloNumArray cpi(env); IloNumArray rpi(env); IloNumArray qpi; cplex.getValues(x, xval); cplex.getSlacks(slack, linear); cplex.getSlacks(qslack, quad); cplex.getReducedCosts(cpi, x); cplex.getDuals(rpi, linear); qpi = getqconstrmultipliers(cplex, xval, quad); /* ***************************************************************** * * * * C H E C K K K T C O N D I T I O N S * * * * Here we verify that the optimal solution computed by CPLEX * * (and the qpi[] values computed above) satisfy the KKT * * conditions. * * * * ***************************************************************** */ // Primal feasibility: This example is about duals so we skip this test. // Dual feasibility: We must verify // - for <= constraints (linear or quadratic) the dual // multiplier is non-positive. // - for >= constraints (linear or quadratic) the dual // multiplier is non-negative. for (IloInt i = 0; i < linear.getSize(); ++i) { if ( linear[i].getLB() <= -IloInfinity ) { // <= constraint if ( rpi[i] > ZEROTOL ) { cerr << "Dual feasibility test failed for <= row " << i << ": " << rpi[i] << endl; result = -1; } } else if ( linear[i].getUB() >= IloInfinity ) { // >= constraint if ( rpi[i] < -ZEROTOL ) { cerr << "Dual feasibility test failed for >= row " << i << ":" << rpi[i] << endl; result = -1; } } else { // nothing to do for equality constraints } } for (IloInt q = 0; q < quad.getSize(); ++q) { if ( quad[q].getLB() <= -IloInfinity ) { // <= constraint if ( qpi[q] > ZEROTOL ) { cerr << "Dual feasibility test failed for <= quad " << q << ": " << qpi[q] << endl; result = -1; } } else if ( quad[q].getUB() >= IloInfinity ) { // >= constraint if ( qpi[q] < -ZEROTOL ) { cerr << "Dual feasibility test failed for >= quad " << q << ":" << qpi[q] << endl; result = -1; } } else { // nothing to do for equality constraints } } // Complementary slackness. // For any constraint the product of primal slack and dual multiplier // must be 0. for (IloInt i = 0; i < linear.getSize(); ++i) { if ( fabs (linear[i].getUB() - linear[i].getLB()) > ZEROTOL && fabs (slack[i] * rpi[i]) > ZEROTOL ) { cerr << "Complementary slackness test failed for row " << i << ": " << fabs (slack[i] * rpi[i]) << endl; result = -1; } } for (IloInt q = 0; q < quad.getSize(); ++q) { if ( fabs (quad[q].getUB() - quad[q].getLB()) > ZEROTOL && fabs (qslack[q] * qpi[q]) > ZEROTOL ) { cerr << "Complementary slackness test failed for quad " << q << ":" << fabs (qslack[q] * qpi[q]) << endl; result = -1; } } for (IloInt j = 0; j < x.getSize(); ++j) { if ( x[j].getUB() < IloInfinity ) { double const slk = x[j].getUB() - xval[j]; double const dual = cpi[j] < -ZEROTOL ? cpi[j] : 0.0; if ( fabs (slk * dual) > ZEROTOL ) { cerr << "Complementary slackness test failed for ub " << j << ": " << fabs (slk * dual) << endl; result = -1; } } if ( x[j].getLB() > -IloInfinity ) { double const slk = xval[j] - x[j].getLB(); double const dual = cpi[j] > ZEROTOL ? cpi[j] : 0.0; if ( fabs (slk * dual) > ZEROTOL ) { cerr << "Complementary slackness test failed for lb " << j << ": " << fabs (slk * dual) << endl; result = -1; } } } // Stationarity. // The difference between objective function and gradient at optimal // solution multiplied by dual multipliers must be 0, i.e., for the // optimal solution x // 0 == c // - sum(r in rows) r'(x)*rpi[r] // - sum(q in quads) q'(x)*qpi[q] // - sum(c in cols) b'(x)*cpi[c] // where r' and q' are the derivatives of a row or quadratic constraint, // x is the optimal solution and rpi[r] and qpi[q] are the dual // multipliers for row r and quadratic constraint q. // b' is the derivative of a bound constraint and cpi[c] the dual bound // multiplier for column c. IloNumArray kktsum(env, x.getSize()); // Objective function. for (IloExpr::LinearIterator it = obj.getLinearIterator(); it.ok(); ++it) kktsum[idx(it.getVar())] = it.getCoef(); // Linear constraints. // The derivative of a linear constraint ax - b (<)= 0 is just a. for (IloInt i = 0; i < linear.getSize(); ++i) { for (IloExpr::LinearIterator it = linear[i].getLinearIterator(); it.ok(); ++it) kktsum[idx(it.getVar())] -= rpi[i] * it.getCoef(); } // Quadratic constraints. // The derivative of a constraint xQx + ax - b <= 0 is // Qx + Q'x + a. for (IloInt q = 0; q < quad.getSize(); ++q) { for (IloExpr::LinearIterator it = quad[q].getLinearIterator(); it.ok(); ++it) kktsum[idx(it.getVar())] -= qpi[q] * it.getCoef(); for (IloExpr::QuadIterator it = quad[q].getQuadIterator(); it.ok(); ++it) { kktsum[idx(it.getVar1())] -= qpi[q] * xval[idx(it.getVar2())] * it.getCoef(); kktsum[idx(it.getVar2())] -= qpi[q] * xval[idx(it.getVar1())] * it.getCoef(); } } // Bounds. // The derivative for lower bounds is -1 and that for upper bounds // is 1. // CPLEX already returns dj with the appropriate sign so there is // no need to distinguish between different bound types here. for (IloInt j = 0; j < x.getSize(); ++j) kktsum[j] -= cpi[j]; for (IloInt j = 0; j < kktsum.getSize(); ++j) { if ( fabs (kktsum[j]) > ZEROTOL ) { cerr << "Stationarity test failed at index " << j << ": " << kktsum[j] << endl; result = -1; } } if ( result == 0) { // KKT conditions satisfied. Dump out the optimal solutions and // the dual values. streamsize oprec = cout.precision(3); ios_base::fmtflags oflags = cout.setf(ios::fixed | ios::showpos); cout << "Optimal solution satisfies KKT conditions." << endl; cout << " x[] = " << xval << endl; cout << " cpi[] = " << cpi << endl; cout << " rpi[] = " << rpi << endl; cout << " qpi[] = " << qpi << endl; cout.precision(oprec); cout.flags(oflags); } } catch (IloException& e) { cerr << "Concert exception caught: " << e << endl; result = -1; } catch (...) { cerr << "Unknown exception caught" << endl; result = -1; } env.end(); return result; } // END main