本文的内容:
什么是整数规划,它与线性规划有什么差异,它的常用解法有哪些
分支定界法的算法框架与代码框架
本文的轮子能/不能支持的整数规划:
支持最大化或最小化,无需手动化为最大化
支持带不等号的约束,无需手动化为标准型
不支持变量为负整数,需通过线性变换手动化为非负整数
本文需要的前提知识:
了解可行域的概念
了解线性规划较易求解这个事实
整数规划(Integer Programming,IP)是指变量为整数的规划问题,如果约束条件和目标函数为线性表达式,那么就为整数线性规划(ILP),本文的整数规划特指整数线性规划。整数规划IP的数学模型为:
再回忆下线性规划LP的数学模型:
IP与LP在模型上的区别就只有一个:变量x为离散的整数还是为连续的实数。可见,IP与LP还是有千丝万缕的联系,这种联系也促成了IP的解法。
一个实际的问题是:为什么我们需要让变量为离散的整数呢?这是因为实际的规划问题经常要求决策变量为整数,如买多少粒大白兔奶糖、买多少瓶可口可乐等等,这都要求结果是整数。
另外一个实际的问题是:我们该怎样求解整数规划问题呢?毕竟让变量不偏不倚地成为一个整数显然是困难的。对此,我们需要使用一些常用的分析思路:
①分析可行域,问题的难点和优化思路一般围绕可行域展开。整数规划的难点就在于它的可行域是离散的,下图中一个个的黑点就表示一个个的可行解。通常来说,离散的可行域比连续的可行域要难分析许多。
②既然问题的难点在于离散的可行域,那么化难为易的关键就在于找到一个新问题,它的可行域是连续的,使得其容易求解;并且其最优解正好是整数规划的最优解,使得只计算新问题就可以得到整数规划的最优解。
③一般而言,要得到既容易求解,结果又与整数规划最优解相同的新问题,需要不断迭代地产生松弛问题。所谓松弛问题,是指可行域更大、因而更易求解的问题;所谓不断迭代,是指根据一个松弛问题的求解结果,来决定下一个松弛问题的形式。
④对于整数规划而言,松弛问题通常使用不考虑整数约束后得到的线性规划,比如说IP模型为:
那么松弛问题就是LP模型:
该松弛问题的可行域显然比原问题的可行域更大,符合松弛问题的基本条件;并且由于是连续的可行域,容易求解,也符合松弛问题存在的意义。
⑤对于整数规划而言,迭代产生松弛问题的方法主要有割平面法与分支定界法,二者的思路都是在松弛问题的基础上再加入新的约束,在不失去整数解的情况下,逐步缩小可行域。
关于IP的解法,包括但不限于:
割平面法:不断地在同一个松弛问题上添加约束,直到最终的松弛问题能给出整数解为止。
Pros:一直只有一个松弛问题,内存占用较小。
Cons:以Gomory割平面法为例,其实际的收敛速度较慢;不为多项式时间算法。
分支定界法:在一个松弛问题上,通过添加约束分支出两个新的松弛问题,以此类推,从而生成分支树,直到分支出的松弛问题能给出最优整数解为止。分支树如下图所示:(同时也会通过定界的方式来避免穷举庞大的树结构)
Pros:实际的收敛速度较快
Cons:由于要同时记录多个松弛问题,内存占用较大;不为多项式时间算法
分支定界法使用了经典的优化思想:如果一个优化问题约束复杂(如变量为整数),那么就忽略该约束、扩大可行域来生成松弛问题,然后对松弛问题不断迭代,直到最终的松弛问题给出了原问题的最优解为止。一个合格的松弛问题,必须做到可行域更大且容易解决;一个合格的迭代方法,必须做到不遗漏原问题可行域上的解。
以最大化问题为例,分支定界法(Branch and Bound,B&B)的大体框架为:
用下图的整数规划为例介绍分支定界法:
①先不考虑变量x1,x2为整数的约束,从而得到一个初始松弛问题z,如下图所示。我们可以轻易地解决如下这个线性规划问题,得到的解为x1=1/3,x2=1/3,z=2/3。
②从不为整数的变量中随便选一个(统一起见,就选择下标最小的变量),本例中选择x1,之后按如下规则生成两个与x1相关的约束(这就是分支):
将这两个约束化简后分别加入到原来的松弛问题z中,就得到了两个新的松弛问题z1和z2:
同时,原来的松弛问题z就不再考虑,直接删除。这样做能让松弛问题z1或z2更接近整数最优解的理由是:分开考虑x1<=0和x1>=1两种情况,没有让x1取整数的范围变小,从而不可能因此丢失掉任何整数解;同时,由于在原来的松弛问题z上加入了新约束,可行域会变小,因而更可能解出整数最优解。(搜索范围越小,找到目标的可能性就越大)
③现在还剩下z1,z2两个松弛问题,从中随便选一个先求解,本例中选择目标函数为z1的松弛问题求解。可以轻易地解决这个线性规划问题,得到的解为x1=0;x2=0.5,z1=0.5。得到的解依然不是整数解,故执行第②步,再次得到两个新的松弛问题z11和z12,并删除原来的松弛问题z1:
④现在还剩下z2,z11,z12三个松弛问题,从中随便选一个先求解,本例选择目标函数为z11的进行求解,得到的解为x1=0,x2=0,z11=0。终于得到了第一个整数解,我们将整数解的目标函数值z11记录下来,作为LowerBound(LB),并直接删除z11松弛问题,不再进行分支。
⑤现在还剩下z2,z12两个松弛问题,从中随便选一个先求解,本例选择目标函数为z2的进行求解,得到的是不可行解,即比下界LB要差,故直接删除z2松弛问题,不再进行分支。(这就是定界,作用是直接删除不可能得到更优整数解的分支,从而避免穷举所有分支)
⑥现在还剩下z12一个松弛问题,对z12求解, 得到的是不可行解,即比下界LB要差,故直接删除z12松弛问题,不再进行分支。
⑦现在没有任何松弛问题存留,故得到的下界LB就是整数规划的最优值。对应的解x1=0,x2=0就是整数规划的最优解。
Part3:分支定界法的算法框架上述内容更偏向于介绍分支定界的思路,从而忽略了完整性,实际上,分支定界理论还涉及到上界UpperBound(UB)的实现,松弛问题的求解顺序,这对算法的求解时间会有很大影响,这些内容都可在教材中找到。本文的代码框架为:
class MipSolver():
def standTran():
# 将最小化问题变成最大化问题
def initialize ():
# 得到初始的松弛问题列表Nodes,数据结构:列表
# 得到初始的下界LB=负无穷
# 得到初始的上界UB=正无穷
initialize Nodes,LB,UB
def solver():
# 不断进行分支、定界工作
# 直到松弛问题列表为空或者上下界的百分比差距小于gap(人为设定的值)
while Nodes and (UB-LB)/LB # node.preValue表示产生松弛问题node的原问题的最优值
UB=max([node.preValue for node in Nodes])
node=Nodes.pop() # 随便选一个松弛问题node
value,solution=simplex(node) # 使用单纯形法快速求解
if value# 若松弛问题node的最优值低于下界,就不再分支,这就是定界continue elif solutin is integer: # 如果松弛问题node的最优解为整数,则更新下界
LB=valueelse: # 如果松弛问题node的最优解不为整数,则进行分支
newNode1,newNode2=branch(node)
Nodes+=[newNode1,newNode2]def main():# 主流程
standTran()
initialize ()
solver()
用以下整数规划的例子测试:
整数规划轮子的结果为:
商业求解器Lingo的结果为:
求解结果没啥问题。关于此次整数规划的轮子,个人最大的收获是忽略变量为整数的条件,从而提出易解决的松弛问题;并通过不断加约束的方式来让松弛问题的解逐步靠近整数最优解,最终松弛问题的解就是整数规划最优解。这种“松弛思想”可以适用于许多其他方面,如求复杂函数或复杂规划的上下界;除了本文涉及的松弛方法,还有其他的松弛方法,也挺有意思的。
代码地址:https://github.com/aitexia/ORtools/tree/master/mip
-End-
小小Qiu