4.2 MIP模型
我们首先对上述问题进行建模。引入下面两组决策变量:
● y :储蓄账户中要储蓄的金额;
● xi :购买第 i 个基金的金额。
则该问题可以建模为下面的MIP模型:

这里,我们直接可以调用Gurobi求解上述模型,具体代码如下:
from gurobipy import *
model = Model('Benders decomposition')
""" create decision variables """
y = model.addVar(lb=0, ub=1000, vtype=GRB.INTEGER, name='y')
x = {}
for i in range(10):
x = model.addVar(lb=0, ub=100, vtype=GRB.CONTINUOUS, name='x_' + str(i))
""" set objective function """
obj = LinExpr()
obj.addTerms(1.045, y)
for i in range(10):
obj.addTerms(1 + 0.01 * (i + 1), x)
model.setObjective(obj, GRB.MAXIMIZE)
""" add constraints """
lhs = LinExpr()
lhs.addTerms(1, y)
for i in range(10):
lhs.addTerms(1, x)
model.addConstr(lhs <= 1000, name='budget')
model.optimize()
print('\n\n\n')
print('Obj:', model.ObjVal)
print('Saving account : ', y.x)
for i in range(10):
if(x.x > 0):
print('Fund ID {}: amount: {}'.format(i+1, x.x))
求解结果如下:
Obj: 1063.0
Saving account : 400.0
Fund ID 5: amount: 100.0
Fund ID 6: amount: 100.0
Fund ID 7: amount: 100.0
Fund ID 8: amount: 100.0
Fund ID 9: amount: 100.0
Fund ID 10: amount: 100.0
结果与上面介绍的相同。小王应该分别购买100元的 F5,F6,F7,⋯,F10 ,剩下的400元存入储蓄账户,最终的本息合计为 1063.0 。
4.3 代码实现:逐步迭代版本ecomposition分解求解小例子:模型分解
虽然上面的模型个非常简单,但是我们还是用Benders Decomposition分解算法来求解一遍上面的模型。由于 y 是整数,而 x 是小数。所以可以将 y 的部分作为主问题,将 x 的部分作为子问题。
因此我们将主问题和子问题分解为下面的形式
主问题

写成具体形式即为:

子问题

写成具体形式即为:

注意,这里 B=(1,0,0,0,0,0,0,0,0,0,0)T
这里我们将主问题稍作变化,我们用 z 表示主问题的目标函数,即 z=fy+q
因此,主问题可以写为

这个形式和上面的形式是等价的。只是做了一个等价替换而已。
此外,为了对比上面说的两种等价处理方法,我们把子问题的对偶, Dual SP 也写出来。

具体形式为

这里,我参考的视频https://www.youtube.com/watch?app=desktop&v=vQzpydNOWDY中有错误,我进行了修正。
- Ray (Jian) Zhang. Learn Optimization Visually: Benders Decomposition. url: https://www.youtube.com/watch?
4.4 代码实现:逐步迭代版本
初始化全局的界限 LB=0 , UB=+∞ 。
**主问题(Step 0)**:

这里 y 可以给任意一个正值。比如给 y¯=1500 。当然,主问题的代码如下
from gurobipy import *
MP = Model('Benders decomposition-MP')
""" create decision variables """
y = MP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name='y')
z = MP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name='z')
MP.setObjective(z, GRB.MAXIMIZE)
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.optimize()
print('\n\n\n')
print('Obj:', MP.ObjVal)
print('z = %4.1f' % (z.x))
print('y = %4.1f' % (y.x))
第1步
**对偶子问题(Step 1)**

我们可以用求解器求解 Dual SP1 ,
from gurobipy import *
y_bar = 1500
Dual_SP = Model('Dual SP')
""" create decision variables """
alpha_0 = Dual_SP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.CONTINUOUS, name='alpha_0')
alpha = {}
for i in range(10):
alpha = Dual_SP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.CONTINUOUS, name='alpha_' + str(i))
""" create objective """
obj = LinExpr()
obj.addTerms(1000-y_bar, alpha_0)
for i in range(10):
obj.addTerms(100, alpha)
Dual_SP.setObjective(obj, GRB.MINIMIZE)
""" add constraints 1-10 """
for i in range(10):
Dual_SP.addConstr(alpha_0 + alpha >= 1 + 0.01 * (i+1))
Dual_SP.optimize()
print('\n\n\n')
print('Model status:', Dual_SP.status)
if(Dual_SP.status == 2):
print('Obj:', Dual_SP.ObjVal)
for i in range(10):
if(alpha.x > 0):
print('{} = {}'.format(alpha.varName, alpha.x))
求解结果为
Infeasible or unbounded model
Model status: 4
此时,我们需要得到模型的极射线。
Gurobi可以很容易地得到极射线,需要使用变量的UnbdRay属性。
手册中是这样描述的:

