Tuesday, September 13, 2022

With CPLEX, You're Not In Until You're In

(For a suitable soundtrack for this post, try this three minute video.)

A question popped up on the CPLEX support forum that reminded me of a slightly obscure and occasionally import aspect of the CPLEX programming APIs. What follows holds true for the Java API, and I believe it applies to the C++ and maybe C APIs. It may also hold for some of the other APIs.

The questioner, working in Java, was trying to use the "annotated Benders" approach to a MIP model. The programming issue actually is not specific to Benders decomposition, annotated or otherwise. The problem occurred in a loop where the user was defining integer variables and trying to associate them with the master problem. Here's the code:

for(int k=0; k < x.length; k++) {
     x [k] = cplex.intVar(0,cluster.getNbVeh(),"x_"ez_plusk);
     cplex.setAnnotation(benders, x[k],
                          IloCplex.CPX_BENDERS_MASTERVALUE);
}
 

The error message from CPLEX was "object is unknown to IloCplex", which was understandably confusing and yet, from personal experience, predictable.

Here is the problem: at the point that variable x[k] is fed to the setAnnotation() function, x[k] is initialized (in the programming sense that it has content) but is not yet part of the model instance (since it has not be used in a constraint or objective function). In fact, even if it had been added to a constraint or objective expression, it would still not be part of the model until the constraint or objective was itself added to the model. This is despite the fact that the model instance (here cplex) was used to create the variable.

One possible fix would be to use x[k] in something that was added to the model before trying to set its annotation. A simpler fix is just to insert the line cplex.add(x[k]); in between where x[k] is defined and where the annotation is set. This nominally adds x[k] to the model (even though not in any specific role), which is enough to make it a "known object" to CPLEX.

I learned (the hard way) about this long before CPLEX added support for Benders decomposition. When you look up solution values after solving a model, CPLEX provides convenient functions to look up either the value of a single variable or the value of an array of variables. In many applications, it is tempting to use the latter. At the same time, in some applications you may want to define an array of variables but not use all of them. For instance, suppose I have a network where some nodes have demands and some do not, and I want to define a binary variable x[k] just at nodes k that do have demand. If I define x to have dimension equal to the number of nodes and then only initialize x[k] at the nodes with demands, passing x to a method to retrieves values will bomb because some of the entries of x are null. So I cleverly initialize x[k] for all k ... and the attempt to retrieve values still bombs, because x[k] was never included in the model (in any constraint or objective function) when node k has no demand. Again, the solution is to call add(x[k]) on the model object before solving, which means the model knows about x[k] even if it never appears in any part of the actual model.