callback_c.c#

/* 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 <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "gurobi_c.h"

/* Define structure to pass my data to the callback function */

struct callback_data {
  double  lastiter;
  double  lastnode;
  double *solution;
  FILE   *logfile;
};

/* Define my callback function */

int __stdcall
mycallback(GRBmodel *model,
           void     *cbdata,
           int       where,
           void     *usrdata)
{
  struct callback_data *mydata = (struct callback_data *) usrdata;

  if (where == GRB_CB_POLLING) {
    /* Ignore polling callback */
  } else if (where == GRB_CB_PRESOLVE) {
    /* Presolve callback */
    int cdels, rdels;
    GRBcbget(cbdata, where, GRB_CB_PRE_COLDEL, &cdels);
    GRBcbget(cbdata, where, GRB_CB_PRE_ROWDEL, &rdels);
    if (cdels || rdels) {
      printf("%7d columns and %7d rows are removed\n", cdels, rdels);
    }
  } else if (where == GRB_CB_SIMPLEX) {
    /* Simplex callback */
    double itcnt, obj, pinf, dinf;
    int    ispert;
    char   ch;
    GRBcbget(cbdata, where, GRB_CB_SPX_ITRCNT, &itcnt);
    if (itcnt - mydata->lastiter >= 100) {
      mydata->lastiter = itcnt;
      GRBcbget(cbdata, where, GRB_CB_SPX_OBJVAL, &obj);
      GRBcbget(cbdata, where, GRB_CB_SPX_ISPERT, &ispert);
      GRBcbget(cbdata, where, GRB_CB_SPX_PRIMINF, &pinf);
      GRBcbget(cbdata, where, GRB_CB_SPX_DUALINF, &dinf);
      if      (ispert == 0) ch = ' ';
      else if (ispert == 1) ch = 'S';
      else                  ch = 'P';
      printf("%7.0f %14.7e%c %13.6e %13.6e\n", itcnt, obj, ch, pinf, dinf);
    }
  } else if (where == GRB_CB_MIP) {
    /* General MIP callback */
    double nodecnt, objbst, objbnd, actnodes, itcnt;
    int    solcnt, cutcnt;
    GRBcbget(cbdata, where, GRB_CB_MIP_NODCNT, &nodecnt);
    GRBcbget(cbdata, where, GRB_CB_MIP_OBJBST, &objbst);
    GRBcbget(cbdata, where, GRB_CB_MIP_OBJBND, &objbnd);
    GRBcbget(cbdata, where, GRB_CB_MIP_SOLCNT, &solcnt);
    if (nodecnt - mydata->lastnode >= 100) {
      mydata->lastnode = nodecnt;
      GRBcbget(cbdata, where, GRB_CB_MIP_NODLFT, &actnodes);
      GRBcbget(cbdata, where, GRB_CB_MIP_ITRCNT, &itcnt);
      GRBcbget(cbdata, where, GRB_CB_MIP_CUTCNT, &cutcnt);
      printf("%7.0f %7.0f %8.0f %13.6e %13.6e %7d %7d\n",
             nodecnt, actnodes, itcnt, objbst, objbnd, solcnt, cutcnt);
    }
    if (fabs(objbst - objbnd) < 0.1 * (1.0 + fabs(objbst))) {
      printf("Stop early - 10%% gap achieved\n");
      GRBterminate(model);
    }
    if (nodecnt >= 10000 && solcnt) {
      printf("Stop early - 10000 nodes explored\n");
      GRBterminate(model);
    }
  } else if (where == GRB_CB_MIPSOL) {
    /* MIP solution callback */
    double nodecnt, obj;
    int    solcnt;
    GRBcbget(cbdata, where, GRB_CB_MIPSOL_NODCNT, &nodecnt);
    GRBcbget(cbdata, where, GRB_CB_MIPSOL_OBJ, &obj);
    GRBcbget(cbdata, where, GRB_CB_MIPSOL_SOLCNT, &solcnt);
    GRBcbget(cbdata, where, GRB_CB_MIPSOL_SOL, mydata->solution);
    printf("**** New solution at node %.0f, obj %g, sol %d, x[0] = %.2f ****\n",
           nodecnt, obj, solcnt, mydata->solution[0]);
  } else if (where == GRB_CB_MIPNODE) {
    int status;
    /* MIP node callback */
    printf("**** New node ****\n");
    GRBcbget(cbdata, where, GRB_CB_MIPNODE_STATUS, &status);
    if (status == GRB_OPTIMAL) {
      GRBcbget(cbdata, where, GRB_CB_MIPNODE_REL, mydata->solution);
      GRBcbsolution(cbdata, mydata->solution, NULL);
    }
  } else if (where == GRB_CB_BARRIER) {
    /* Barrier callback */
    int    itcnt;
    double primobj, dualobj, priminf, dualinf, compl;
    GRBcbget(cbdata, where, GRB_CB_BARRIER_ITRCNT, &itcnt);
    GRBcbget(cbdata, where, GRB_CB_BARRIER_PRIMOBJ, &primobj);
    GRBcbget(cbdata, where, GRB_CB_BARRIER_DUALOBJ, &dualobj);
    GRBcbget(cbdata, where, GRB_CB_BARRIER_PRIMINF, &priminf);
    GRBcbget(cbdata, where, GRB_CB_BARRIER_DUALINF, &dualinf);
    GRBcbget(cbdata, where, GRB_CB_BARRIER_COMPL, &compl);
    printf("%d %.4e %.4e %.4e %.4e %.4e\n",
           itcnt, primobj, dualobj, priminf, dualinf, compl);
  } else if (where == GRB_CB_IIS) {
    int constrmin, constrmax, constrguess, boundmin, boundmax, boundguess;
    GRBcbget(cbdata, where, GRB_CB_IIS_CONSTRMIN, &constrmin);
    GRBcbget(cbdata, where, GRB_CB_IIS_CONSTRMAX, &constrmax);
    GRBcbget(cbdata, where, GRB_CB_IIS_CONSTRGUESS, &constrguess);
    GRBcbget(cbdata, where, GRB_CB_IIS_BOUNDMIN, &boundmin);
    GRBcbget(cbdata, where, GRB_CB_IIS_BOUNDMAX, &boundmax);
    GRBcbget(cbdata, where, GRB_CB_IIS_BOUNDGUESS, &boundguess);
    printf("IIS: %d,%d,%d %d,%d,%d\n",
           constrmin, constrmax, constrguess,
           boundmin, boundmax, boundguess);
  } else if (where == GRB_CB_MESSAGE) {
    /* Message callback */
    char *msg;
    GRBcbget(cbdata, where, GRB_CB_MSG_STRING, &msg);
    fprintf(mydata->logfile, "%s", msg);
  }
  return 0;
}