因此,我们打开InfUnbdInfo,获得极射线。
Dual_SP.setParam('InfUnbdInfo', 1)
Dual_SP.optimize()
print('\n\n\n')
print('Model status:', Dual_SP.status)
if(Dual_SP.status == 2):
print('Obj:', Dual_SP.ObjVal)
for i in range(10):
if(alpha.x > 0):
print('{} = {}'.format(alpha.varName, alpha.x))
else:
print('===== Infeasible or Unbounded information =====')
print('extreme ray: {} = {}'.format(alpha_0.varName, alpha_0.UnbdRay))
for i in range(10):
print('extreme ray: {} = {}'.format(alpha.varName, alpha.UnbdRay))
结果如下
Model status: 5
===== Infeasible or Unbounded information =====
extreme ray: pi = 1.0
extreme ray: alpha_0 = 0.0
extreme ray: alpha_1 = 0.0
extreme ray: alpha_2 = 0.0
extreme ray: alpha_3 = 0.0
extreme ray: alpha_4 = 0.0
extreme ray: alpha_5 = 0.0
extreme ray: alpha_6 = 0.0
extreme ray: alpha_7 = 0.0
extreme ray: alpha_8 = 0.0
extreme ray: alpha_9 = 0.0
我们得到了极射线:
extreme ray:(α0,α)=(1,0,0,0,0,0,0,0,0,0,0)
于是,可以构造一个Benders feasibility cut
(αri)T(b−By)⩾0
由于
B=(1,0,0,0,0,0,0,0,0,0,0)T
b=(1000,100,100,100,100,100,100,100,100,100,100)T
即Benders feasibility cut为
1000−y⩾0
将Benders feasibility cut加入MP,更新MP。
**主问题(step 1)**:

求解 MP1 。
from gurobipy import *
MP = Model('Benders decomposition-MP')
""" create decision variables """
y = MP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name='y')
z = MP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.INTEGER, name='z')
MP.setObjective(z, GRB.MAXIMIZE)
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.optimize()
print('\n\n\n')
print('Obj:', MP.ObjVal)
print('z = %4.1f' % (z.x))
print('y = %4.1f' % (y.x))
结果为
Obj: 1e+30
z = 1000000000000000019884624838656.0
y = 1000
更新全局的 UB : UB=z=+∞.
第2步
y¯=1000 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 2)**:

求解 Dual SP2 ,
y_bar = 1000
结果为
Model status: 2
Obj: 0.0
alpha_0 = 1.1
alpha_0 = 0.0
alpha_1 = 0.0
alpha_2 = 0.0
alpha_3 = 0.0
alpha_4 = 0.0
alpha_5 = 0.0
alpha_6 = 0.0
alpha_7 = 0.0
alpha_8 = 0.0
alpha_9 = 0.0
我们得到了极点:
extreme point:(α0,α)=(1.1,0,0,0,0,0,0,0,0,0,0)
更新全局的 LB : LB=max{LB,fy+Q(y)}=max{0,1.045×1000+0}=1045.
因为 Dual SP2 有有界可行解,因此可以构造Benders optimality cut。
fy+(αpi)T(b−By)⩾z
即
1.045y+1.1(1000−1⋅y)⩾z→1100−0.055y⩾z
将Benders optimality cut加入MP,更新MP。
**主问题(Step 2)**:

求解 MP2 。
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
结果为
Obj: 1100.0
z = 1100.0
y = -0.0
更新全局的 UB : UB=z=1100
此时,Gap为:

第3步
根据上文,此时 y¯=0 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 3)**:

