If you have trouble understanding how branch callbacks work: did you take a look at the iloadmipex1.cpp and iloadmipex3.cpp examples that come with CPLEX? Those examples use branch callbacks and could help you to understand how things work.
With respect to
Through the use function, cplex invokes a StartEndBranch object with two parameters: opl and zero. Are these parameters passed everytime a node is considered for branching? Even if it is a composite node with attached info?
What you pass to the use function is an instance of class StartEndBranch. The arguments opl and zero are passed to the StartEndBranch constructor to instantiate this class. At each node in the branch and bound tree CPLEX invokes the main() method of the callback instance that was passed to use().
If you use
void main () {return;}
as main() method and you get a crash, does it help to register your callback with this line of code (note the additional '(env)')?
cplex.use(new (env) StartEndBranch(env, &opl, 0));
Do you get any meaningful backtrace for the crash? Is it a hard crash or just an uncaught exception.
Here is a (mostly untested) example code that mimicks a 4-way branch by means of two subsequent binary branches.
#include <ilcplex/ilocplex.h>
#include <ilconcert/iloiterator.h>
/** Branch callback that allows creation of 4-way branches.
* A 4-way branch is realized as two levels of 2-way branches.
* In this simple implementation we just use 4-way branches that branch
* up and down on two variables. More complicated ways of 4-way branches
* are of course possible.
*/
class Branch4 : public IloCplex::BranchCallbackI {
/** Node data that specifies the branch(es) to be taken on the second
* level of a 4-way branch.
* Since we use a simple 4-way branching this class is also simple: it
* just specifies the variables to branch on as well as the branching
* direction and the bound to use.
*/
struct Level2 : public IloCplex::MIPCallbackI::NodeData {
IloNumVar const leftVar; /**< Variable for left branch. */
IloCplex::BranchDirection const leftDir; /**< Direction for left branch. */
double const leftBound; /**< Bound for left branch. */
IloNumVar const rightVar; /**< Variable for right branch. */
IloCplex::BranchDirection const rightDir; /**< Direction for right branch. */
double const rightBound; /**< Bound for right branch. */
/** Create branching information for a single child.
*/
Level2(IloNumVar lVar, IloCplex::BranchDirection lDir, double lBound)
: leftVar(lVar), leftDir(lDir), leftBound(lBound),
rightVar(0), rightDir(IloCplex::BranchDown), rightBound(IloInfinity)
{
}
/** Create branching information for two children.
*/
Level2(IloNumVar lVar, IloCplex::BranchDirection lDir, double lBound,
IloNumVar rVar, IloCplex::BranchDirection rDir, double rBound)
: leftVar(lVar), leftDir(lDir), leftBound(lBound),
rightVar(rVar), rightDir(rDir), rightBound(rBound)
{
}
};
/** Integer variables in the model.
* This array is required to select the variables on which we branch.
*/
IloNumVarArray vars;
public:
/** Constructor.
*/
Branch4(IloEnv env, IloNumVarArray v)
: IloCplex::BranchCallbackI(env), vars(v)
{
}
/** Function to duplicate this callback.
* This function is required by the BranchCallbackI super class.
*/
IloCplex::CallbackI *duplicateCallback() const {
return new (getEnv()) Branch4(getEnv(), vars);
}
/** Function that is invoked by CPLEX on each node.
* It is responsible for creating new branches. If the function neither
* explicitly prune()s the node nor explicitly creates at least one
* branch then CPLEX will use the branches it would have created itself.
*/
void main() {
// The objective function estimate for new nodes we create.
double const estimate = getObjValue();
// Figure out if we are in a 4-way branching. The intermediate
// nodes in a 4-way branching have node data attached to them that
// describe the next level to be created.
Level2 *const level2 = dynamic_cast<Level2 *>(getNodeData());
if ( level2 ) {
// We have a node data object. That means that we are at an
// intermediate node of a multi-level branch. We just create
// the branch that is described in the node data object.
int created = 0;
std::cout << "Creating second level of 4-way branch." << std::endl;
if ( level2->leftVar.getImpl() ) {
// A left-child is specified: create it.
makeBranch(level2->leftVar, level2->leftBound, level2->leftDir,
estimate);
++created;
}
if ( level2->rightVar.getImpl() ) {
// A left-child is specified: create it.
makeBranch(level2->rightVar, level2->rightBound, level2->rightDir,
estimate);
++created;
}
if ( created == 0 ) {
// No children were specified. That is an exceptional corner
// case. No children means the node should be pruned.
prune();
}
}
else {
// We don't have any node data. That is we need to decide what
// to do: Either create the branches that CPLEX would create
// or create a multi-way branching.
// For this example we just pick a random number and create
// a multi-way branching if the random number is a multiple of 7.
if ( (rand() % 7) == 0 ) {
// We use a very simple strategy to create a 4-way branching:
// Go through the variables and find the two variables
// That have the most fractional values. Then we perform all
// 4 possible up/down branching combinations on those two.
IloInt v1 = -1, v2 = -1;
double f1 = 2, f2 = 2;
IloNumArray vals(getEnv());
getValues(vals, vars);
for (IloInt i = 0; i < vars.getSize(); ++i) {
double const f = fabs(round(vals[i]) - vals[i]);
if ( v1 < 0 ) {
v1 = i; f1 = f;
}
else if ( v2 < 0 ) {
if ( f > f1 ) {
v2 = v1;
f2 = f1;
v1 = i;
f1 = f;
}
else {
v2 = i;
f2 = f;
}
}
else if ( f > f1 ) {
v2 = v1;
f2 = f1;
v1 = i;
f1 = f;
}
else if ( f > f2 ) {
v2 = i;
f2 = f;
}
}
if ( v1 >= 0 && v2 >= 0 ) {
// Setup information for two-level branches.
// On the first level we branch on v1 (this is done by
// calling makeBranch()), on the second level we branch
// on v2 (this is stored in the node data.
std::cout << "Creating a 4 way branch on "
<< vars[v1] << " (" << vals[v1] << ") and "
<< vars[v2] << " (" << vals[v2] << ")" << std::endl;
makeBranch(vars[v1], floor(vals[v1]), IloCplex::BranchDown, estimate,
new Level2(vars[v2], IloCplex::BranchDown, floor(vals[v2]),
vars[v2], IloCplex::BranchUp, ceil(vals[v2])));
makeBranch(vars[v1], ceil(vals[v1]), IloCplex::BranchUp, estimate,
new Level2(vars[v2], IloCplex::BranchDown, floor(vals[v2]),
vars[v2], IloCplex::BranchUp, ceil(vals[v2])));
}
vals.end();
}
else {
// Just do nothing. This will result in creation of the branches
// that CPLEX would have created.
}
}
}
};
/** main() function to test the callback.
* The function expects model files on the command line.
*/
int
main(int argc, char **argv)
{
srand(0);
for (int i = 1; i < argc; ++i) {
try {
// Create an IloCplex instance and load the model.
IloEnv env;
IloModel model(env);
IloCplex cplex(model);
cplex.importModel(model, argv[i]);
// Now get a list of all integer variables in the model.
// This list is required for the branch callback.
IloNumVarArray intVars(env);
for (IloIterator<IloNumVar> it(env); it.ok(); ++it) {
IloNumVar v = *it;
if ( v.getType() != IloNumVar::Float )
intVars.add(v);
}
std::cout << "Found " << intVars.getSize() << " integer variables."
<< std::endl;
// Register an instance of the branch callback with IloCplex.
cplex.use(new (env) Branch4(env, intVars));
// Solve the model.
if ( cplex.solve() )
std::cout << "Objective: " << cplex.getObjValue() << std::endl;
else
std:: cout << "INFEASIBLE" << std::endl;
env.end();
} catch (IloException& e) {
std::cerr << "IloException: " << e << std::endl;
}
}
return 0;
}
I hope this helps.
#CPLEXOptimizers#DecisionOptimization