I've been working on implementing Branch-and-Benders Cut using CPLEX version 20.1 with the C++ API, but I'm encountering some difficulties. I've followed the example provided by CPLEX, specifically ilobendersatsp.cpp, and have also looked through various resources online, including past questions on this platform, but I'm still struggling to get my code to work.
The problem I'm trying to solve is relatively simple, aimed at testing out Benders'. I've structured my code with a Main.cpp file where I define the problem and callbacks, and I have separate files for the CutClass and the SubProblem function. I've verified that these components are working correctly as they were initially designed for iterative Benders' decomposition, which I've successfully implemented.
Currently, my code compiles without errors, but it fails to solve the master problem as expected. Upon debugging, I've found that the feasibility cut in the first iteration is generated correctly, but the values of the master problem solution don't change as they should.
Here's the code of the Main.cpp file:
#include "Header.h"
#include <ilcplex/ilocplex.h>
ILOSTLBEGIN
// Declare these variables globally so they can be used in the callback
IloEnv env;
IloNumVar Eta;
IloIntVarArray MP_X;
vector<Cut> Cuts;
int* Xs;
// Callback function to generate Benders cuts
class MyBranchCallback : public IloCplex::LazyConstraintCallbackI {
public:
MyBranchCallback(IloEnv& env, int* Xs, vector<Cut>& Cuts) : IloCplex::LazyConstraintCallbackI(env), Xs(Xs), Cuts(Cuts) {}
[[nodiscard]] IloCplex::CallbackI* duplicateCallback() const override {
return new(env) MyBranchCallback{ *this };
}
// Implement the main() function
void main() override {
try {
// Save the current solution of the MP in the Xs vector
for (int i = 0; i < 3; i++) {
Xs[i] = getValue(MP_X[i]);
std::cout << "Xs[" << i << "]: " << Xs[i] << std::endl;
}
// Solve the subproblem with current Xs vector, store dual coefficients in the Cuts vector
double SP_obj = SubProblem(Xs, Cuts);
// Generate Benders cuts using the latest cut
if (Cuts[Cuts.size() - 1].add_optimality_cut) {
IloExpr exp(env);
exp += -3 * MP_X[0] - 2 * MP_X[1] + 4 * MP_X[2] + Cuts[Cuts.size() - 1].Us[0] * (-4 + 2 * MP_X[0] - 4 * MP_X[1] + MP_X[2]) + Cuts[Cuts.size() - 1].Us[1] * (2 + MP_X[0] + 2 * MP_X[1] - 4 * MP_X[2]);
add(exp <= Eta);
exp.end();
}
if (Cuts[Cuts.size() - 1].add_feasibility_cut) {
IloExpr exp2(env);
exp2 += Cuts[Cuts.size() - 1].Vs[0] * (-4 + 2 * MP_X[0] - 4 * MP_X[1] + MP_X[2]) + Cuts[Cuts.size() - 1].Vs[1] * (2 + MP_X[0] + 2 * MP_X[1] - 4 * MP_X[2]);
add(exp2 >= 0);
exp2.end();
}
}
catch (IloException& e) {
cerr << "IloException: " << e.getMessage() << endl;
}
catch (...) {
cerr << "Unknown exception caught" << endl;
}
}
private:
int* Xs;
vector<Cut>& Cuts;
};
int main() {
try {
// Initialize environment and CPLEX model
env = IloEnv();
IloModel ModelMP(env);
Eta = IloNumVar(env, -IloInfinity, IloInfinity, ILOFLOAT);
MP_X = IloIntVarArray(env, 3, 0, IloIntMax);
ModelMP.add(IloMaximize(env, Eta));
// Initialize Xs vector (with zeros)
Xs = new int[3];
for (int i = 0; i < 3; i++) {
Xs[i] = 1;
}
// Callback object with Xs and Cuts arguments
MyBranchCallback mycb(env, Xs, Cuts);
// Add constraints that only involve integer variables to the MP
for (int i = 0; i < MP_X.getSize(); i++) {
ModelMP.add(MP_X[i] <= 6);
}
// Create the IloCplex object and use the callback
IloCplex cplex(ModelMP);
cplex.setParam(IloCplex::Param::Preprocessing::Presolve, IloFalse);
cplex.use(&mycb);
// Solve the master problem
if (!cplex.solve()) {
cerr << "Failed to optimize the Master Problem!!!" << endl;
throw(-1);
}
double MP_obj = cplex.getObjValue();
// Free memory and finish
delete[] Xs;
env.end();
return 0;
}
catch (IloException& e) {
cerr << "IloException: " << e.getMessage() << endl;
env.end();
}
catch (...) {
cerr << "Unknown exception caught" << endl;
env.end();
}
}
Did I implement the callback class correctly?
If further code or information is necessary, I will gladly provide that.