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