You have it almost all right, with a few caveats. Let's say I want to implement a Foo Lazy Constraint:
#include <ilocplex/ilocplex.h>
#include <ilocplex/ilocplexi.h>
struct Foo : IloCplex::LazyConstraintCallbackI {
// Your code goes here...
};
There are three main things you need to put inside your class; two of these are documented (more or less) and one comes from my experience:
- A reference to the current CPLEX environment and any variable you might want to use, captured via the constructor. Pass the environment up to the parent constructor. This is the part that comes from my experience... indeed, you might think that to access the CPLEX environment object you can just use the inherited
getEnv()
method, but for me this consistently causes crashes if I do something like IloExpr lhs{getEnv()};
. Perhaps some more knowledgeable user can explain why.
#include <ilocplex/ilocplex.h>
#include <ilocplex/ilocplexi.h>
struct Foo : IloCplex::LazyConstraintCallbackI {
IloEnv& env;
IloNumVar& x;
Foo(IloEnv& env, IloNumVar& x) :
LazyConstraintCallbackI{env}, env{env}, x{x} {}
};
- Implement a
duplicateCallback()
method for CPLEX to use internally. This always looks the same, I basically just copy-paste it from project to project.
#include <ilocplex/ilocplex.h>
#include <ilocplex/ilocplexi.h>
struct Foo : IloCplex::LazyConstraintCallbackI {
IloEnv& env;
IloNumVar& x;
Foo(IloEnv& env, IloNumVar& x) :
LazyConstraintCallbackI{env}, env{env}, x{x} {}
[[nodiscard]] IloCplex::CallbackI* duplicateCallback() const override {
return new(env) Foo{*this};
}
};
- Implement a
main()
function that adds the cut using the parent's add()
method. This is the juicy part... here you access the current solution, decide whether you need a new cut and, in this case, add it.
#include <ilocplex/ilocplex.h>
#include <ilocplex/ilocplexi.h>
struct Foo : IloCplex::LazyConstraintCallbackI {
IloEnv& env;
IloNumVar& x;
Foo(IloEnv& env, IloNumVar& x) :
LazyConstraintCallbackI{env}, env{env}, x{x} {}
[[nodiscard]] IloCplex::CallbackI* duplicateCallback() const override {
return new(env) Foo{*this};
}
void main() override {
const auto x_value = getValue(x);
if(x_value < 2.0) {
try {
add(x >= 2.0);
} catch(IloException& e) {
std::cerr << "Exception while adding lazy constraint for x = " << x_value << ": " << e.getMessage() << "\n";
throw;
}
}
};
Note how I can use the inherited getValue()
to access variables' values, and finally add()
to eventually add a cut. If add()
is called at least once, CPLEX knows that it needs to re-solve the node; otherwise, it assumes that no cuts are needed and proceeds with branching. If the lazy constraint should not join the "global" pool of constraints, but should only be active in the subtree of the current node, use addLocal()
instead of add()
.