callback
is called regularly during presolve.
callback
is called regularly during crossover from a barrier solution to a SIMPLEX basis.
callback
is called regularly during the network simplex.
callback
is called at each iteration during the barrier algorithm.
callback
is called at each iteration during the primal simplex algorithm.
callback
is called at each iteration during the dual simplex algorithm.
callback
is called at each node during the branch & cut search.
callback
is called regularly during probing.
callback
is called regularly during the separation for fractional cuts.
callback
is called regularly during the separation for disjunctive cuts.
IloCplex::CallbackI | +--- IloCplex::PresolveCallbackI | +--- IloCplex::CrossoverCallbackI | +--- IloCplex::NetworkCallbackI | +--- IloCplex::LPCallbackI | | | +--- IloCplex::BarrierCallbackI | | | +--- IloCplex::PrimalSimplexCallbackI | | | +--- IloCplex::DualSimplexCallbackI | +--- IloCplex::MIPCallbackI | +--- IloCplex::ProbingCallback | +--- IloCplex::FractionalCutCallbackI | +--- IloCplex::DisjunctiveCutCallbackI |
There are two ways of implementing callbacks for IloCplex
: a more complex way that exposes all the C++ implementation details, and a simplified way that uses macros to handle the C++ technicalities. We will first expose the more complex way and discuss the underlying design. To quickly implement your callback without details on the internal design, proceed directly to Writing Callbacks with Macros.
IloCplex::Callback MyCallback(IloEnv env, IloInt num) { return (new (env) MyCallbackI(num)); } |
virtual IloCplex::CallbackI* IloCplex::CallbackI::makeClone() const = 0;
ILODUALSIMPLEXCALLBACK1(MyCallback, IloInt, num) { if ( getNiterations() == num ) abort(); } |
To use this callback with an IloCplex
object cplex
, simply call:
IloCplex::Callback mycallback = cplex.use(MyCallback(env, 10));
|
|
defines the callback MyCallback
without parameters with the code enclosed in the outer {}
.
Here is how the macro ILOPRIMALSIMPLEXCALLBACK0
is expanded:
The 0
in the macro indicates that 0
parameters are passed to the constructor of the callback. For callbacks requiring up to 7 parameters similar macros are defined where the 0
is replaced by the number of parameters, ranging from 1 through 7. For an example of this using the cut callback, see Example: Controlling Cuts. If you need more than 7 parameters, you will need to derive your callback class yourself without the help of a macro.
After the callback MyCallback
is defined, it can be used with the line:
cplex.use(MyCallback(env));
|
The complete program, ilolpex4.cpp
, appears here or online in the standard distribution.
#include <ilcplex/ilocplex.h> ILOSTLBEGIN ILOPRIMALSIMPLEXCALLBACK0(MyCallback) { cout << "Iteration " << getNiterations() << ": "; if ( isFeasible() ) { cout << "Objective = " << getObjValue() << endl; } else { cout << "Infeasibility measure = " << getInfeasibility() << endl; } } static void populatebycolumn (IloModel model, IloNumVarArray var, IloRangeArray rng); int main (int argc, char **argv) { IloEnv env; try { IloModel model(env, "example"); IloNumVarArray var(env); IloRangeArray rng(env); populatebycolumn (model, var, rng); IloCplex cplex(model); cplex.setOut(env.getNullStream()); cplex.setRootAlgorithm(IloCplex::Primal); cplex.use(MyCallback(env)); cplex.solve(); cplex.out() << "Solution status = " << cplex.getStatus() << endl; cplex.out() << "Solution value = " << cplex.getObjValue() << endl; IloNumArray vals(env); cplex.getValues(vals, var); env.out() << "Values = " << vals << endl; cplex.getSlacks(vals, rng); env.out() << "Slacks = " << vals << endl; cplex.getDuals(vals, rng); env.out() << "Duals = " << vals << endl; cplex.getReducedCosts(vals, var); env.out() << "Reduced Costs = " << vals << endl; cplex.exportModel("lpex4.lp"); } catch (IloException& e) { cerr << "Concert exception caught: " << e << endl; } catch (...) { cerr << "Unknown exception caught" << endl; } env.end(); return 0; } // END main // To populate by column, we first create the rows, and then add the // columns. static void populatebycolumn (IloModel model, IloNumVarArray x, IloRangeArray c) { IloEnv env = model.getEnv(); IloObjective obj = IloMaximize(env); c.add(IloRange(env, -IloInfinity, 20.0)); c.add(IloRange(env, -IloInfinity, 30.0)); x.add(IloNumVar(obj(1.0) + c[0](-1.0) + c[1]( 1.0), 35.0, 40.0)); x.add(obj(2.0) + c[0]( 1.0) + c[1](-3.0)); x.add(obj(3.0) + c[0]( 1.0) + c[1]( 1.0)); model.add(obj); model.add(c); } // END populatebycolumn |
Implementing Callbacks in the Callable C Library
ILOG CPLEX optimization routines in the Callable Library incorporate a callback facility to allow your application to transfer control temporarily from ILOG CPLEX to the calling application. Using callbacks, your application can implement interrupt capability, for example, or create displays of optimization progress. Once control is transferred back to a function in the calling application, the calling application can retrieve specific information about the current optimization from the routine CPXgetcallbackinfo()
. Optionally, the calling application can then tell ILOG CPLEX to discontinue optimization.
Every user-defined callback must have these arguments:
env
, a pointer to the ILOG CPLEX environment;
cbdata
, a pointer to ILOG CPLEX internal data structures needed by CPXgetcallbackinfo()
;
wherefrom
, indicates which optimizer is calling the callback;
cbhandle
, a pointer supplied when your application calls CPXsetlpcallbackfunc()
or CPXsetmipcallbackfunc()
(so that the callback has access to private user data).
The arguments wherefrom
and cbhandle
should be used only in calls to CPXgetcallbackinfo()
.
For LP problems, if the callback returns a nonzero value, the solution process will terminate. If the process was not terminated during the presolve process, the status returned by the function IloCplex::getStatus
or the routines CPXsolution()
or CPXgetstat()
will be one of the values in Table 8.5.
For MIP problems, if the callback returns a nonzero value, the solution process will terminate and the status returned by IloCplex::getStatus()
or CPXgetstat()
will be one of the values in Table 8.6.
Value | Symbolic constant | Meaning |
---|---|---|
113 | CPXMIP_ABORT_FEAS | current solution integer feasible |
114 | CPXMIP_ABORT_INFEAS | no integer feasible solution found |
CPX_PARAM_SCRIND
) is not turned on. Only the callback function produces output. Consequently, this program calls CPXgeterrorstring()
to determine any error messages and then prints them. After the TERMINATE:
label, the program uses separate status variables so that if an error occurred earlier, its error status will not be lost or destroyed by freeing the problem object and closing the ILOG CPLEX environment. Table 8.7 summarizes those status variables.
Variable | Represents status returned by this routine |
---|---|
frstatus | |
clstatus |
mycallback()
at the end of the program is called by the optimizer. This function tests whether the primal simplex optimizer has been called. If so, then a call to CPXgetcallbackinfo()
gets the following information:
CPXlpopt()
, the default optimizer from the ILOG CPLEX Callable Library, it sets the callback function by calling CPXsetlpcallbackfunc()
. It unsets the callback immediately after optimization.
The complete program, lpex4.c
, appears here or online in the standard distribution.
#include <ilcplex/cplex.h> /* Bring in the declarations for the string functions */ #include <string.h> /* Include declaration for function at end of program */ #ifndef CPX_PROTOTYPE_MIN static int populatebycolumn (CPXENVptr env, CPXLPptr lp); static int CPXPUBLIC mycallback (CPXENVptr env, void *cbdata, int wherefrom, void *cbhandle); #else static int populatebycolumn (); static int CPXPUBLIC mycallback (); #endif /* The problem we are optimizing will have 2 rows, 3 columns and 6 nonzeros. */ #define NUMROWS 2 #define NUMCOLS 3 #define NUMNZ 6 #ifndef CPX_PROTOTYPE_MIN int main (void) #else int main () #endif { char probname[16]; /* Problem name is max 16 characters */ /* Declare and allocate space for the variables and arrays where we will store the optimization results including the status, objective value, variable values, dual values, row slacks and variable reduced costs. */ int solstat; double objval; double x[NUMCOLS]; double pi[NUMROWS]; double slack[NUMROWS]; double dj[NUMCOLS]; CPXENVptr env = NULL; CPXLPptr lp = NULL; int status; int i, j; int cur_numrows, cur_numcols; /* Initialize the CPLEX environment */ env = CPXopenCPLEX (&status); /* If an error occurs, the status value indicates the reason for failure. The error message will be printed at the end of the program. */ if ( env == NULL ) { fprintf (stderr, "Could not open CPLEX environment.\n"); goto TERMINATE; } /* Turn *off* output to the screen since we'll be producing it via the callback function. This also means we won't see any CPLEX generated errors, but we'll handle that at the end of the program. */ status = CPXsetintparam (env, CPX_PARAM_SCRIND, CPX_OFF); if ( status ) { fprintf (stderr, "Failure to turn off screen indicator, error %d.\n", status); goto TERMINATE; } /* Create the problem. */ strcpy (probname, "example"); lp = CPXcreateprob (env, &status, probname); /* A returned pointer of NULL may mean that not enough memory was available or there was some other problem. In the case of failure, an error message will have been written to the error channel from inside CPLEX. In this example, we wouldn't see an error message from CPXcreateprob since we turned off the CPX_PARAM_SCRIND parameter above. The only way to see this message would be to use the CPLEX message handler, but that clutters up the simplicity of this example, which has a point of illustrating the CPLEX callback functionality. */ if ( lp == NULL ) { fprintf (stderr, "Failed to create LP.\n"); goto TERMINATE; } /* Now populate the problem with the data. */ status = populatebycolumn (env, lp); if ( status ) { fprintf (stderr, "Failed to populate problem data.\n"); goto TERMINATE; } status = CPXsetlpcallbackfunc (env, mycallback, NULL); if ( status ) { fprintf (stderr, "Failed to set callback function.\n"); goto TERMINATE; } /* Optimize the problem and obtain solution. */ status = CPXsetintparam (env, CPX_PARAM_LPMETHOD, CPX_ALG_PRIMAL); if ( status ) { fprintf (stderr, "Failed to set the optimization method, error %d.\n", status); goto TERMINATE; } status = CPXlpopt (env, lp); if ( status ) { fprintf (stderr, "Failed to optimize LP.\n"); goto TERMINATE; } /* Turn off the callback function. This isn't strictly necessary, but is good practice. Note that the cast in front of NULL is only necessary for some compilers. */ #ifndef CPX_PROTOTYPE_MIN status = CPXsetlpcallbackfunc (env, (int (CPXPUBLIC *)(CPXENVptr, void *, int, void *)) NULL, NULL); #else status = CPXsetlpcallbackfunc (env, (int (CPXPUBLIC *)()) NULL, NULL); #endif if ( status ) { fprintf (stderr, "Failed to turn off callback function.\n"); goto TERMINATE; } status = CPXsolution (env, lp, &solstat, &objval, x, pi, slack, dj); if ( status ) { fprintf (stderr, "Failed to obtain solution.\n"); goto TERMINATE; } /* Write the output to the screen. */ printf ("\nSolution status = %d\n", solstat); printf ("Solution value = %f\n\n", objval); /* The size of the problem should be obtained by asking CPLEX what the actual size is, rather than using sizes from when the problem was built. cur_numrows and cur_numcols store the current number of rows and columns, respectively. */ cur_numrows = CPXgetnumrows (env, lp); cur_numcols = CPXgetnumcols (env, lp); for (i = 0; i < cur_numrows; i++) { printf ("Row %d: Slack = %10f Pi = %10f\n", i, slack[i], pi[i]); } for (j = 0; j < cur_numcols; j++) { printf ("Column %d: Value = %10f Reduced cost = %10f\n", j, x[j], dj[j]); } /* Finally, write a copy of the problem to a file. */ status = CPXwriteprob (env, lp, "lpex4.lp", NULL); if ( status ) { fprintf (stderr, "Failed to write LP to disk.\n"); goto TERMINATE; } TERMINATE: /* Free up the problem as allocated by CPXcreateprob, if necessary */ if ( lp != NULL ) { int frstatus; frstatus = CPXfreeprob (env, &lp); if ( frstatus ) { fprintf (stderr, "CPXfreeprob failed, error code %d.\n", frstatus); if (( !status ) && frstatus ) status = frstatus; } } /* Free up the CPLEX environment, if necessary */ if ( env != NULL ) { int clstatus; clstatus = CPXcloseCPLEX (&env); if ( clstatus ) { fprintf (stderr, "CPXcloseCPLEX failed, error code %d.\n", clstatus); if (( !status ) && clstatus ) status = clstatus; } } if ( status ) { char errmsg[1024]; /* Note that since we have turned off the CPLEX screen indicator, we'll need to print the error message ourselves. */ CPXgeterrorstring (env, status, errmsg); fprintf (stderr, "%s", errmsg); } return (status); } /* END main */ /* This function builds by column the linear program: Maximize obj: x1 + 2 x2 + 3 x3 Subject To c1: - x1 + x2 + x3 <= 20 c2: x1 - 3 x2 + x3 <= 30 Bounds 35 <= x1 <= 40 End */ #ifndef CPX_PROTOTYPE_MIN static int populatebycolumn (CPXENVptr env, CPXLPptr lp) #else static int populatebycolumn (env, lp) CPXENVptr env; CPXLPptr lp; #endif { int status = 0; double obj[NUMCOLS]; double lb[NUMCOLS]; double ub[NUMCOLS]; char *colname[NUMCOLS]; int matbeg[NUMCOLS]; int matind[NUMNZ]; double matval[NUMNZ]; double rhs[NUMROWS]; char sense[NUMROWS]; char *rowname[NUMROWS]; /* To build the problem by column, create the rows, and then add the columns. */ CPXchgobjsen (env, lp, CPX_MAX); /* Problem is maximization */ /* Now create the new rows. First, populate the arrays. */ rowname[0] = "c1"; sense[0] = `L'; rhs[0] = 20.0; rowname[1] = "c2"; sense[1] = `L'; rhs[1] = 30.0; status = CPXnewrows (env, lp, NUMROWS, rhs, sense, NULL, rowname); if ( status ) goto TERMINATE; /* Now add the new columns. First, populate the arrays. */ obj[0] = 1.0; obj[1] = 2.0; obj[2] = 3.0; matbeg[0] = 0; matbeg[1] = 2; matbeg[2] = 4; matind[0] = 0; matind[2] = 0; matind[4] = 0; matval[0] = -1.0; matval[2] = 1.0; matval[4] = 1.0; matind[1] = 1; matind[3] = 1; matind[5] = 1; matval[1] = 1.0; matval[3] = -3.0; matval[5] = 1.0; lb[0] = 35.0; lb[1] = 0.0; lb[2] = 0.0; ub[0] = 40.0; ub[1] = CPX_INFBOUND; ub[2] = CPX_INFBOUND; colname[0] = "x1"; colname[1] = "x2"; colname[2] = "x3"; status = CPXaddcols (env, lp, NUMCOLS, NUMNZ, obj, matbeg, matind, matval, lb, ub, colname); if ( status ) goto TERMINATE; TERMINATE: return (status); } /* END populatebycolumn */ /* The callback function will print out the Phase of the simplex method, the sum of infeasibilities if in Phase 1, or the objective if in Phase 2. If any of our requests fails, we'll return an indication to abort. */ #ifndef CPX_PROTOTYPE_MIN static int CPXPUBLIC mycallback (CPXENVptr env, void *cbdata, int wherefrom, void *cbhandle) #else static int CPXPUBLIC mycallback (env, cbdata, wherefrom, cbhandle) CPXENVptr env; void *cbdata; int wherefrom; void *cbhandle; #endif { int status = 0; int phase = -1; double suminf_or_objective; int itcnt = -1; if ( wherefrom == CPX_CALLBACK_PRIMAL ) { status = CPXgetcallbackinfo (env, cbdata, wherefrom, CPX_CALLBACK_INFO_ITCOUNT, &itcnt); if ( status ) goto TERMINATE; status = CPXgetcallbackinfo (env, cbdata, wherefrom, CPX_CALLBACK_INFO_PRIMAL_FEAS, &phase); if ( status ) goto TERMINATE; if ( phase == 0 ) { status = CPXgetcallbackinfo (env, cbdata, wherefrom, CPX_CALLBACK_INFO_PRIMAL_INFMEAS, &suminf_or_objective); if ( status ) goto TERMINATE; printf ("Iteration %d: Infeasibility measure = %f\n", itcnt, suminf_or_objective); } else { status = CPXgetcallbackinfo (env, cbdata, wherefrom, CPX_CALLBACK_INFO_PRIMAL_OBJ, &suminf_or_objective); if ( status ) goto TERMINATE; printf ("Iteration %d: Objective = %f\n", itcnt, suminf_or_objective); } } TERMINATE: return (status); } /* END mycallback */ |