求解 Dual SP3 ,
y_bar = 0
结果为
Model status: 2
Obj: 1055.0
alpha_0 = 0.0
alpha_0 = 1.01
alpha_1 = 1.02
alpha_2 = 1.03
alpha_3 = 1.04
alpha_4 = 1.05
alpha_5 = 1.06
alpha_6 = 1.07
alpha_7 = 1.08
alpha_8 = 1.09
alpha_9 = 1.1
我们得到了极点:
extreme point:(α0,α)=(0,1.01,1.02,1.03,1.04,1.05,1.06,1.07,1.08,1.09,1.10)
Dual SP3 有有界可行解。
更新全局的 LB :
LB=max{LB,fy+Q(y)}
=max{1045,0×1000+1055}=1055.
可以构造Benders optimality cut:
fy+(αpi)T(b−By)⩾z
即
1055+1.045y⩾z
将Benders optimality cut加入MP,更新MP。
**主问题(Step 3)**:

求解 MP3 .
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
结果为
Obj: 1097.0
z = 1097.0
y = 41.0
更新全局的 UB : UB=z=1097
此时,Gap为:

第4步
根据上文,此时 y¯=41 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 4)**:

求解 Dual SP4 ,
y_bar = 0
结果为
Model status: 2
Obj: 1013.59
alpha_0 = 1.01
alpha_0 = 0.0
alpha_1 = 0.010000000000000009
alpha_2 = 0.020000000000000018
alpha_3 = 0.030000000000000027
alpha_4 = 0.040000000000000036
alpha_5 = 0.050000000000000044
alpha_6 = 0.06000000000000005
alpha_7 = 0.07000000000000006
alpha_8 = 0.08000000000000007
alpha_9 = 0.09000000000000008
我们得到了极点:
extreme point:(α0,α)=(1.01,0,0.01,0.02,0.03,0.04,0.05,0.06,0.07,0.08,0.09)
Dual SP4 有有界可行解。
更新全局的 LB :
LB=max{LB,fy+Q(y)}
=max{1055,1.045×41+1013.59}=1056.435.
可以构造Benders optimality cut:
fy+(αpi)T(b−By)⩾z
即
1055+0.035y⩾z
将Benders optimality cut加入MP,更新MP。
**主问题(Step 4)**:

求解 MP4 .
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
MP.addConstr(1055 + 0.035 * y >= z, name='benders optimality cut iter 4')
结果为
Obj: 1072.0
z = 1072.0
y = 500.0
更新全局的 UB :
UB=z=1072
此时,Gap为:

第5步
根据上文,此时 y¯=500 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 5)**:

求解 Dual SP5 ,
y_bar = 500
结果为
Model status: 2
Obj: 540.0
alpha_0 = 1.05
alpha_0 = 0.0
alpha_1 = 0.0
alpha_2 = 0.0
alpha_3 = 0.0
alpha_4 = 0.0
alpha_5 = 0.010000000000000009
alpha_6 = 0.020000000000000018
alpha_7 = 0.030000000000000027
alpha_8 = 0.040000000000000036
alpha_9 = 0.050000000000000044
我们得到了极点:
extreme point:(α0,α)=(1.05,0,0,0,0,0,0.01,0.02,0.03,0.04,0.05)
Dual SP5 有有界可行解。
更新全局的 LB :
LB=max{LB,fy+Q(y)}
=max{1056.435,1.045×500+540}=1062.5.
可以构造Benders optimality cut:
fy+(αpi)T(b−By)⩾z
即
1065−0.005y⩾z
将Benders optimality cut加入MP,更新MP。
**主问题(Step 5)**:

求解 MP5 .
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
MP.addConstr(1055 + 0.035 * y >= z, name='benders optimality cut iter 4')
MP.addConstr(1065 - 0.005 * y >= z, name='benders optimality cut iter 5')
结果为
Obj: 1063.0
z = 1063.0
y = 250.0
更新全局的 UB :
UB=z=1063
此时,Gap为:

第6步
根据上文,此时 y¯=250 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 6)**:

求解 Dual SP6 ,
y_bar = 250
结果为
Obj: 800.5
alpha_0 = 1.03
alpha_0 = 0.0
alpha_1 = 0.0
alpha_2 = 0.0
alpha_3 = 0.010000000000000009
alpha_4 = 0.020000000000000018
alpha_5 = 0.030000000000000027
alpha_6 = 0.040000000000000036
alpha_7 = 0.050000000000000044
alpha_8 = 0.06000000000000005
alpha_9 = 0.07000000000000006
我们得到了极点:
extreme point:(α0,α)=(1.03,0,0,0,0.01,0.02,0.03,0.04,0.05,0.06,0.07)
Dual SP6 有有界可行解。
更新全局的 LB :
LB=max{LB,fy+Q(y)}
=max{1062.5,1.045×250+800.5}
=max{1062.5,1061.75}=1062.5.
可以构造Benders optimality cut:
fy+(αpi)T(b−By)⩾z
即
1058+0.015y⩾z
将Benders optimality cut加入MP,更新MP。
**主问题(Step 6)**:

