Callbacks

COPT provides the callbacks utility, which supports users in obtaining information during the MIP solving process, e.g., the current best bound, the current optimal objective value, etc.; or controlling the solving process, e.g., by adding lazy constraints and cutting planes, or terminating the solving process. The problem types supporting the use of callbacks are MILP, MISOCP, MIQ(C)P.

A callback function is a user-provided function called by COPT during the solving process. The user can register one custom callback function via their preferred API for one or multiple callback contexts. Section Using the callback utilities in different APIs gives a detailed introduction to how to setup a callback function. The callback function will be invoked at certain moments during the solving process, depending on the callback contexts. When invoked, the user can access information and control the solving process, respectively. The available information and operations depend on the context. Currently, COPT supports four callback contexts:

  • CBCONTEXT_INCUMBENT: Invokes the callback after a new incumbent was found.

  • CBCONTEXT_MIPNODE: Invokes the callback after a MIP node was processed.

  • CBCONTEXT_MIPRELAX : Invokes the callback when an LP-relaxation was solved.

  • CBCONTEXT_MIPSOL: Invokes the callback when a new MIP candidate solution is found.

The content of this chapter is organized as follows:

Notes

Only one callback function can be registered in COPT at a time. But one callback can be registered for multiple contexts. If a user wants to call different operations for different contexts (such as adding lazy constraints under CBCONTEXT_MIPSOL and adding user cuts under CBCONTEXT_MIPRELAX ), they need to register one callback for all relevant contexts and have this callback call the respective operations based on the context in which it was called.

Obtaining information during the solving process

The information that can be obtained during the MIP solving process depends on the context the callback is invoked in, see the table below. Information is usually obtained by calling (an API dependent version of) getInfo / GetCallbackInfo from within the callback function, specifying the desired information via a string supplied as the function argument. For a detailed description of the available callback information arguments, please refer to Callback information.

The following table lists information that can be obtained in different contexts:

Context

Callback Information

CBCONTEXT_MIPNODE

NodeStatus, RelaxSolution, RelaxSolObj, MipCandObj, MipCandidate

CBCONTEXT_MIPRELAX

RelaxSolution, RelaxSolObj

CBCONTEXT_MIPSOL

MipCandObj, MipCandidate

CBCONTEXT_INCUMBENT

In addition to the corresponding Callback Context and Information listed above, BestObj, BestBnd, HasIncumbent, and Incumbent can be obtained in any context.

