Callbacks功能
杉数求解器COPT提供Callbacks(回调)功能,支持用户在混合整数规划分支切割的求解过程中,获取中间信息(如当前最优下界、当前最优目标值等);以及控制求解进程:如修改模型(如添加惰性约束、添加割平面),终止求解等。目前支持使用Callbacks的问题类型有:混合整数规划、混合二阶锥规划、混合整数二次(约束)规划。(下文为避免表述冗余,我们将这些混合整数的问题类统一称为MIP)。
回调函数是COPT在求解过程中所调用的由用户自定义的函数,用户可以通过COPT支持的API为一个或多个Callback Context(回调函数触发条件)注册一个自定义的回调函数。 不同API中使用Callbacks功能 章节将会详细介绍如何设置回调函数。回调函数将在求解过程中的特定时刻被调用,取决于回调触发条件。在回调函数被调用中,用户可以分别 获取求解过程中间信息 和 控制求解进程。可获取的信息以及可执行的操作取决于Callback Context。目前,COPT支持四种Callback Context:
CBCONTEXT_INCUMBENT
: 当找到当前最优可行解时,触发回调函数;CBCONTEXT_MIPNODE
: 当处理完成MIP节点(并求解LP松弛问题完成)时,触发回调函数;CBCONTEXT_MIPRELAX
: 当找到MIP线性松弛解时,触发回调函数;CBCONTEXT_MIPSOL
: 当找到MIP可行解时,触发回调函数。
本章节的内容构成如下:
注意:
COPT一次性只支持注册一个回调函数,但是在同一个回调函数中可以传递多个Callback Context。如果用户想要在不同的Callback Context下执行不同的操作(例如在 CBCONTEXT_MIPSOL
下,添加惰性约束;在 CBCONTEXT_MIPRELAX
下,添加用户自定义的割平面约束),则需要将这些相关的Callback Context都作为函数参量传递给回调函数,在该函数内部,可以在指定的Context下,执行对应的操作。
获取求解过程中间信息
MIP求解过程中,可获取到的中间信息取决于Callback Context(Callback的触发条件),具体对应关系请参见下表。
通常,用户在回调函数内部调用API函数来获取求解中间信息(API函数的名称取决于API,如C API是 COPT_GetCallbackInfo
,面向对象的API则通过 CallbackBase.getInfo
),通过将所需信息以字符串形式传递给函数参数来指定。COPT支持获取的Callback信息,详细请参考 Callback相关信息 部分。
对应关系罗列如下表:
Callback Context |
Callback Information |
---|---|
|
|
|
|
|
|
|
除了上述列出的Callback信息及特定相对应的Callback Context以外, BestObj
, BestBnd
, HasIncumbent
, Incumbent
在任何触发条件都可以被获取。
注意
如果
HasIncumbent == False
, 则无法获取Incumbent
。信息
NodeStatus
的返回值为常数,表示当前节点LP松弛问题的求解状态,可取值请参考: 一般常数章节:解状态(部分) 。对于
Incumbent
,RelaxSolution
, 和MipCandidate
这三项信息,不同接口中的获取方式有所不同:C语言:通过函数
COPT_GetCallbackInfo
,需获取的中间信息名称作为函数的参数提供;面向对象的编程语言(C++/C#/Java/Python)中,
CallbackBase
类提供专门的函数,以获取相应中间信息。
例如,Python/C++接口的
CallbackBase
类提供getIncumbent
,getRelaxSol
,getSolution
。其他编程语言接口类似, 可参考各API的CallbackBase
类。
控制MIP求解进程
COPT提供相关函数,让用户在MIP分支切割法的求解过程中,交互式地添加惰性约束或割平面,以控制MIP求解进程。主要有如下三类操作:
添加惰性约束
添加割平面
设置自定义的可行解
添加惰性约束
惰性约束是仅在判断约束条件被违反时才向模型添加的约束。对于一些包含大量约束的模型,这种做法可以有效地减小模型的规模,提高MIP求解效率。其中一个常
见问题示例是TSP模型,可以参考安装包中 "examlpes"
下的 "cb_ex1"
。
COPT支持两种方式添加惰性约束。一种是 在求解之前 显式向原始模型中添加惰性约束;另一种是通过用户自定义的回调函数, 在求解过程中 动态添加
惰性约束。为此,每个API都提供了两组方法:一组方法用于将惰性约束添加到初始模型中,另一组则用于从回调中添加惰性约束。
在C API中,这两组方法可以根据函数名称是否包含 "Callback" 来区分,例如, COPT_AddLazyConstr
和 COPT_AddCallbackLazyConstr
。
在面向对象的API中,这两组函数分别属于 Model
类和 CallbackBase
类。以Python为例:
在求解之前,用户可以通过调用
Model.addLazyConstr()
或Model.addLazyConstrs()
直接向模型添加惰性约束。在求解过程中,(在当前Callback Context支持的情况下)用户可以通过回调函数内部的
CallbackBase.addLazyConstr()
或CallbackBase.addLazyConstrs()
动态添加惰性约束。
对于以上两种方式,COPT会将添加的惰性约束单独存储,与实际优化模型分开,并且只有在求解过程中发现的解违反了这些约束时,才会将它们添加到模型中。
为确保正确性,在求解过程中,COPT会检查到目前为止所添加的全部惰性约束是否被任何找到的解违反,这将会增加求解时间(特别是当添加了许多未被违反的惰性约束时)。 建议用户只在必要时添加惰性约束,例如当它们被找到的解所违反时。
惰性约束只能在 CBCONTEXT_MIPSOL
和 CBCONTEXT_MIPRELAX
中添加。虽然严格来说不需要检查每个LP松弛解是否违反了惰性约束,但用户必须
针对 CBCONTEXT_MIPSOL
下所找到的每个可行解,检查其是否违背了惰性约束,以避免产生错误结果。
为了避免添加过多不必要的惰性约束,COPT实施了一些简单的冗余检查,完全重复惰性约束将被丢弃。即便如此,添加许多非常相似但冗余的惰性约束会对COPT的性能 产生负面影响,用户应避免这种情况。
注意:
如果用户为
CBCONTEXT_MIPSOL
注册一个回调函数,那么COPT将会认为用户需要添加惰性约束。由于惰性约束实际上并不是模型的一部分,这将导致 COPT在预处理期间停用对偶约减,因为对偶参数依赖于对模型中所有行(约束)的信息。如果用户并不打算添加惰性约束,但仍然想使用
CBCONTEXT_MIPSOL
,COPT提供了 LazyConstaints 参数, 该参数使得用户可以明确告诉COPT是否将惰性约束添加到模型中。默认情况下,此参数设置为-1
,表示如果模型中包含惰性约束或注册了CBCONTEXT_MIPSOL
的回调函数,COPT将会在预处理期间关闭对偶约减。将参数显式设置为0
,表示即使存在惰性约束或CBCONTEXT_MIPSOL
的回调函数。将允许COPT在预处理期间进行对偶约减,这仅在非常罕见的情况下起作用。例如,如果在回调函数仅打印找到的可行解信息,但并不添加惰性约束这种 情况。然而,一旦用户添加了惰性约束,这可能导致错误的结果。如果需要打印关于模型解的信息,请考虑使用CBCONTEXT_INCUMBENT
。如果用户在
CBCONTEXT_MIPSOL
中添加惰性约束,无论实际上添加的惰性约束是否被违反,当前的MIP可行解都将被拒绝。 当找到解不符合要求的情况下,这种设计使得用户能够通过添加空的惰性约束来拒绝任意可行解。但请注意,如果没有提供具体的惰性约束,COPT可能会多次 找到相同的解。如果在CBCONTEXT_MIPRELAX
中添加惰性约束,当前的LP松弛解不一定会被拒绝,只有在实际违反了这些约束时才会被拒绝。对于面向对象的编程接口来说,在回调函数中调用
Model
类的任何函数来添加惰性约束是无效的(对于C接口来说,则是对应的API函数)。更一般的说, 在求解过程中不能更改模型,除非是添加惰性约束或切割平面。
添加割平面
在求解过程中添加割平面到模型中可以加强LP松弛模型的效果,例如削减取值为小数的LP解以改善MIP问题的下界。
COPT支持在求解过程中向模型中添加用户自定义的割平面。与惰性约束相类似,割平面可以在 求解之前 或者通过callback 在求解期间 添加到模型中。
每个API提供两组方法,一组用于向初始模型中添加割平面,另一组用于通过回调函数添加割平面。在C API中,可以根据函数名是否包含 "Callback"
进行区分,
例如, COPT_AddUserCut
和 COPT_AddCallbackUserCut
;在面向对象的API中,这两组函数分别对应于 Model
类和 CallbackBase
类。
以Python为例:
在求解之前,用户可以通过调用
Model.addUserCut()
或Model.addUserCuts()
直接向模型添加割平面。在求解过程中,(在当前Callback Context支持的情况下)用户可以通过回调函数内部
CallbackBase.addUserCut()
或CallbackBase.addUserCuts()
动态地添加割平面。
割平面 只能 在 CBCONTEXT_MIPRELAX
中添加。在此处,用户基于当前的LP松弛解以分离自定义的割平面。
注意
COPT会舍弃没有违背当前LP松弛解的割平面。
对于面向对象的编程接口来说,在回调函数中调用
Model
类的任何函数来添加割平面是无效的(对于C接口来说,则是对应的API函数)。更一般的说, 在求解过程中不能更改模型,除非是添加惰性约束或割平面。
设置自定义的可行解
COPT支持在MIP求解过程中添加可行解。这使得用户能够在COPT求解过程中同步提供任何他们自己找到的可行解。例如在自行实现的启发式算法中。
已知的可行解可以通过调用 COPT_AddMipStart
(参见:ref:chapMipstart)提供或者在回调函数中提供。
如果用户事先知道一个现成的解,则最好将其作为MIP的起始解提供。对于在求解过程中找到的解,在C API中,可以在回调函数内部调用
COPT_AddCallbackSolution
来逐个进行添加,在面向对象的API中,添加解所需的函数由 CallbackBase
类提供,需要依次调用两个函数:
以Python为例:
设置自定义的可行解:
CallbackBase.setSolution(vars, val)
将自定义的解加载到模型中:
CallbackBase.loadSolution()
注意
目前,通过callbacks的方式,COPT支持设置完整的可行解。
不同API中使用Callbacks功能
以面向对象的编程接口为例,展示在不同的API中,调用Callback功能的基本步骤如下:
构建一个自定义Callback类,并继承
CallbackBase
类;实现
CallbackBase.callback()
函数,该函数为COPT调用的回调函数。在其中用户可以在不同的Callback Context下调用执行所需操作 (获取中间信息或者添加惰性约束/割平面等);新建自定义的Callback实例;
通过Model类的
Model.setCallback()
注册Callback实例,并将Callback Context作为参数输入。如果需要为多个Context注册回调函数, 可以使用按位或运算符连接。例如,COPT.CBCONTEXT_MIPSOL
|COPT.CBCONTEXT_MIPNODE
。
在后续的求解过程中,用户实现的 allbackBase.callback()
函数将在每个被注册的Context中被调用。用户可以通过调用 CallbackBase
类的方法
where()
以获取当前调用的Context。
如前面的部分已经提到的, CallbackBase
类中的函数(或其对应的C API函数)只能在特定的Context中被调用。下表列出了每个Callback Context中
允许的特定回调操作,以Python为例, CallbackBase
类中的成员函数以及对应可调用的Context罗列如下:
Context |
Function |
---|---|
CBCONTEXT_INCUMBENT |
|
CBCONTEXT_MIPNODE |
|
CBCONTEXT_MIPRELAX |
|
CBCONTEXT_MIPSOL |
|
注意:
虽然
getInfo
可以在所有Callback Context中被调用,但可用的信息依然取决于Callback Context。详细信息请参阅 获取求解过程中间信息。在除Python以外的其他API中,
getInfo
经常被拆分为getIntInfo
和getDblInfo
以获取不同数据类型的回调信息。上述函数在不同的API中可能名称略有不同,但呈现的关系是相同的。
虽然上述步骤以Python API中作为参考,但对于面向对象编程语言API中,实现方式都类似,用户可以参考安装包 "examlpes"
目录下的的示例代码。
如Python接口的 "cb_ex1.py"
。对于C API,在实现和注册回调时的主要区别如下:
自定义回调函数可以是任何函数,其函数名称为
int COPT_CALL <function>(copt_prob* prob, void* cbdata, int cbctx, void* usrdata)
, 其中<function>
是任意的函数名。获取当前触发条件不再使用
where()
,而是在cbctx
参数中传递回调触发条件。可以通过定义一个自定义的
struct
结构并将其作为usrdata
参数传递来传递与回调相关的信息。
具体实现请参考C API示例文件夹中的 cb_ex1.c
。
回调函数在不同编程接口中的调用方式和函数名称 略有不同 ,但是支持的功能和函数含义是一致的,请进一步参考不同编程接口API参考手册的对应章节以获取具体介绍:
C API:回调功能函数
C++ API:CallbackBase类
C# API:CallbackBase类
Java API:CallbackBase类
Python API:CallbackBase类