求解 MP6 .
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
MP.addConstr(1055 + 0.035 * y >= z, name='benders optimality cut iter 4')
MP.addConstr(1065 - 0.005 * y >= z, name='benders optimality cut iter 5')
MP.addConstr(1058 + 0.015 * y >= z, name='benders optimality cut iter 6')
结果为
Obj: 1063.0
z = 1063.0
y = 350.0
更新全局的 UB :
UB=z=1063
此时,Gap为:

第7步
根据上文,此时 y¯=350 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 7)**:

求解 Dual SP7 ,
y_bar = 350
结果为
Model status: 2
Obj: 697.0
alpha_0 = 1.04
alpha_0 = 0.0
alpha_1 = 0.0
alpha_2 = 0.0
alpha_3 = 0.0
alpha_4 = 0.010000000000000009
alpha_5 = 0.020000000000000018
alpha_6 = 0.030000000000000027
alpha_7 = 0.040000000000000036
alpha_8 = 0.050000000000000044
alpha_9 = 0.06000000000000005
我们得到了极点:
extreme point:(α0,α)=(1.04,0,0,0,0,0.01,0.02,0.03,0.04,0.05,0.06)
Dual SP7 有有界可行解。
更新全局的 LB :
LB=max{LB,fy+Q(y)}
=max{1062.5,1.045×350+697}
=max{1062.5,1062.75}=1062.75.
可以构造Benders optimality cut:
fy+(αpi)T(b−By)⩾z
即
1061+0.005y⩾z
将Benders optimality cut加入MP,更新MP。
**主问题(Step 7)**:

求解 MP7 .
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
MP.addConstr(1055 + 0.035 * y >= z, name='benders optimality cut iter 4')
MP.addConstr(1065 - 0.005 * y >= z, name='benders optimality cut iter 5')
MP.addConstr(1058 + 0.015 * y >= z, name='benders optimality cut iter 6')
MP.addConstr(1061 + 0.005 * y >= z, name='benders optimality cut iter 7')
结果为
Obj: 1063.0
z = 1063.0
y = 400.0
更新全局的 UB :
UB=z=1063
此时,Gap为:

第8步
根据上文,此时 y¯=400 ,更新对偶子问题的目标函数如下:
**对偶子问题(Step 8)**:

求解 Dual SP8 ,
y_bar = 400
结果为
Obj: 645.0
alpha_0 = 1.04
alpha_0 = 0.0
alpha_1 = 0.0
alpha_2 = 0.0
alpha_3 = 0.0
alpha_4 = 0.010000000000000009
alpha_5 = 0.020000000000000018
alpha_6 = 0.030000000000000027
alpha_7 = 0.040000000000000036
alpha_8 = 0.050000000000000044
alpha_9 = 0.06000000000000005
我们得到了极点:
extreme point:(α0,α)=(1.04,0,0,0,0,0.01,0.02,0.03,0.04,0.05,0.06)
Dual SP8 有有界可行解。
更新全局的 LB :
LB=max{LB,fy+Q(y)}
=max{1062.75,1.045×400+645}
=max{1062.75,1063}=1063.
此时 UB 和 LB 相遇了, Gap=0 。
我们从 Dual SP 的约束的对变量即可获得 x 的取值,代码如下:
print('\n\n\n ==== Solution of SP primal ===== ')
cnt = 1
for con in Dual_SP.getConstrs():
print('x {} = {}'.format(cnt, con.Pi))
cnt += 1
==== Solution of SP primal =====
x 1 = 0.0
x 2 = 0.0
x 3 = 0.0
x 4 = 0.0
x 5 = 100.0
x 6 = 100.0
x 7 = 100.0
x 8 = 100.0
x 9 = 100.0
x 10 = 100.0
因此得到了最优解。最优解为
Obj = 1063
y = 400
x_5 = 100
x_6 = 100
x_7 = 100
x_8 = 100
x_9 = 100
x_10 = 100
到这里,这个小例子的逐步迭代我们就展示完成了。可以看到,我们是对子问题进行了对偶,采用了 Dual SP 。
仅从算法实现的角度,实际上我们也可以不对偶子问题,直接采用子问题本身,即 SP ,来实现Benders Decomposition。只需要使用 Constr.Pi 获得约束的对偶变量,然后拼接出 Cuts 即可。因此并没有必要对子问题进行对偶。
但是从理论推导的角度,还是需要进行对偶的。
4.5 Benders Decomposition过程的Bounds更新
根据上述解释,我们得到,在Benders Decomposition的过程中, UB 和 LB 按照下面的表达式进行更新: LB=max{LB,fy+Q(y)},UB=min{UB,fy+αT(b−By¯)}.
我们将上述例子的上下界更新的过程可视化出来,方便读者查看。

