callback_c++.cpp#

/* Copyright 2024, Gurobi Optimization, LLC */

/*
   This example reads a model from a file, sets up a callback that
   monitors optimization progress and implements a custom
   termination strategy, and outputs progress information to the
   screen and to a log file.

   The termination strategy implemented in this callback stops the
   optimization of a MIP model once at least one of the following two
   conditions have been satisfied:
     1) The optimality gap is less than 10%
     2) At least 10000 nodes have been explored, and an integer feasible
        solution has been found.
   Note that termination is normally handled through Gurobi parameters
   (MIPGap, NodeLimit, etc.).  You should only use a callback for
   termination if the available parameters don't capture your desired
   termination criterion.
*/

#include "gurobi_c++.h"
#include <fstream>
#include <cmath>
using namespace std;

class mycallback: public GRBCallback
{
  public:
    double lastiter;
    double lastnode;
    int numvars;
    GRBVar* vars;
    ofstream* logfile;
    mycallback(int xnumvars, GRBVar* xvars, ofstream* xlogfile) {
      lastiter = lastnode = -GRB_INFINITY;
      numvars = xnumvars;
      vars = xvars;
      logfile = xlogfile;
    }
  protected:
    void callback () {
      try {
        if (where == GRB_CB_POLLING) {
          // Ignore polling callback
        } else if (where == GRB_CB_PRESOLVE) {
          // Presolve callback
          int cdels = getIntInfo(GRB_CB_PRE_COLDEL);
          int rdels = getIntInfo(GRB_CB_PRE_ROWDEL);
          if (cdels || rdels) {
            cout << cdels << " columns and " << rdels
                 << " rows are removed" << endl;
          }
        } else if (where == GRB_CB_SIMPLEX) {
          // Simplex callback
          double itcnt = getDoubleInfo(GRB_CB_SPX_ITRCNT);
          if (itcnt - lastiter >= 100) {
            lastiter = itcnt;
            double obj = getDoubleInfo(GRB_CB_SPX_OBJVAL);
            int ispert = getIntInfo(GRB_CB_SPX_ISPERT);
            double pinf = getDoubleInfo(GRB_CB_SPX_PRIMINF);
            double dinf = getDoubleInfo(GRB_CB_SPX_DUALINF);
            char ch;
            if (ispert == 0)      ch = ' ';
            else if (ispert == 1) ch = 'S';
            else                  ch = 'P';
            cout << itcnt << " " << obj << ch << " "
                 << pinf << " " << dinf << endl;
          }
        } else if (where == GRB_CB_MIP) {
          // General MIP callback
          double nodecnt = getDoubleInfo(GRB_CB_MIP_NODCNT);
          double objbst = getDoubleInfo(GRB_CB_MIP_OBJBST);
          double objbnd = getDoubleInfo(GRB_CB_MIP_OBJBND);
          int solcnt = getIntInfo(GRB_CB_MIP_SOLCNT);
          if (nodecnt - lastnode >= 100) {
            lastnode = nodecnt;
            int actnodes = (int) getDoubleInfo(GRB_CB_MIP_NODLFT);
            int itcnt = (int) getDoubleInfo(GRB_CB_MIP_ITRCNT);
            int cutcnt = getIntInfo(GRB_CB_MIP_CUTCNT);
            cout << nodecnt << " " << actnodes << " " << itcnt
                 << " " << objbst << " " << objbnd << " "
                 << solcnt << " " << cutcnt << endl;
          }
          if (fabs(objbst - objbnd) < 0.1 * (1.0 + fabs(objbst))) {
            cout << "Stop early - 10% gap achieved" << endl;
            abort();
          }
          if (nodecnt >= 10000 && solcnt) {
            cout << "Stop early - 10000 nodes explored" << endl;
            abort();
          }
        } else if (where == GRB_CB_MIPSOL) {
          // MIP solution callback
          int nodecnt = (int) getDoubleInfo(GRB_CB_MIPSOL_NODCNT);
          double obj = getDoubleInfo(GRB_CB_MIPSOL_OBJ);
          int solcnt = getIntInfo(GRB_CB_MIPSOL_SOLCNT);
          double* x = getSolution(vars, numvars);
          cout << "**** New solution at node " << nodecnt
               << ", obj " << obj << ", sol " << solcnt
               << ", x[0] = " << x[0] << " ****" << endl;
          delete[] x;
        } else if (where == GRB_CB_MIPNODE) {
          // MIP node callback
          cout << "**** New node ****" << endl;
          if (getIntInfo(GRB_CB_MIPNODE_STATUS) == GRB_OPTIMAL) {
            double* x = getNodeRel(vars, numvars);
            setSolution(vars, x, numvars);
            delete[] x;
          }
        } else if (where == GRB_CB_BARRIER) {
          // Barrier callback
          int itcnt = getIntInfo(GRB_CB_BARRIER_ITRCNT);
          double primobj = getDoubleInfo(GRB_CB_BARRIER_PRIMOBJ);
          double dualobj = getDoubleInfo(GRB_CB_BARRIER_DUALOBJ);
          double priminf = getDoubleInfo(GRB_CB_BARRIER_PRIMINF);
          double dualinf = getDoubleInfo(GRB_CB_BARRIER_DUALINF);
          double cmpl = getDoubleInfo(GRB_CB_BARRIER_COMPL);
          cout << itcnt << " " << primobj << " " << dualobj << " "
               << priminf << " " << dualinf << " " << cmpl << endl;
        } else if (where == GRB_CB_MESSAGE) {
          // Message callback
          string msg = getStringInfo(GRB_CB_MSG_STRING);
          *logfile << msg;
        }
      } catch (GRBException e) {
        cout << "Error number: " << e.getErrorCode() << endl;
        cout << e.getMessage() << endl;
      } catch (...) {
        cout << "Error during callback" << endl;
      }
    }
};

