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:

Maximize
x1 + 2x2 + 3x3
subject to
-x1 + x2 + x3 20
x1 - 3x2 + x3 30
with these bounds
0 x1 40
0 x2 +
0 x3 +

General Structure of a CPLEX Concert Technology Application

The first operation is to create the environment object env, and the last operation is to destroy it by calling env.end(). The rest of the code is enclosed in a try/catch clause to gracefully handle any errors that may occur.

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:

  x.add(obj(2.0) + c[0]( 1.0) + c[1](-3.0));

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.

Modeling by Nonzero Elements

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.

  // -------------------------------------------------------------- -*- C++ -*-
  // File: examples/src/ilolpex1.cpp
  // Version 8.1
  // --------------------------------------------------------------------------
  //  Copyright (C) 1999-2002 by ILOG.
  //  All Rights Reserved.
  //  Permission is expressly granted to use this example in the
  //  course of developing applications that use ILOG products.
  // --------------------------------------------------------------------------
  //
  // ilolpex1.cpp - Entering and optimizing a problem.  Demonstrates different
  // methods for creating a problem.  The user has to choose the method
  // on the command line:
  //
  //    ilolpex1  -r     generates the problem by adding rows
  //    ilolpex1  -c     generates the problem by adding columns
  //    ilolpex1  -n     generates the problem by adding a list of coefficients
  
  #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);
  
        // Optimize the problem and obtain solution.
        if ( !cplex.solve() ) {
           env.error() << "Failed to optimize LP" << endl;
           throw(-1);
        }
  
        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
  
  
  static void usage (const char *progname)
  {
     cerr << "Usage: " << progname << " -X" << endl;
     cerr << "   where X is one of the following options:" << endl;
     cerr << "      r          generate problem by row" << endl;
     cerr << "      c          generate problem by column" << endl;
     cerr << "      n          generate problem by nonzero" << endl;
     cerr << " Exiting..." << endl;
  } // END usage
  
  
  // 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();
  
     x.add(IloNumVar(env, 0.0, 40.0));
     x.add(IloNumVar(env));
     x.add(IloNumVar(env));
     model.add(IloMaximize(env, x[0] + 2 * x[1] + 3 * x[2]));
  
     c.add( - x[0] +     x[1] + x[2] <= 20);
     c.add(   x[0] - 3 * x[1] + x[2] <= 30);
     model.add(c);
  
  }  // END populatebyrow
  
  
  // To populate by column, we first create the range constraints and the
  // objective, and then create the variables and add them to the ranges and
  // objective using column expressions.
  
  static void
  populatebycolumn (IloModel model, IloNumVarArray x, IloRangeArray c)
  {
     IloEnv env = model.getEnv();
  
     IloObjective obj = IloMaximize(env);
     c.add(IloRange(env, -IloInfinity, 20.0));
     c.add(IloRange(env, -IloInfinity, 30.0));
  
     x.add(IloNumVar(obj(1.0) + c[0](-1.0) + c[1]( 1.0), 0.0, 40.0));
     x.add(IloNumVar(obj(2.0) + c[0]( 1.0) + c[1](-3.0)));
     x.add(IloNumVar(obj(3.0) + c[0]( 1.0) + c[1]( 1.0)));
  
     model.add(obj);
     model.add(c);
  
  }  // END populatebycolumn
  
  
  // To populate by nonzero, we first create the rows, then create the
  // columns, and then change the nonzeros of the matrix 1 at a time.
  
  static void
  populatebynonzero (IloModel model, IloNumVarArray x, IloRangeArray c)
  {
     IloEnv env = model.getEnv();
  
     IloObjective obj = IloMaximize(env);
     c.add(IloRange(env, -IloInfinity, 20.0));
     c.add(IloRange(env, -IloInfinity, 30.0));
  
     x.add(IloNumVar(env, 0.0, 40.0));
     x.add(IloNumVar(env));
     x.add(IloNumVar(env));
  
     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
  
  


Previous Page: Handling Errors   Return to Top Next Page: Writing and Reading Models and Files