回到主页

加速整数规划建模和求解的策略

——以解决TSP问题为例

预先课程:tutorial的TSP模型,或参考博客:IntegerProgramming整数规划模型 (mysxl.cn)

一、TSP问题介绍

旅行商问题(Travelingsalesman problem),简称为TSP问题,即在一个具有n个城市的完全图中,旅行者希望进行一次巡回旅行,或经历一次哈密顿回路,可以恰好访问每一个城市一次,并且最终回到出发城市。而这次巡回旅行的总费用为访问各个城市费用的总和,故旅行者同时希望整个行程的费用是最低的,求这个路线的排列策略。

 

二、TSP模型回顾

按子回路消除方式不同,TSP模型总体上可分为两类。

1.基于subtour-elimination约束的模型(简称SEC模型)

broken image

中心思想:子集中所有边(弧)的数目不超过子集中顶点的数目,证明过程从略。
MATLAB代码实现如下:

broken image

2.基于MTZ约束的模型(简称MTZ模型)

broken image

中心思想:引入辅助决策变量u,ui代表客户点i所在的位置,确保ui沿回路持续增加,避免产生子环,证明过程从略。

MATLAB代码实现如下:

broken image

一般情况下,MTZ约束消除子回路的效率更高。然而随着问题规模的扩大,上述任何一种约束均无法实现合理时间内的求解以满足科学研究或工程实践的需求,故有必要采取相应手段进行提速。这也是撰写本文的重要动机,接下来的章节将详细讨论导致传统TSP模型求解速度慢的原因及解决方法(主要包括:如何使用向量化方法加快模型转换、如何利用惰性约束/精简决策变量提升求解效率、如何基于启发式算法的解提升求解器性能以及Gurobi在Python环境下如何通过callback机制进一步发挥惰性约束的性能),请读者按需阅读。

 

三、传统TSP模型效率低下的原因分析

broken image

 

四、加速TSP模型求解的五种技术手段

1.向量化建模

本质是将“1次添加1个约束”转化为“1次添加1组约束”、将“多维数组”转化为“一维数组”,下面用2段MATLAB代码加以说明。

broken image

上图是进出平衡约束的一部分(only leave),即旅行商只能从唯一的点i处离开并去往别处。初始版本通过for循环实现,令i执行从1到n的迭代,并在每次迭代中对二元决策变量x矩阵的第i行所有列求和,确保值为1;改进后的版本消除了for循环,直接对二元决策变量x矩阵的所有行进行求和,并输出一个n行1列的ones矩阵。

前后对比,不难发现:由于不带循环,后者(向量化)的代码更为简洁。代码行的减少意味着引入编程错误的机会也减少了。此外,在TSP问题中,随着for循环嵌套次数的增加(如:MTZ约束带2个for循环),向量化代码在运行速度上的优势将体现得更加明显,具体介绍请见下文。

broken image

本文分3种情况论证向量化方法在加快模型转换速度上发挥的巨大功用,分别是:不采用向量化约束、采用向量化约束消除1个for循环和采用向量化约束消除2个for循环,各自建模用时如下图所示。(注:算例为Berlin52)

broken image

可见,for循环越少(向量化应用得越完善),模型转换耗时越短。然而,随着问题复杂度攀升,在多维VRP问题中,向量化后矩阵维度整体扩大,Gurobi求解速度甚至不如原始for循环的现象也时有发生。因此,正确并巧妙运用repmat、repelem、reshape等函数来保证向量化复制方向/顺序在多维问题中至关重要,建议读者访问MathWorks中文官网自行学习,附网址链接:向量化 - MATLAB & Simulink - MathWorks 中国衡量代码的性能 - MATLAB & Simulink - MathWorks 中国

 

2.惰性约束提升求解效率

TSP子回路消除约束可防止巡回中出现子环,这些约束通常是指数级的,然而它们对问题求解并非总能起效,因此我们不希望将其全部添加至模型中。惰性约束(lazy constraints)的功能在于:仅在必要时刻向模型中添加违反的子回路约束,此举将极大程度上避免浪费不必要的计算资源。

本节把在MATLAB中调用Gurobi并结合惰性约束加速模型求解概括为3个步骤:

(1)首次优化

broken image

此时模型中只包含进出平衡约束,无子回路消除约束,调用Gurobi求解,得到的解x带有子回路(非可行解)。

(2)基于二元决策变量x,找到子回路subTours

broken image

 

broken image

以上代码块用于判定有无子回路,以及子回路是什么、数量是多少。此处使用的函数无具体要求,可“八仙过海,各显神通”,只要速度尽可能快、效率尽可能高即可。
提取解x,寻找对应的子回路和子回路数量。若子回路数量=1,则证明x为最优解。

(3)对于每条子回路施加子回路消除约束(SEC或MTZ),直到无子回路

broken image

当子回路数量>1时,执行以上while循环,通过nchoosek、fliper、sub2ind等函数将子回路内无序的边转化为1维有序index,继而对已有子回路进行消除。

