Monday, August 16, 2010

Iterating over a CPLEX Model: the Sequel

In a previous post is showed methods for iterating over the constraints of a CPLEX model.  One can also iterate over the model as a whole, but it turns out there are some quirks.  I'll enumerate a couple here. Whether they are also quirks in the C++ API I'm not sure.

The example model is the same small LP I used before, so I won't repeat it here. I will repeat the Java code to generate the model, though, so that I can tweak it later.

IloCplex cplex = new IloCplex();
IloNumVar[] vars = new IloNumVar[3];
vars[0] = cplex.numVar(0, Double.MAX_VALUE, "x");
vars[1] = cplex.numVar(0, Double.MAX_VALUE, "y");
vars[2] = cplex.numVar(0, Double.MAX_VALUE, "z");
cplex.addMaximize(
        cplex.scalProd(new double[]{1., 2., 3.}, vars),
        "Obj")
      );
// first constraint: x + y + z <= 5
cplex.addLe(cplex.sum(vars), 5., "Con1");
// second constraint: y + 2z <= 3
cplex.addLe(
  cplex.scalProd(new double[]{0., 1., 2.}, vars),
                 3., "Con2");
// third constraint: x = z
cplex.addEq(vars[0], vars[2], "Con3");

This is pretty vanilla stuff.  If we print cplex.toString(), we get a nice, readable representation of the model:

IloModel  {
IloMaximize  : 1.0*x + 2.0*y + 3.0*z
IloRange Con1 : -infinity <= 1.0*x + 1.0*y + 1.0*z <= 5.0
IloRange Con2 : -infinity <= 1.0*y + 2.0*z <= 3.0
IloRange Con3 : 0.0 <= 1.0*x - 1.0*z <= 0.0
}

One (minor?) surprise so far: the objective function's somewhat unimaginative name ("Obj") did not print out. Now suppose that, for some reason, I want to disassemble the model into its components.  CPLEX will give me an iterator (an instance of java.util.Iterator) that I can use to iterate over the model.  Here comes the first surprise. Consider the following code, which should iterate over the model and print object names along with some context information:

Iterator it = cplex.iterator();
while (it.hasNext()) {
  Object thing = it.next();
  if (thing instanceof IloNumVar) {
    System.out.print("Variable ");
  }
  else if (thing instanceof IloRange) {
    System.out.print("Constraint ");
  }
  else if (thing instanceof IloObjective) {
    System.out.print("Objective ");
  }
  System.out.println("named " +
                     ((IloAddable) thing).getName());
}

Here's the output:

Objective named null
Variable named Con1
Variable named Con2
Variable named Con3

Notice anything funny?  Besides being consistent about denying that the objective function has a name, the iterator identifies the three constraints as variables (and does not identify any of the variables as anything). I'm told by an insider that listing the constraints as variables turns out to be a "feature" of CPLEX: for somewhat arcane reasons associated with using IloConstraint instances as arguments to the ifThen() method, IloRange implements the IloNumVar interface. At any rate, to use the current cliche, it is what it is.

So I have three things to fix: get CPLEX to acknowledge the name of the objective function; get CPLEX to distinguish constraints from variables; and get CPLEX to list the variables. There is an easy workaround for the first issue (objective name): I change the statement introducing the objective function to

cplex.addMaximize(
        cplex.scalProd(new double[]{1., 2., 3.}, vars)
      ).setName("Obj");

Applying the setName method to the instance of IloObjective returned by addMaximize gets the name installed correctly. Fixing the third issue (finding the variables) is also simple: I can just add them explicitly into the model. It's redundant, given that they already occur in the model, but seems harmless and gets them listed. I'll insert the following line at the end of model construction:

cplex.add(vars);

The model output changes to

The model:
IloModel  {
IloMaximize Obj : 1.0*x + 2.0*y + 3.0*z
IloRange Con1 : -infinity <= 1.0*x + 1.0*y + 1.0*z <= 5.0
IloRange Con2 : -infinity <= 1.0*y + 2.0*z <= 3.0
IloRange Con3 : 0.0 <= 1.0*x - 1.0*z <= 0.0
x
y
z
}

(note that the variables are now listed at the end of the model). An alternative is to iterate over the expressions in the objective and constraint, using an  IloLinearNumExprIterator, and pick out the variables. It's less efficient, and it will find every variable multiple times, but if someone else generated the model and did not explicitly (and redundantly) add the variables, it may be the only viable approach. (And, let's face it, if you wrote the model yourself, you probably don't need to iterate over it looking for variables.)


