当整数规划或者混合整数规划的求解时间被拉长以后,如果能够提前把一组已知的变量取值交给CPLEX,就有可能让它更快地找到一套可用的方案。这个做法的关键,就是使用MIP Start功能,把现成的变量值先传给求解器,好让搜索的起点不至于完全空白,而是从一个更靠近可行区域的位置开始。CPLEX收到这组解之后,并不是直接拿来当答案,而是会自己先检查它到底可不可行,再决定要不要把它作为后续搜索的基础。
一、怎样把初始解导入CPLEX
在把初始解传进模型以前,需要先整理好各个变量和它们对应的数值,同时要保证变量名称、变量类型以及允许变动的范围,都跟当前模型里的定义完全一致,要不然求解器很可能认不出来,甚至直接拒绝这组解。
1、准备好初始解的数据
先把模型当中那些比较关键的整数变量挑出来,给它们填上初始值。二进制变量就只取0或1,整数变量填整数,连续变量只要别超出上下界就行。这组数据并不要求把全部变量都覆盖到,但那些居于核心位置的整数变量最好都能包含进去,否则能起到的引导作用就会很有限。
2、在程序代码中添加MIP Start
如果用的是Python、C++、Java或者.NET的接口来调用CPLEX,可以在代码里找到一个类似addMIPStart的方法,把装着变量的列表和装着数值的列表一起传进去。传送的时候要特别留意,两个列表的前后顺序必须严格对齐,既不能出现错位,也不能漏掉任何一个。
3、通过外部文件导入初始解
要是这组初始解是由别的系统或者工具生成的,也可以把它单独存成一份CPLEX能认得的初始解文件,在求解开始之前先读进来。用文件的好处在于,同一套初始方案能够在不同的实验里反复使用,而且需要跟团队共享的时候,直接把文件发过去就行了。
4、到求解日志里确认是否加载成功
把初始解喂进去并启动求解之后,一定要记得翻看一下CPLEX输出的日志。在正常的运行日志里面,会出现一行跟MIP start有关的提示,可能是说已经读入了一份起始解,也可能顺带标出了这组解对应的目标值。如果从头到尾翻遍了日志,都没有找到任何相关的记录,那就基本说明刚才传进去的信息并没有被成功加载。
二、初始解不生效时该怎么排查
如果明明已经把初始解传进了模型,可是求解的时间一点都没有缩短,或者日志里直接提示这组解被拒绝了,那多半不是求解器本身的问题,而是递进去的那组解身上存在着某些它没法接受的缺陷。
1、先检查这组解是不是真的可行
在所有可能的原因当中,最常见的一种情况就是初始解违反了模型里的某一条约束。有时候,一组数值看上去好像没什么问题,其实已经悄悄碰破了容量限制、打破了平衡条件,或者超出了上下界。CPLEX一旦发现这种情形,就会直接把它丢掉。所以碰到这种情况,最管用的办法就是把这些变量值逐个代回到所有的约束式里去,实实在在地验算一遍,看到底是哪一条没有被满足。
2、核对每一个变量的类型有没有写错
如果一时疏忽,把一个应该只取0或1的二进制变量填成了小数,或者让整型变量带上了小数点,又或者让某个连续变量蹭到了它自己的上下界之外,这些错误都会立刻让初始解被判成无效。尤其是从外部文件导入的时候,在格式转换的过程中更容易出现这类偏差,排查的时候要格外留心。
3、检查变量名称和顺序是否仍然匹配
模型在经过修改之后,有些变量的名字可能已经在不知不觉中变了样。比如原先叫x_1_1,重构之后在系统里变成了x[1][1],而提供的初始解还在沿用旧名字,自然怎么都对不上号。另外,用代码传值的时候,也要确认变量列表和数值列表从头到尾的顺序完全一致,中间没有多出来一个,也没有漏掉哪一个。
4、看看被覆盖的变量是不是太少了
MIP start并不要求把全部变量都填满,可以只给出其中一部分的值。但是如果只象征性地给几个连续变量填了数,却把那些真正影响决策的二值选择变量、路径变量或者开关变量全都空在那里,那它对整个混合整数规划的帮助就相当微弱。真正能让后面的搜索少绕弯路的,还是要给这些核心的整数变量提供一组比较靠近较优区域的合理取值。
三、初始解成功导入后怎么判断它的效果
即便初始解已经平平稳稳地被CPLEX吞下去了,也并不能代表求解速度就一定会明显加快。它的真实效果,还是要结合日志里的信息,并且拿有初始解和没有初始解的两组实验结果放在一起比较,才能看得比较清楚,不能只靠一两次的运行时间就急着下结论。另外还要注意,模型的结构一旦发生过改动,以前费心调出来的旧初始解很可能转眼就会失效,所以最好把模型的版本、变量的定义方式、初始解的文件,还有每次对应的求解日志,都一一对应着保存妥当。
1、确认初始解有没有被真正接受
如果日志里面清楚地写明了初始解已被接受,并且还给出了这组解的目标值,那就说明CPLEX已经把它当作后续搜索的基础了。反过来,如果日志里写着被拒绝,就要赶紧顺着不可行、类型出错、名称对不上这几个方向继续往下找原因。
2、观察第一个可行解出现的时间
可以把一次带了初始解和一次完全没有带初始解的两个实验摆在一起,比对比对求解器打出第一个可用方案的那条时间记录,看看这个间隔是不是真的缩短了。如果手上的模型本来就能在很短的时间内找到可行解,那初始解在时间上的改进可能就不会太明显。
3、观察最终Gap和搜索节点数的变化
有一类模型,它的难题并不是找不到可行解,而是卡在后面怎么也证明不了自己已经是最优的了,具体表现就是Gap值迟迟降不下来。在这种情形下,初始解最多能帮着改善一下前期的上界,至于它对整个搜索树规模和最终Gap收敛能起多大作用,常常还需要把跑过的节点数,还有总共花掉的求解时间,这些指标合在一起来综合判断。
4、做好长期记录和版本管理
每次改动初始解以后,最好顺手记下目标值、第一个可行解出现的时间、最终的Gap和总共跑过的节点数这些关键数据。等攒够了多组数据之后,再把有无初始解的两排结果放在一起比较,就能比较清楚地看出它到底帮了多大的忙。
总结
在CPLEX里导入初始解,说到底就是通过MIP Start功能,把一组跟当前模型对得上号的变量取值交给求解器。如果初始解没有生效,应该从可行性、变量类型、名称顺序以及覆盖范围这几个方面一项一项地排查。等到初始解被接受之后,也不能光盯着一次的求解时间就下判断,还要结合日志提示、第一个可行解出现的时间、最终的Gap和节点数变化,把这些信息综合起来,才能把它的真实效果看明白。
