Thursday, December 22, 2011

Extracting Variables in CPLEX

It's never happened to me, but apparently some CPLEX users (working with one of the programming APIs) inherit a fully formed problem, an instance of IloCplex or perhaps IloModel, without having access to the Concert code that constructed it.  You only need the problem object to solve it and get the solution status and objective value; but in order to get the values of the variables in an optimal solution, you need to pass either an array of variables (IloNumVar[]) or individual variables in function calls (so that CPLEX can return values matched to the corresponding variables).  If all you have is the problem object, how do you know what the variables are?

This has been asked more than once on help forums, so I list below a Java function that I believe will extract all variables from an instance of IloCplex.  Something quite similar should work in the C++ API, and hopefully if you use the C API you can make the leap from the Java code.  The Python API seems to provide a VariablesInterface class that I think provides a mechanism (if it's even needed in Python -- I don't really know).  I'm blissfully ignorant about the Matlab API.

I've tried to stress-test the Java code, but nonetheless you use it at your own peril.


  private IloNumVar[] parse(IloCplex cplex) throws IloException {
    HashSet<IloNumVar> vars = new HashSet<IloNumVar>();
    Iterator it = cplex.iterator();
    IloLinearNumExpr expr;
    IloLinearNumExprIterator it2;
    while (it.hasNext()) {
      IloAddable thing = (IloAddable) it.next();
      if (thing instanceof IloRange) {
        expr = (IloLinearNumExpr) ((IloRange) thing).getExpr();
        it2 = expr.linearIterator();
        while (it2.hasNext()) {
          vars.add(it2.nextNumVar());
        }
      } else if (thing instanceof IloObjective) {
        expr = (IloLinearNumExpr) ((IloObjective) thing).getExpr();
        it2 = expr.linearIterator();
        while (it2.hasNext()) {
          vars.add(it2.nextNumVar());
        }
      } else if (thing instanceof IloSOS1) {
        vars.addAll(Arrays.asList(((IloSOS1) thing).getNumVars()));
      } else if (thing instanceof IloSOS2) {
        vars.addAll(Arrays.asList(((IloSOS2) thing).getNumVars()));
      } else if (thing instanceof IloLPMatrix) {
        vars.addAll(Arrays.asList(((IloLPMatrix) thing).getNumVars()));
      }
    }
    IloNumVar[] varray = vars.toArray(new IloNumVar[1]);
    return varray;
  } 
 
Update: I have updated the code, incorporating some suggestions I received on a CPLEX forum. The parser is now a static method in a class of its own. You can download the source code (one file, plain text) from Google Docs. To use it, you will likely want to change the package name. Also feel free to delete the various print statements, which are there only for demonstration purposes.

4 comments:

  1. O' Java, the horror! [No "switch"ing on object types? That's _so_ behind the times.]

    Unfortunately, Concert Technology makes things even worse: No use of generics, thus: no type-safe for-each-loop for iterators? Multiple getExpr() and getNumVars() methods, but no common interface? ...That's not just "behind the times", that's really bad software design.

    ReplyDelete
  2. I'd reserver "horror" for C++ (and "terror" for Perl).

    I don't have a problem with multiple getters. The Java API does have some rough edges (I find IloNumVar being an interface but not a class a bit funky). Part of it is that the C and C++ interfaces, the latter using separate models and problem classes and requiring an IloEnvironment class to sort things out, came first. The Java API merges the model and problem (IloModel and IloCplex in C++) into one class (IloCplex), and jettisons the environment class (thank you!). My guess is that if the Java API had been built from scratch, without regard to the C and C++ APIs, things would be different.

    As far as generics go, I wouldn't rule out the possibility of using them. I'm inexperienced using Java's introspection features, so I took the easy way out writing my code. It may not be possible to streamline the parsing with generics, but it also may just be beyond my coding skills. That's also true of switching on object type; I didn't try (if-else was easier), so I don't know if it could be done.

    I agree, though, that Java is behind the times ... in the sense that there's a new programming language popping up every 30 minutes or so, and Java has been around for a while.

    ReplyDelete
  3. Great! cplex doesn't allow to access to vars values when you import a model (using the importModel method for a simple lp file).. your solution bypass this problem

    ReplyDelete

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 OR-Exchange.