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如下表:
操作
C
C++
Java
Python
CB_GET
CB_CUT
CB_BRANCH
CB_SETSOLUTION
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")