int
main(int   argc,
     char *argv[])
{
  GRBenv   *env   = NULL;
  GRBmodel *model = NULL;
  int       error = 0;
  int       numvars, solcount, optimstatus, j;
  double    objval, x;
  char     *varname;
  struct callback_data mydata;

  mydata.lastiter = -GRB_INFINITY;
  mydata.lastnode = -GRB_INFINITY;
  mydata.solution = NULL;
  mydata.logfile  = NULL;

  if (argc < 2) {
    fprintf(stderr, "Usage: callback_c filename\n");
    goto QUIT;
  }

  /* Open log file */
  mydata.logfile = fopen("cb.log", "w");
  if (!mydata.logfile) {
    fprintf(stderr, "Cannot open cb.log for callback message\n");
    goto QUIT;
  }

  /* Create environment */

  error = GRBloadenv(&env, NULL);
  if (error) goto QUIT;

  /* Turn off display and heuristics */

  error = GRBsetintparam(env, GRB_INT_PAR_OUTPUTFLAG, 0);
  if (error) goto QUIT;

  error = GRBsetdblparam(env, GRB_DBL_PAR_HEURISTICS, 0.0);
  if (error) goto QUIT;

  /* Read model from file */

  error = GRBreadmodel(env, argv[1], &model);
  if (error) goto QUIT;

  /* Allocate space for solution */

  error = GRBgetintattr(model, GRB_INT_ATTR_NUMVARS, &numvars);
  if (error) goto QUIT;

  mydata.solution = malloc(numvars*sizeof(double));
  if (mydata.solution == NULL) {
    fprintf(stderr, "Failed to allocate memory\n");
    exit(1);
  }

  /* Set callback function */

  error = GRBsetcallbackfunc(model, mycallback, (void *) &mydata);
  if (error) goto QUIT;

  /* Solve model */

  error = GRBoptimize(model);
  if (error) goto QUIT;

  /* Capture solution information */

  printf("\nOptimization complete\n");

  error = GRBgetintattr(model, GRB_INT_ATTR_SOLCOUNT, &solcount);
  if (error) goto QUIT;

  error = GRBgetintattr(model, GRB_INT_ATTR_STATUS, &optimstatus);
  if (error) goto QUIT;

  if (solcount == 0) {
    printf("No solution found, optimization status = %d\n", optimstatus);
    goto QUIT;
  }

  error = GRBgetdblattr(model, GRB_DBL_ATTR_OBJVAL, &objval);
  if (error) goto QUIT;

  printf("Solution found, objective = %.4e\n", objval);

  for ( j = 0; j < numvars; ++j ) {
    error = GRBgetstrattrelement(model, GRB_STR_ATTR_VARNAME, j, &varname);
    if (error) goto QUIT;
    error = GRBgetdblattrelement(model, GRB_DBL_ATTR_X, j, &x);
    if (error) goto QUIT;
    if (x != 0.0) {
      printf("%s %f\n", varname, x);
    }
  }

QUIT:

  /* Error reporting */

  if (error) {
    printf("ERROR: %s\n", GRBgeterrormsg(env));
    exit(1);
  }

  /* Close log file */

  if (mydata.logfile)
    fclose(mydata.logfile);

  /* Free solution */

  if (mydata.solution)
    free(mydata.solution);

  /* Free model */

  GRBfreemodel(model);

  /* Free environment */

  GRBfreeenv(env);

  return 0;
}