Building and Solving A Small LP Model in C++
Building and Solving A Small LP Model in C++
A complete example of building and solving a small LP model can now be presented. This example demonstrates:
Example ilolpex1.cpp, which is one of the example programs in the standard CPLEX distribution, is an extension of the example presented in Introducing ILOG
CPLEX. It shows three different ways of creating a Concert Technology LP model, how to solve it using IloCplex, and how to access the solution. Here is the
problem that the example optimizes:
First the example creates the model object and, after checking the correctness of command line parameters, it creates empty arrays for storing the variables and range
constraints of the optimization model. Then, depending on the command line parameter, the example calls one of the functions populatebyrow(),
populatebycolumn(), or populatebynonzero(), to fill the model object with a representation of the optimization problem. These functions return the variable and
range objects in the arrays var and con which are passed to them as parameters.
After the model has been populated, the IloCplex algorithm object cplex is created and the model is extracted to it. The following call of method solve() invokes the
optimizer. If it fails to generate a solution, an error message is issued to the error stream of the environment, cplex.error(), and the integer -1 thrown as exception.
IloCplex provides the output streams out() for general logging, warning() for warning messages, and error() for error messages. They are preconfigured to cout,
cerr, and cerr respectively. Thus by default you will see logging output on the screen when invoking the method solve(). This can be turned off by calling
cplex.setOut(env.getNullStream()), that is, by redirecting the out() stream of the IloCplex object cplex to the null stream of the environment.
If a solution is found, solution information is output through the channel, env.out() which is initialized to cout by default. The output operator << is defined for type
IloAlgorithm::Status as returned by the call to cplex.getStatus(). It is also defined for IloNumArray, the Concert Technology class for an array of numerical
values, as returned by the calls to cplex.getValues(), cplex.getDuals(), cplex.getSlacks(), and cplex.getReducedCosts(). In general, the output operator is defined
for any Concert Technology array of elements if the output operator is defined for the elements.
The functions named populateby* are purely about modeling and are completely decoupled from the algorithm IloCplex. In fact, they don't use the cplex object,
which is created only after executing one of these functions.
Modeling by Rows
The function populatebyrow creates the variables and adds them to the array x. Then the objective function and the constraints are created using expressions on the
variables stored in x. The range constraints are also added to the array of constraints c. The objective object and the constraints are added to the model.
Modeling by Columns
Function populatebycolumn can be viewed as the transpose of populatebyrow. While for simple examples like this one population by rows may seem the most
straightforward and natural approach, there are some models where modeling by column is a more natural or more efficient approach.
When modeling by columns, range objects are created with their lower and upper bound only. No expression is given-which is impossible since the variables are not
yet created. Similarly, the objective function is created with only its intended optimization sense, and without any expression. Next the variables are created and
installed in the already existing ranges and objective.
The description of how the newly created variables are to be installed in the ranges and objective is by means of column expressions, which are represented by the
class IloNumColumn. Column expressions consist of objects of class IloAddNumVar linked together with operator +. These IloAddNumVar objects are created using
operator() of the classes IloObjective and IloRange. They describe how to install a new variable to the invoking objective or range objects. For example obj(1.0)
creates an IloAddNumVar capable of adding a new modeling variable with a linear coefficient of 1.0 to the expression in obj. Column expressions can be built in loops
using operator +=.
Column expressions (objects of class IloNumColumn) are handle objects, like most other Concert Technology objects. The method end() must therefore be called to
delete the associated implementation object when it is no longer needed. However, for implicit column expressions, where no IloNumColumn object is explicitly
created, such as the ones used in this example, method end() should not be called.
The column expression is passed as a parameter to the constructor of class IloNumVar. For example the constructor IloNumVar(obj(1.0) + c[0](-1.0) + c[1]( 1.0),
0.0, 40.0) creates a new modeling variable with lower bound 0.0, upper bound 40.0 and, by default, type ILOFLOAT, and adds it to the objective obj with a linear
coefficient of 1.0, to the range c[0] with a linear coefficient of -1.0 and to c[1] with a linear coefficient of 1.0. Column expressions can be used directly to construct
numerical variables with default bounds [0, IloInfinity] and type ILOFLOAT, as in the following statement:
where IloNumVar does not need to be explicitly written. Here, the C++ compiler recognizes that an IloNumVar object needs to be passed to the add method and
therefore automatically calls the constructor IloNumVar(IloNumColumn) in order to create the variable from the column expression.
The last of the three functions that can be used to build the model is populatebynonzero(). It creates objects for the objective and the ranges without expressions, and
variables without columns. Then methods IloObjective::setCoef() and IloRange::setCoef() are used to set individual nonzero values in the expression of the
objective and the range constraints. As usual, the objective and ranges must be added to the model.
Complete Program
The complete program follows. You can also view it online in the file ilolpex1.cpp.
#include <ilcplex/ilocplex.h>
ILOSTLBEGIN
static void
usage (const char *progname),
populatebyrow (IloModel model, IloNumVarArray var, IloRangeArray con),
populatebycolumn (IloModel model, IloNumVarArray var, IloRangeArray con),
populatebynonzero (IloModel model, IloNumVarArray var, IloRangeArray con);
int
main (int argc, char **argv)
{
IloEnv env;
try {
IloModel model(env);
if (( argc != 2 ) ||
( argv[1][0] != '-' ) ||
( strchr ("rcn", argv[1][1]) == NULL ) ) {
usage (argv[0]);
throw(-1);
}
IloNumVarArray var(env);
IloRangeArray con(env);
switch (argv[1][1]) {
case 'r':
populatebyrow (model, var, con);
break;
case 'c':
populatebycolumn (model, var, con);
break;
case 'n':
populatebynonzero (model, var, con);
break;
}
IloCplex cplex(model);
IloNumArray vals(env);
env.out() << "Solution status = " << cplex.getStatus() << endl;
env.out() << "Solution value = " << cplex.getObjValue() << endl;
cplex.getValues(vals, var);
env.out() << "Values = " << vals << endl;
cplex.getSlacks(vals, con);
env.out() << "Slacks = " << vals << endl;
cplex.getDuals(vals, con);
env.out() << "Duals = " << vals << endl;
cplex.getReducedCosts(vals, var);
env.out() << "Reduced Costs = " << vals << endl;
cplex.exportModel("lpex1.lp");
}
catch (IloException& e) {
cerr << "Concert exception caught: " << e << endl;
}
catch (...) {
cerr << "Unknown exception caught" << endl;
}
env.end();
return 0;
} // END main
// To populate by row, we first create the variables, and then use them to
// create the range constraints and objective.
static void
populatebyrow (IloModel model, IloNumVarArray x, IloRangeArray c)
{
IloEnv env = model.getEnv();
} // END populatebyrow
static void
populatebycolumn (IloModel model, IloNumVarArray x, IloRangeArray c)
{
IloEnv env = model.getEnv();
model.add(obj);
model.add(c);
} // END populatebycolumn
static void
populatebynonzero (IloModel model, IloNumVarArray x, IloRangeArray c)
{
IloEnv env = model.getEnv();
obj.setCoef(x[0], 1.0);
obj.setCoef(x[1], 2.0);
obj.setCoef(x[2], 3.0);
c[0].setCoef(x[0], -1.0);
c[0].setCoef(x[1], 1.0);
c[0].setCoef(x[2], 1.0);
c[1].setCoef(x[0], 1.0);
c[1].setCoef(x[1], -3.0);
c[1].setCoef(x[2], 1.0);
model.add(obj);
model.add(c);
} // END populatebynonzero