待当前子回路完全消除,再次调用Gurobi优化当前解,优化完毕后重新计算子回路。同理,若子回路数量>1,则重复while循环,直到numtours=1,即所有子回路被消除完毕。

以Berlin52算例为例,使用和不使用惰性约束均可求得问题的最优解7542,但前者用时显著少于后者,如下图所示。

broken image
broken image


3.精简决策变量提升求解效率

传统TSP问题的二元决策变量x形成一个n*n的矩阵,用以下代码定义:

broken image

然而,由于客户点不能实现对自身的访问(如从城市2出发,到达城市2),所以决策变量矩阵内对角线上的元素(i=j)是冗余的。为避免非必要的判定,引入1D(1维)方式设置决策变量,形如:

broken image

该方法达成了削减决策变量的目的,将决策变量数从最初的n*n个压缩至n*(n-1)个。值得注意的是,以1D方式定义决策变量不够直观,应用中易出错,需格外小心。

本节以Berlin8算例为例,简要阐释1D方式精简决策变量的原理和过程。

broken image

pair为边的集合,nbpair为边的数量,同时也是决策变量x的个数。

broken image

 

broken image

在非1D版本中,dist通常是距离矩阵,而在1D版本中,dist成为包含每条边(pair)和各边对应距离(value)的结构体。

broken image

基于向量化方法(带有1个for循环,时间上可接受)描述进出平衡约束,使得当索引为i时,双精度数组pair的第1、2列求和均等于1。随后施加MTZ约束消除子回路。

 

4. 基于启发式算法的解提升求解器性能

利用启发式算法获得质量较好的解作为精确算法热启动(warm start)初始解上界值从而增强商业求解器性能是学界普遍采用的方法,但这种改进是概率性的。下图摘自某科研论文,较为直观地展现了这一点。

broken image

观察该图,纵坐标为CPU用时,点越靠近下方证明用时越短、求解效果越好;蓝色、红色图例分别表示启发式解作为上界和warm start初始解。可以看出:启发式解作为上界时耗时普遍更短,而作为初始解时往往未必能获得更理想的效果。

本节以最近邻启发式算法为例,评测启发式解分别作为初始解和上界时对Gurobi求解性能的影响。

(1)启发式解作为初始解

基于最近邻启发式得到的解heuristicTour无法直接被Gurobi识别,需先将其转化为n*n的决策变量矩阵,如下图所示。

broken image

将得到的解heuristicX传递给Gurobi,使之成为精确算法的初始解。Warm start具体实现代码如下。

broken image
broken image

(2)启发式解作为上界

将启发式算法得到的解对应值记为heuristicObj,并直接赋给Gurobi作为Cutoff(可理解为上界)。或采用增加约束的方法,规定目标函数必须小于heuristicObj的值。关于Cutoff函数的使用请参考Gurobi官方帮助文档,附网址链接:Cutoff - Gurobi Optimization

broken image

采用以上2种方式求解Berlin52算例,结果分析见下表。

broken image

综上,启发式算法的解在增强求解器性能上存在一定的概率性,需结合现实情况按需使用。

 

5.Python环境下的call back机制

对比st70、pr76和a280算例分别在MATLAB和Python环境下使用惰性约束的求解时间,显而易见,Python的效率远高于MATLAB。

broken image

截至目前,Python与Gurobi的联用能在最短时间内完成惰性约束的添加和模型的求解,这得益于GurobiAPI的call back(回调)功能,而该机制在MATLAB中无法使用。

broken image

因Python环境下惰性约束的实现相对较复杂,受限于篇幅,本文在此不做详尽展开。感兴趣的读者可参阅以下网站:tsp (gurobi.github.io)Customization through callbacks - Gurobi Optimization --- 通过回调进行自定义 - Gurobi 优化运筹学修炼日记:TSP中两种不同消除子环路的方法及callback实现(Python调用Gurobi求解)_刘兴禄的博客-CSDN博客
下面仅以部分代码介绍Gurobi的Python版本最具代表性的call back机制。

broken image
broken image

上图红色框线标注部分可视作Python与Gurobi的“交互”,计算过程中一旦达到该条件便中断,在添加完惰性约束后继续计算。MATLAB则不具备此功能,只有在一次完整的运算结束后才能对当前解添加惰性约束,并通过多次check和optimize持续优化模型。相反,如图中白色框线标注部分所示,在Python中应用call back机制后,Gurobi仅需optimize一次,这亦是提速的核心所在。

 

五、小结

本文是对使用精确算法(特别是借助商业求解器,如Gurobi)求解TSP问题的进一步拓展。在指出原始TSP模型存在的问题和不足后,依次介绍了向量化、惰性约束、精简决策变量、与启发式解相结合以及call back机制等加速建模和求解TSP问题的高阶方法。希望各位读者在日后科研中遇到类似问题时,能从本文获得些许灵感和启发。

 

【囿于作者的学术水平,本文的诸多表述或许不够严谨,用途仅限于优化算法入门者的非正式学术交流,不足之处还望各位读者指正。】

 

附录 Yalmip+Gurobi参数设置官方文档网址:

 

作者:宋博文

校对:陈慧敏