int
main(int   argc,
     char *argv[])
{
  if (argc < 2) {
    cout << "Usage: callback_c++ filename" << endl;
    return 1;
  }

  // Open log file
  ofstream logfile("cb.log");
  if (!logfile.is_open()) {
    cout << "Cannot open cb.log for callback message" << endl;
    return 1;
  }

  GRBEnv *env = 0;
  GRBVar *vars = 0;

  try {
    // Create environment
    env = new GRBEnv();

    // Read model from file
    GRBModel model = GRBModel(*env, argv[1]);

    // Turn off display and heuristics
    model.set(GRB_IntParam_OutputFlag, 0);
    model.set(GRB_DoubleParam_Heuristics, 0.0);

    // Create a callback object and associate it with the model
    int numvars = model.get(GRB_IntAttr_NumVars);
    vars = model.getVars();
    mycallback cb = mycallback(numvars, vars, &logfile);

    model.setCallback(&cb);

    // Solve model and capture solution information
    model.optimize();

    cout << endl << "Optimization complete" << endl;
    if (model.get(GRB_IntAttr_SolCount) == 0) {
      cout << "No solution found, optimization status = "
           << model.get(GRB_IntAttr_Status) << endl;
    } else {
      cout << "Solution found, objective = "
           << model.get(GRB_DoubleAttr_ObjVal) << endl;
      for (int j = 0; j < numvars; j++) {
        GRBVar v = vars[j];
        double x = v.get(GRB_DoubleAttr_X);
        if (x != 0.0) {
          cout << v.get(GRB_StringAttr_VarName) << " " << x << endl;
        }
      }
    }

  } catch (GRBException e) {
    cout << "Error number: " << e.getErrorCode() << endl;
    cout << e.getMessage() << endl;
  } catch (...) {
    cout << "Error during optimization" << endl;
  }

  // Close log file
  logfile.close();

  delete[] vars;
  delete env;

  return 0;
}