Finally, the middle issue (distinguishing variables from constraints) is also fairly easy to solve. I can use some sort of naming convention to let me tell them apart by names, but there's an even easier method.  A constraint will appear as an instance of both IloNumVar and IloRange, so I can use the latter fact to screen out constraints.  I'll change the first if statement to

if (thing instanceof IloNumVar &&
    !(thing instanceof IloRange)) {

which skips over the constraints. With those changes, the output finally looks right:

Objective named Obj
Constraint named Con1
Constraint named Con2
Constraint named Con3
Variable named x
Variable named y
Variable named z

7 comments:

  1. Hi Paul,
    I am looking for a way to list all variable names and their values. I defined my CplexModel in a function, and trying to print the values after solving in another function. I looked over and over again to C++ API but couldn't find a way to do it. I'm afraid even your solution (cplex.iterator()) is not available in C++.

    Actually I don't understand why this is so difficult. When i call cplex.writeSolutions("file.sol") a nice solution file with variable names and values are printed. But somehow I cannot reach these values with a function.

    There are still some tricks that I can apply, but I really want to use an easy function for this task. Do you know any function for this operation? (it could be for Java, maybe I can find the equivalent one for C++)

    ReplyDelete
    Replies
    1. Sertalp,

      First, just to be clear, iterating over a model is potentially useful when the model is imported from an external source (such as a file). If you construct the model in your own code, you should never need to iterate over it (because you know what it contains).

      Second, the C++ API has a model iterator class (IloModel::Iterator) that should work about the same way my Java solution works.

      I don't think CPLEX provides a method for printing a solution, because every user is going to want to format the output differently. If the model builder assigns names to the variables in their constructors (a practice I always recommend), printing the solution should be easy. When you call IloCplex::getValues to get the solution, you pass it an IloNumVarArray (call that 'vars') and an IloNumArray (call that 'vals'). In the print statement, you just need to pair each vals[i] with vars[i].getName().

      If the variables are not named, you'll need to create names for them somewhere (in a string array, say 'names') and pair vals[i] with names[i].

      Delete
    2. Actually I know that I should use getValues to get values :) I have a question about it. For my code, I have
      IloModel model
      which is my basic model. Later, I want to add some cuts to this model and get another one, hence I'm creating a clone of it as
      IloModel localCopy(env);
      localCopy.add(*model);
      IloCplex cplex(localCopy);
      So, what I wonder is if I solve "cplex" then does it also change the original values of the IloNumVar objects? As far as I understand, variables are special to the models, so I wonder what happens when I clone them.

      Delete
    3. Ah, I solved my problem. As you said, I used the iterator class.
      Here is my solution for future reference

      typedef IloIterator IloNumVarIterator; // Defines an iterator for IloNumVar
      ...
      for (IloNumVarIterator it(env); it.ok(); ++it) {
      try {
      IloNumVar ext = *it;
      cout << ext.getName() << ": " << cplex.getValue(ext) << endl;
      } catch (IloException &e){
      cerr << "Exception for variable " << ext.getName() << endl;
      }
      }

      As you mentioned, usage of the IloNumVar itself as a parameter is the proper way. I just coded this way, because after cloning a model, the IloNumVar objects are also cloned (I guess).

      Thanks for your help!


      Delete
    4. I'm not positive - it has been a long time since I used C++ - but I don't think the variables and original constraints are cloned; I think the same variables and constraints are in both models. The extracted IloCplex objects are distinct, so the solution to one will not affect the solution to the other even though the variables are the same.

      Delete
  2. Good morning Sir,
    I try to solve a MILP problem using a cutting-plane methode. I start solving the relax problem and I add constraint after each iteration. I use cplex and I am programing in c++. I would like to know how to stop a process after an iteration, add an excepted constraint to the model,and solve the new problem with the new constrainst.
    Happy new year in advance.

    ReplyDelete
    Replies
    1. Your question does not seem related to the topic of this post. Also, it is a bit ambiguous as to what you mean by "after an iteration". Do you want to stop after each pivot and check the current solution, or solve the current relaxation to optimality, then add a constraint? If the latter, there is nothing special required: call solve(); get the solution; generate the cut; call add() on the IloModel object to add the cut; loop.

      Delete

Due to intermittent spamming, comments are being moderated. If this is your first time commenting on the blog, please read the Ground Rules for Comments. In particular, if you want to ask an operations research-related question not relevant to this post, consider asking it on Operations Research Stack Exchange.