4.6 Benders Decomposition的深入理解
我们前面讲到:
● MP 删除了所有关于 x 的约束,相当于抛弃了 x 的影响。但是实际上 x 一定会影响 MP 。我们通过一种先试错,再补救的过程,先删除所有的 x 的相关部分,构成初始的 MP ,通过求解 SP ,获得一些信息,这些信息反映了 x 对 y 的影响,我们通过某种方式,将 x 对 y 的影响加回到 MP 中,进行补救。通过整个过程的迭代,我们最终就可以达到求解原MIP的目的。其中,补救的步骤,实际上专业术语叫做recourse,反应在具体形式上,就是以cutting plane的形式,将补救措施加回到 MP 中。
我们来可视化一下上述例子中,Benders Decomposition在迭代的过程中,recourse是如何以cutting plane的形式补救回来的。
我们只需要画出主问题的可行域的变化即可。
第1步迭代

第2步迭代

第3步迭代

第4步迭代

第5步迭代

第6步迭代

第7步迭代

从上述过程可以看到,每一步子问题都可以提供一个 z 的更紧的上界,一般形式即为 fy+(αpi)T(b−By)⩾z
本问题的具体形式即为:
1000−y⩾0 (Cut 1: feasibility cut)
1100−0.055y⩾z (Cut 2: optimality cut)
1055+1.045y⩾z (Cut 3: optimality cut)
1055+0.035y⩾z (Cut 4: optimality cut)
1065−0.005y⩾z (Cut 5: optimality cut)
1058+0.015y⩾z (Cut 6: optimality cut)
1061+0.005y⩾z (Cut 7: optimality cut)
这些界限,或者说Cuts,就像是对 z 的秋后算账。每一步迭代,都把 z 切一刀,拍一巴掌。相当于把前面忽略掉的 x 的影响,以cutting plane的形式加回来。也就相当于recourse。直到最后的Bound收敛为止。
实际上,如果穷举了所有可能的 y 的取值对应的 dual SP 的极点和极射线的集合 P 和 R ,构造相应的Cuts,并将其全部加入 MP ,则可以直接得到原问题MIP的等价形式。但是这样是非常低效以及不可处理的。因为 P 和 R 的规模是指数级增长的。
虽然 P 和 R 的规模是指数级增长的,但是最后在最优解中,真正binding的却只是一部分。如果能够找到最有可能binding的那部分极点和极射线,这样就可以大大提高求解效率,并且能够保证全局最优性。
Benders Decomposition就是通过迭代的方式,向子问题提供固定了的 y ,并通过求解子问题的对偶问题(或者子问题本身),从而找到相应的极点和极射线。这些找到的极点和极射线,就是非常有可能binding的极点和极射线。
我们通过求解最终的 MP7 ,获得约束的对偶变量,来查看一下,究竟最后是哪些Benders feasibility cuts和Benders optimality cuts是binding的。
注意,由于MIP是没有dual的,因此我们需要将MP改成线性规划。也就是将 y 设置成连续变量求解。本问题比较特殊,松弛了整数规划,解释不变的。我这么做纯属是为了查看binding的情况,不代表真实的研究可以这么操作,望读者周知。
判定约束是否 binding ,只需要获得约束的对偶变量,查看其是否为0。如果对偶不为0,说明其 binding,如果为0,实际上是可以认为是非binding的,但是这个不是一定的。我这里就认为是0就为非 binding。
具体代码如下:
from gurobipy import *
MP = Model('Benders decomposition-MP')
""" create decision variables """
y = MP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.CONTINUOUS, name='y')
z = MP.addVar(lb=0, ub=GRB.INFINITY, vtype=GRB.CONTINUOUS, name='z')
MP.setObjective(z, GRB.MAXIMIZE)
MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
MP.addConstr(1055 + 0.035 * y >= z, name='benders optimality cut iter 4')
MP.addConstr(1065 - 0.005 * y >= z, name='benders optimality cut iter 5')
MP.addConstr(1058 + 0.015 * y >= z, name='benders optimality cut iter 6')
MP.addConstr(1061 + 0.005 * y >= z, name='benders optimality cut iter 7')
MP.optimize()
print('\n\n\n')
print('Obj:', MP.ObjVal)
print('z = %4.1f' % (z.x))
print('y = %4.1f' % (y.x))
print('\n\n\n ******* Binding information ******* ')
for con in MP.getConstrs():
con_dual = con.Pi
if(abs(con_dual) > 0):
print("cons name: %30s | binding istatus: Yes | Dual: %4.2f" % (con.ConstrName, con.Pi))
else:
print("cons name: %30s | binding istatus: No | Dual: %4.2f" % (con.ConstrName, con.Pi))
结果如下:
Obj: 1063.0
z = 1063.0
y = 400.0
******* Binding information *******
cons name: benders feasibility cut iter 1 | binding istatus: No | Dual: 0.00
cons name: benders optimality cut iter 2 | binding istatus: No | Dual: 0.00
cons name: benders optimality cut iter 3 | binding istatus: No | Dual: 0.00
cons name: benders optimality cut iter 4 | binding istatus: No | Dual: 0.00
cons name: benders optimality cut iter 5 | binding istatus: Yes | Dual: -0.50
cons name: benders optimality cut iter 6 | binding istatus: No | Dual: 0.00
cons name: benders optimality cut iter 7 | binding istatus: Yes | Dual: -0.50
根据上面的信息,可得:Cut 5和Cut 7是binding的,其余的都没有binding.
也就是Cuts

