6.2. 回调功能 (Callback)

MindOpt 使用经典的分支定界法来求解MIP问题。MindOpt 为用户提供回调功能,帮助用户对MIP问题的求解过程进行跟踪与修改。利用回调功能,用户可以提供一些决策以修改求解过程,包括:

  • 添加割平面,裁剪不会出现最优解的分支;

  • 干预 MindOpt 的分支选择策略,控制节点二分方法及遍历顺序;

  • 添加自定义可行解(比如通过某种启发式算法得到的解),一个较好的可行解可以提高 MindOpt 的求解效率。

Note

用户需要确保自定义割平面的正确性。如果裁剪掉了最优解所在的节点,则可能导致 MindOpt 最终无法寻找到最优解。

6.2.1. API列表

  • 读操作:对MIP求解过程进行跟踪

    CB_GET

    通过一个数据ID,获得某个求解过程中的上下文数据。数据ID的列表见下文。

  • 写操作:对MIP求解过程进行修改

    CB_CUT

    增加一个用户定义的割平面。

    CB_BRANCH

    用户自定义节点的二分方案以及子节点的遍历顺序。

    CB_SETSOLUTION

    提供一个可行解给 MindOpt

各操作对应多语言SDK中的API如下表:

6.2.2. 回调Code

MindOpt 在回调用户自定义函数时,通过一个整数参数 where 告知用户,当前求解器所处的阶段。 参数 where 可能取值如下:

名称

描述

MIPSTART

3

MIP优化器处于初始化阶段。

MIPSOL

4

MIP优化器刚刚得到了一个新的更优解。

MIPNODE

5

MIP优化器正在迭代一个节点。

用户在自定义的回调函数中调用cbGet获取数据时,需要指定数据ID,即参数 what,可选的值如下:

名称

数据ID

数据类型

数组长度

描述

PRE_NUMVARS

200

int

预处理模型的列数。

PRE_NUMCONSTRS

201

int

预处理模型的行数。

PRE_NUMNZS

202

int

预处理模型的非零元个数。

MIP_OBJBST

300

double

当前历史最优解对应的目标函数值。

MIP_OBJBND

301

double

当前目标函数值下界。

MIP_RELVAL

302

double

当前松弛问题的解对应的目标值。

MIP_NODCNT

306

int

当前搜索过的节点数。

MIP_SOLCNT

307

int

当前搜索到的所有可行解的数目。

MIP_CUTCNT

308

int

当前应用的所有切平面的数目。

MIP_NODLFT

309

int

未搜索的节点数目。

MIP_NODEID

310

int

当前节点的唯一ID。

MIP_DEPTH

311

int

当前节点的深度。

MIP_PHASE

312

int

MIP优化器当前所处的阶段。(0:无解;2:远离最优;3:逼近最优)

MIP_SOL

316

double array

MODEL_NUMVARS

当前问题的最佳解决方案

MIP_REL

317

double array

MODEL_NUMVARS

当前搜索到的松弛解

Note

使用 MindOpt C SDK开发时,写操作(MDOcbcut()MDOcbbranch()MDOcbsolution())需要传入的变量索引,应当以预处理后模型的变量索引为准,而不是原模型的变量索引。

6.2.3. 回调函数签名

  • C SDK

    /**
     * The model argument is the original model.
     * The cbdata argument is the callback context for read or write operations.
     * The where argument indicates where in the optimization process the callback function is being called.
     * The userdata argument is the user-defined context.
     * The callback function should return zero by default.
     */
    int callbackfn(const MDOmodel* model, void* cbdata, int where, const void* usrdata);
    
  • C++ SDK

    /**
     * The model argument is the original model.
     * The cbdata argument is the callback context for read or write operations.
     * The where argument indicates where in the optimization process the callback function is being called.
     * The userdata argument is the user-defined context.
     * The callback function should return zero by default.
     */
    int cbfn(const MDOmodel* model, void* cbdata, int where, const void* usrdata);
    
  • Java SDK

    /**
     * The model argument is the original model.
     * The cbdata argument is the callback context for read or write operations.
     * The where argument indicates where in the optimization process the callback function is being called.
     * The userdata argument is the user-defined context.
     * The callback function should return zero by default.
     */
    int java_callback(const MDOmodel* model, void* cbdata, int where, const void* usrdata)
    
  • Python SDK

    # The model argument is the presolved model.
    # The where argument indicates the current phase in the optimization process.
    # The return value is ignored.
    def cbfn(model, where)
    

6.2.4. 示例

接下来,以Python语言为例,展示Callback功能中的函数使用方法。

from mindoptpy import *

m = Model()
m.modelsense = MDO.MAXIMIZE

x = m.addMVar((3,), obj=[20, 6, 8], vtype=['C', 'I', 'I'])

A = [
    [0.8, 0.2, 0.3],
    [0.4, 0.3, 0],
    [0.2, 0, 0.1]
]

b = [4, 2, 1]

c = m.addMConstr(A, x, '<', b)

def callback(model, where):
    where_names = {
        3: "MIPSTART",
        4: "MIPSOL",
        5: "MIPNODE"
    }

    print("where = {}".format(where_names[where]))

    # Count call times
    model._calls += 1

    # When using the Python SDK, the model passed to the callback is a presolved model. Note that the C SDK uses the original model.
    # Print the constraint matrix of the presolved model here. The getA() function returns a scipy.sparse.csc_matrix.
    # The constraint matrix returned by getA() is a reference, and any modifications to the constraint matrix should not be allowed as it may lead to unforeseen issues.
    print(model.getA())

    cur_sols = model.cbGetSolution(model.getVars())

    # Print the current best solutions
    print(cur_sols)

    rel_sols = model.cbGetNodeRel(model.getVars())

    # Print  the current relaxation solution
    print(rel_sols)


# Member variables starting with "_" can be added to the model.
# The member variable "_calls" in this context is used to show the passing of the callback context.
m._calls = 0

# Pass the callback function to the solver
m.optimize(callback)

print("Number of calls: {}".format(m._calls))

# Print the optimization result
if m.status == MDO.OPTIMAL:
    print("Objective value: {}".format(m.objval))
    print("Solution")
    sol = x.X
    for i in range(len(sol)):
        print("  x[{}] = {}".format(i, sol[i]))

else:
    print("No feasible solution found so far")