Search code examples
cplexoplcp-optimizer

How to fix errors when using opl.end() in main flow control loop


I'm batch running tens of thousands of tests using a loop inside a main flow control block. Each run of the loop spawns a new OPL model using the designated .dat data file and .mod model file (which is separate from the batch runner .mod file which solely contains the main flow control block).

Because every run of the loop creates a new model, the memory usage builds and builds until it eventually crashes the IDE when I don't use .end() statements to close all of the data structures created for each test. While most of the data structures close just fine, I've found that opl.end(); crashes the tester.

How do I close the OPL models without crashing the tester?

I've tried both the oplide application and the oplrun command line interface, both on macOS and Windows (though I need to run the tests on Windows in the end). I'm using CPLEX Optimization Studio 12.9.

I create the objects using:

// Create new model
var source = new IloOplModelSource("model-file.mod");
var def = new IloOplModelDefinition(source);
var cp = new IloCP();
var opl = new IloOplModel(def,cp);

// Set next test case as data source
var testCasePath = thisOplModel.TestCaseFilenames[testNumber];
var data = new IloOplDataSource(testCasePath);
opl.addDataSource(data);

// Generate model
opl.generate();

And I attempt to close them when I'm done by using:

data.end();
opl.end(); // Causes it to crash
cp.end();
def.end();
source.end();

Edited to add more specific information: I'm running an RCPSP scheduling problem, using my own model which is a modified version of the built-in example.

Here's a stripped-down version of the code I'm using. I created a run configuration containing just this batch runner .mod file, with the model file and data files it references stored elsewhere:

using CP;

// Import test case filename data
int NumberOfFiles = 3;
range FileIDs = 0..NumberOfFiles-1;
string TestCaseFilenames[FileIDs] = ["TestCase-01.dat", "TestCase-02.dat", "TestCase-03.dat"];

main {
  var testCaseDirectory = "~/Desktop/TestCases/";

  // Solve each test case in the list
  for (var testNumber = 0; testNumber < thisOplModel.NumberOfFiles; testNumber++) {
    // Create new model
    var source = new IloOplModelSource("RCPSP.mod");
    var def = new IloOplModelDefinition(source);
    var cp = new IloCP();
    var opl = new IloOplModel(def,cp);

    // Set CP parameters
    cp.param.TimeLimit = 5; // Number is in seconds
    cp.param.Workers = 4; // Number of computer cores used
    cp.param.TimeMode = "ElapsedTime"; // How to report time 
    cp.param.LogVerbosity = "Quiet"; // How much to write to the engine log

    // Set next test case as data source
    var testCaseFilename = thisOplModel.TestCaseFilenames[testNumber];
    var testCasePath = testCaseDirectory + testCaseFilename;
    var data = new IloOplDataSource(testCasePath);
    opl.addDataSource(data);

    // Generate model
    opl.generate();

    // Report test case name to script log for progress visibility
    writeln(testNumber+1 + ") Solving " + opl.TestCaseFilename + "...");

    // Solve model
    if (cp.solve()) { // Successfully found solution
      // Run solution validation code in model file's post-processing execute statement
        // If a solution is invalid, the entire bath runner will fail with an error at that test and line
      opl.postProcess();

      // Report some solve-dependent results to oplide log for visibility
      writeln(" - UB = " + cp.getObjValue() + ", LB = " + cp.getObjBound()); // Makespan
    } else { // Failed to find solution
      // Report no solution to oplide log for visibility
      writeln(" - No solution found.")
      writeln(" - Lower Bound = " + cp.getObjBound());
    }

    // Report some results to script log for visibility
    writeln(" - Status = " + cp.status); // Solver status
    writeln(" - Solve Time = " + cp.info.SolveTime + " sec"); // Time spent solving

    // End processes to prevent memory leaks
    data.end();
    opl.end(); // Causes it to crash
    cp.end();
    def.end();
    source.end();
  }

  // Confirm to user that tests are complete
  writeln();
  writeln("All done!");
}

Here's what the output should be (produced by commenting out the opl.end() line):

1) Solving TestCase-01.dat...
 - UB = 48, LB = 48
 - Status = 2
 - Solve Time = 1.299999952 sec
2) Solving TestCase-02.dat...
 - UB = 65, LB = 36
 - Status = 1
 - Solve Time = 5.019999981 sec
3) Solving TestCase-03.dat...
 - No solution found.
 - LB = 1
 - Status = 0
 - Solve Time = 5.010000229 sec

All done!

Instead, it just logs this output:

1) Solving TestCase-01.dat...
 - UB = 48, LB = 48
 - Status = 2
 - Solve Time = 1.299999952 sec

and then pops up a window with this error message:

Oplrun process is not responding, you must relaunch the Run Configuration.


Solution

  • We sorted this out by email. The reason for the crash is a bug in OPL. The problematic part of the code is something like this:

    tuple T {
      key int id;
      int intArray[0..0];
    }
    
    {T} tuples = ...;
    
    T tupleById[1..1];
    execute {
      for (var t in tuples)
        tupleById[t.id] = t;
    }
    

    The problem is the assigment of a tuple that contains an array or set member in scripting. This triggers a bug in OPL that then eventually leads to a crash. A workaround is to initialize tupleById without scripting via

    T tupleById[id in 1..1] = first({ t | t in tuples : t.id == id});
    

    The trick here is that all the generated sets are singletons as ids are unique. We then use the first() function to extract the first element from the singleton sets to get plain tuples.