提供了非常紧凑的bound,在最优解中处于binding状态。
其余的Cuts (也就是下面的Cuts)

都是冗余的,是中间迭代过程中产生的暂时性有帮助的Cuts。是我们奔向最优解的垫脚石。最后真正的MVP属于Cut 5和Cut 7.
我们可以验证一下:
# MP.addConstr(1000 - y >= 0, name='benders feasibility cut iter 1')
# MP.addConstr(1100 - 0.055 * y >= z, name='benders optimality cut iter 2')
# MP.addConstr(1055 + 1.045 * y >= z, name='benders optimality cut iter 3')
# MP.addConstr(1055 + 0.035 * y >= z, name='benders optimality cut iter 4')
MP.addConstr(1065 - 0.005 * y >= z, name='benders optimality cut iter 5')
# MP.addConstr(1058 + 0.015 * y >= z, name='benders optimality cut iter 6')
MP.addConstr(1061 + 0.005 * y >= z, name='benders optimality cut iter 7')
发现解为:
Obj: 1063.0
z = 1063.0
y = 400.0
也就是说,如果我们找的足够准,直接两步就可以找到Cut 5和Cut 7,那我们2步就可以收敛。
我们只画出Cut 5和Cut 7,如下

小结
本文首先综述了求解MIP的常用算法的主要思想以及适用场景。然后简要介绍了Benders Decomposition的原理,并附以直观易懂的解释。
此外,我们以Youtube上一个视频教程的例子为例,详细的展示了Benders Decomposition的迭代过程。并且我们用Python调用Gurobi,实现了整个过程,并提供了非常详尽的解释。
最后我们从偏理论的角度,通过可视化的手段,将这个过程进行了完整的回顾,并给出了Benders Decomposition的较为深入的理解。
本文的介绍是Benders Decomposition算法的全套学习资料,非常适合想要从理论、公式推导、代码实现3个方面达到全方位入门Benders Decomposition的小伙伴们学习钻研。
参考文献
1. Ray (Jian) Zhang. Learn Optimization Visually: Benders Decomposition. url: https://www.youtube.com/watch?app=desktop&v=vQzpydNOWDY
2. Zeki Caner Taşkin. Benders Decomposition. American Cancer Society, 2011. ISBN 9780470400531.
3. 运筹优化常用模型、算法与案例实战——Python+Java实现. 刘兴禄, 熊望祺, 臧永森, 段宏达, 曾文佳, 陈伟坚. 清华大学出版社(即将出版)
本文转载自知乎博主:运小筹