#!/usr/bin/env python3.11# Copyright 2025, 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.importsysfromfunctoolsimportpartialimportgurobipyasgpfromgurobipyimportGRBclassCallbackData:def__init__(self,modelvars):self.modelvars=modelvarsself.lastiter=-GRB.INFINITYself.lastnode=-GRB.INFINITYdefmycallback(model,where,*,cbdata,logfile):""" Callback function. 'model' and 'where' arguments are passed by gurobipy when the callback is invoked. The other arguments must be provided via functools.partial: 1) 'cbdata' is an instance of CallbackData, which holds the model variables and tracks state information across calls to the callback. 2) 'logfile' is a writeable file handle. """ifwhere==GRB.Callback.POLLING:# Ignore polling callbackpasselifwhere==GRB.Callback.PRESOLVE:# Presolve callbackcdels=model.cbGet(GRB.Callback.PRE_COLDEL)rdels=model.cbGet(GRB.Callback.PRE_ROWDEL)ifcdelsorrdels:print(f"{cdels} columns and {rdels} rows are removed")elifwhere==GRB.Callback.SIMPLEX:# Simplex callbackitcnt=model.cbGet(GRB.Callback.SPX_ITRCNT)ifitcnt-cbdata.lastiter>=100:cbdata.lastiter=itcntobj=model.cbGet(GRB.Callback.SPX_OBJVAL)ispert=model.cbGet(GRB.Callback.SPX_ISPERT)pinf=model.cbGet(GRB.Callback.SPX_PRIMINF)dinf=model.cbGet(GRB.Callback.SPX_DUALINF)ifispert==0:ch=" "elifispert==1:ch="S"else:ch="P"print(f"{int(itcnt)}{obj:g}{ch}{pinf:g}{dinf:g}")elifwhere==GRB.Callback.MIP:# General MIP callbacknodecnt=model.cbGet(GRB.Callback.MIP_NODCNT)objbst=model.cbGet(GRB.Callback.MIP_OBJBST)objbnd=model.cbGet(GRB.Callback.MIP_OBJBND)solcnt=model.cbGet(GRB.Callback.MIP_SOLCNT)ifnodecnt-cbdata.lastnode>=100:cbdata.lastnode=nodecntactnodes=model.cbGet(GRB.Callback.MIP_NODLFT)itcnt=model.cbGet(GRB.Callback.MIP_ITRCNT)cutcnt=model.cbGet(GRB.Callback.MIP_CUTCNT)print(f"{nodecnt:.0f}{actnodes:.0f}{itcnt:.0f}{objbst:g} "f"{objbnd:g}{solcnt}{cutcnt}")ifabs(objbst-objbnd)<0.1*(1.0+abs(objbst)):print("Stop early - 10% gap achieved")model.terminate()ifnodecnt>=10000andsolcnt:print("Stop early - 10000 nodes explored")model.terminate()elifwhere==GRB.Callback.MIPSOL:# MIP solution callbacknodecnt=model.cbGet(GRB.Callback.MIPSOL_NODCNT)obj=model.cbGet(GRB.Callback.MIPSOL_OBJ)solcnt=model.cbGet(GRB.Callback.MIPSOL_SOLCNT)x=model.cbGetSolution(cbdata.modelvars)print(f"**** New solution at node {nodecnt:.0f}, obj {obj:g}, "f"sol {solcnt:.0f}, x[0] = {x[0]:g} ****")elifwhere==GRB.Callback.MIPNODE:# MIP node callbackprint("**** New node ****")ifmodel.cbGet(GRB.Callback.MIPNODE_STATUS)==GRB.OPTIMAL:x=model.cbGetNodeRel(cbdata.modelvars)model.cbSetSolution(cbdata.modelvars,x)elifwhere==GRB.Callback.BARRIER:# Barrier callbackitcnt=model.cbGet(GRB.Callback.BARRIER_ITRCNT)primobj=model.cbGet(GRB.Callback.BARRIER_PRIMOBJ)dualobj=model.cbGet(GRB.Callback.BARRIER_DUALOBJ)priminf=model.cbGet(GRB.Callback.BARRIER_PRIMINF)dualinf=model.cbGet(GRB.Callback.BARRIER_DUALINF)cmpl=model.cbGet(GRB.Callback.BARRIER_COMPL)print(f"{itcnt:.0f}{primobj:g}{dualobj:g}{priminf:g}{dualinf:g}{cmpl:g}")elifwhere==GRB.Callback.MESSAGE:# Message callbackmsg=model.cbGet(GRB.Callback.MSG_STRING)logfile.write(msg)# Parse argumentsiflen(sys.argv)<2:print("Usage: callback.py filename")sys.exit(0)model_file=sys.argv[1]# This context block manages several resources to ensure they are properly# closed at the end of the program:# 1) A Gurobi environment, with console output and heuristics disabled.# 2) A Gurobi model, read from a file provided by the user.# 3) A Python file handle which the callback will write to.withgp.Env(params={"OutputFlag":0,"Heuristics":0})asenv,gp.read(model_file,env=env)asmodel,open("cb.log","w")aslogfile:# Set up callback function with required argumentscallback_data=CallbackData(model.getVars())callback_func=partial(mycallback,cbdata=callback_data,logfile=logfile)# Solve model and print solution informationmodel.optimize(callback_func)print("")print("Optimization complete")ifmodel.SolCount==0:print(f"No solution found, optimization status = {model.Status}")else:print(f"Solution found, objective = {model.ObjVal:g}")forvinmodel.getVars():ifv.X!=0.0:print(f"{v.VarName}{v.X:g}")