Notes

  1. If HasIncumbent == False, then Incumbent cannot be obtained.

  2. The return value of the “NodeStatus” information is constant, representing the solving status of the current node’s LP relaxation. For possible values, please refer to General Constants Section: Solution Status (Partial).

  3. Incumbent, RelaxSolution , and MipCandidate are obtained through different methods in different interfaces:

    • C API: through the function COPT_GetCallbackInfo, the name of the intermediate information to be obtained is provided as arguments of the function;

    • In object-oriented programming languages (C++/C#/Java/Python), the CallbackBase class provides specialized functions to obtain the corresponding intermediate information. E.g., in Python/C++ CallbackBase provides GetIncumbent, GetRelaxSol, and GetSolution. Other programming language interfaces are similar, please refer to the CallbackBase class of each API.

Controlling the MIP solving process

COPT provides functions to allow the user to interactively add lazy constraints or cutting planes during the solving process of the MIP branch-and-cut to control the MIP solving process. There are three main types of operations:

  1. Adding lazy constraints

  2. Adding cutting planes

  3. Adding feasible solutions

Adding lazy constraints

Lazy constraints are constraints that are added to the model only when they are violated. For some models with a large number of constraints, adding lazy constraints only when violated can effectively reduce the size of the model during the solution process and improve the efficiency of the solving process. A popular example of this is the TSP model, see "cb_ex1" in the examples directory in the installation package.

COPT supports two ways of adding lazy constraints. One is to explicitly add lazy constraints to the model before starting the solution process. The other is to add lazy constraints during the solving process through a user callback. For this purpose, each API provides two sets of methods, one for adding lazy constraints to the initial model and one for adding them from a callback. In the C API, the methods can be distinguished according to whether or not the function name contains "Callback", e.g., COPT_AddLazyConstr and COPT_AddCallbackLazyConstr. In object-oriented APIs, the two sets of functions correspond to the Model class and the CallbackBase class respectively. Taking python as an example:

  • Before solving, a user can directly add lazy constraints to the model by calling Model.addLazyConstr() or Model.addLazyConstrs() respectively.

  • During the solving process, a user can dynamically add lazy constraints (if supported by the current context, see below) from within the callback function via CallbackBase.addLazyConstr() or CallbackBase.addLazyConstrs().

In both cases, the added lazy constraints will be stored by COPT separately from the actual model and are only added to the model, when they are violated by a solution found during the solving process.

In order to ensure correctness, COPT will check whether any lazy constraints added so far are violated by any solution found during the solving process. This will increase the solving time, especially when many non-violated lazy constraints have been added. It is recommended that the user only adds lazy constraints when necessary, e.g. when they are violated by a solution.

Lazy constraints can only be added in the callback contexts CBCONTEXT_MIPSOL and CBCONTEXT_MIPRELAX. While is is not strictly necessary to check every LP relaxation solution for violated lazy constraints, a user has to check every solution provided in CBCONTEXT_MIPSOL for feasibility against the lazy constraints in order to not produce wrong results.

To avoid adding unnecessarily many lazy constraints, COPT has some simple redundancy checks for lazy constraints in place. Exact duplicates will be discarded. However, adding many, very similar but redundant lazy constraints will negatively affect COPT’s performance. This should be avoided by the user.

Notes

  • Registering a callback function for the CBCONTEXT_MIPSOL will make COPT believe that the user wants to add lazy constraints. As lazy constraints are not actually part of the model, this will lead to the deactivation of dual reductions during COPT’s presolve, as dual arguments rely on the knowledge of all model rows. If the user does not intend to add lazy constraints but still wants to use the CBCONTEXT_MIPSOL, COPT provides the LazyConstaints parameter which enables the user to explicitly tell COPT whether or not lazy constraints will be added to the model. By default, this parameter is set to -1 meaning COPT will turn off dual presolve reductions if either lazy constraints are part of the model or a callback for context CBCONTEXT_MIPSOL has been installed. Explicitly setting the parameter to 0 will allow dual reductions during COPT’s presolve even if lazy constraints or a callback for context CBCONTEXT_MIPSOL are present. This is useful only in very rare cases, e.g., if the callback only prints information about solution candidates but never adds lazy constraints. As soon as lazy constraints are added, this might lead to wrong results, however. For printing information about solutions, consider using the CBCONTEXT_INCUMBENT context instead.

  • If a user invokes a function to add lazy constraints from a callback in the CBCONTEXT_MIPSOL context, the current MIP candidate solution will be rejected, no matter whether the added lazy constraint(s) are actually violated or not. This enables the user to reject arbitrary solutions by adding empty lazy constraints when an undesirable solution is found. Note however, that COPT might find the same solution multiple times if no lazy constraint is provided. The LP relaxation solution will not necessarily be rejected if lazy constraints are added in CBCONTEXT_MIPRELAX, only when these are actually violated.

  • It is invalid to call the any functions of the Model class for object-oriented languages (or their C equivalent) to add lazy constraints in a callback. More generally, the model cannot be changed during the solving process, except by adding lazy constraints or cutting planes.

Adding cutting planes

Cutting planes are added to the model during the solving process to strengthen the LP relaxation, e.g., cut off fractional LP solutions and improve the lower bound of the MIP problem.

COPT supports the addition of custom cutting planes to the model during the solving process. Similar to lazy constraints, cutting planes can be added to the model before and, via the callback, during the solving process. Each API provides two sets of methods, one for adding cutting planes to the initial model and one for adding them from a callback. In the C API, the methods can be distinguished according to whether or not the function name contains "Callback", e.g., COPT_AddUserCut and COPT_AddCallbackUserCut. In object-oriented APIs, the two sets of functions correspond to the Model class and the CallbackBase class respectively. Taking python as an example:

  • Before solving, user can directly add cutting planes to the model by calling Model.addUserCut() or Model.addUserCuts().

  • During the solving process, a user can dynamically add cutting planes (if supported by the current context, see below) from within the callback function via CallbackBase.addUserCut() or CallbackBase.addUserCuts().

Cutting planes can only be added in the CBCONTEXT_MIPRELAX context. Here, the user is provided with the current LP relaxation solution to separate their own cutting planes.

Notes

  • Cutting planes that do not violate the current LP relaxation solution are discarded by COPT.

  • It is invalid to call any functions of the Model class (or their C equivalent) to add cutting planes in a callback. More generally, the model cannot be changed during the solution process, except by adding lazy constraints or cutting planes.

Adding feasible solutions

COPT supports adding feasible solutions during the MIP solving process. This enables the user to provide any feasible solution they found in parallel to the COPT solution process, e.g., in a self-implemented heuristic. Known solutions can either be supplied as starting solutions by calling COPT_AddMipStart (see MIP Starts) or from within a callback function. If a solution is know beforehand, supplying it as a MIP starting solution is preferred. For solutions found during the solving process, in the C API, a solution can be added by calling COPT_AddCallbackSolution inside the callback. In object-oriented APIs, the functions needed to add a solution are provided by the CallbackBase class and the workflow consists of calling two functions:

  • Set the feasible solution: CallbackBase.setSolution(vars, val)

  • Load a custom solution into the model: CallbackBase.loadSolution()

COPT will check any provided solution for feasibility and compute its objective value. The computed objective is return by loadSolution / COPT_AddCallbackSolution. If a solution is infeasible or worse than the current incumbent, it is discarded and the objective value returned by COPT is set to 1.0e+30.

Solutions can be added in any callback context.

Notes

Currently, COPT only supports complete feasible solutions within callbacks.

Using the callback utilities in different APIs

For object oriented programming languages the basic steps of setting up a callback function are:

  1. Implement a custom callback class, inheriting from the CallbackBase class.

  2. Implement the CallbackBase.callback() function. This is the callback function that will be invoked by COPT. Here, the user can invoke the callback-specific functions for obtaining information or controlling the solution process.

  3. Create an object of the custom callback class.

  4. Register the callback in COPT through Model.setCallback(), and input the Callback Context as a parameter. For registering the callback function for multiple contexts, one can bitwise-or the desired contexts, e.g., COPT.CBCONTEXT_MIPSOL | COPT.CBCONTEXT_MIPNODE.

In the subsequent solving process, the user-supplied CallbackBase.callback() function will be called in each context registered. The currently invoked context can be obtained by calling the CallbackBase class method where().

As already mentioned in the sections above, functions in CallbackBase class (or their corresponding C functions) can only be called in certain contexts. The following table lists for each callback context the allowed callback operations, using the Python API:

Context

Function

CBCONTEXT_INCUMBENT

getInfo, getIncumbent, load/setSolution

CBCONTEXT_MIPNODE

getInfo, getIncumbent, getRelaxSol, load/setSolution

CBCONTEXT_MIPRELAX

addUserCut(s), getIncumbent, getInfo, getRelaxSol, load/setSolution

CBCONTEXT_MIPSOL

addLazyConstr(s), getIncumbent, getInfo, getSolution, load/setSolution

Notes

  • While getInfo can be called in all contexts, the information available depends on the context. See Obtaining information during the solving process for details.

  • In other APIs getInfo is often split into getIntInfo and getDblInfo.

  • The functions above may look slightly different for a certain API, but the presented relationships are the same.

While the above steps use Python as the reference API, the implementation in each object oriented programming language API is similar, and the user can refer to the provided sample code. For Python, "cb_ex1.py" is available in the examples directory in the installation package. For C, the main differences when implementing and registering a callback are as follows:

  • The custom callback function can be any function using the signature int COPT_CALL <function>(copt_prob* prob, void* cbdata, int cbctx, void* usrdata) where <function> is arbitrary.

  • Instead of using Where() for obtaining the current context, the callback context is supplied in cbctx.

  • Callback-relevant information can be passed by defining a custom struct and passing it as the usrdata argument.

See cb_ex1.c in the C examples folder for a reference implementation.

The calling method and function name of the callback function in different programming interfaces are slightly different, but the supported functions and function meanings are the same. Please refer to the corresponding chapters of different programming interface API reference manuals for specific introductions: