C&CG算法求解两阶段鲁棒优化问题:推导与AMPL/python代码

C&CG算法求解两阶段鲁棒优化问题:推导与AMPL代码

  • Reference
  • 算例介绍
    • 参数
    • 变量
      • 主要变量
      • 对偶变量
      • 辅助变量
    • 不确定集(uncertainty set)
    • 数据
  • 模型构建
    • 目标函数
    • 约束条件
      • 第一阶段约束
      • 第二阶段约束
    • 推导KKT条件
  • 最终主子问题模型
    • 主问题(即第一阶段)
    • 子问题(即第二阶段)
  • 总结
    • python代码

Reference

本文介绍的内容,均参考自Zeng Bo 于2013 年在Operations Research Letters 上发表的论文,引用信息如下:
Bo Zeng, Long Zhao,Solving two-stage robust optimization problems using a column-and-constraint generation method,Operations Research Letters,Volume 41, Issue 5,2013,Pages 457-461,ISSN0167-6377,https://doi.org/10.1016/j.orl.2013.05.003.
原文链接如下:
(https://www.sciencedirect.com/science/article/pii/S0167637713000618)

此外,零基础细致讲解请参阅:鲁棒优化| C&CG算法求解两阶段鲁棒优化:全网最完整、最详细的【入门-完整推导-代码实现】笔记 - 运小筹的文章 - 知乎
https://zhuanlan.zhihu.com/p/534285185

本文只对算例进行说明。

算例介绍

文中的算例是一个location-transportation问题。
即:货物存放在3个仓库(SUPPLY)中,有3个客户(DEMAND)。现在需要通过优化方法来安排货物的配送情况。其中第一阶段确定仓库启用情况和存储的货物量,第二阶段确定运输情况。涉及如下参数和变量:

参数

仓库的固定成本: f i f_i fi, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2
仓库的单位容量使用成本: a i a_i ai, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2
仓库最大容量: k i k_i ki, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2

客户基础需求: d ‾ j \underline{d}_j dj, \quad j = 0 , 1 , 2 j=0,1,2 j=0,1,2
客户需求的最大误差: d ~ j \tilde{d}_j d~j, \quad j = 0 , 1 , 2 j=0,1,2 j=0,1,2
货物从仓库 i i i到客户 j j j的单位运输成本: c i j c_{ij} cij, \quad i = 0 , 1 , 2 j = 0 , 1 , 2 i=0,1,2\quad j=0,1,2 i=0,1,2j=0,1,2

变量

主要变量

仓库状态变量(布尔型): y i y_i yi, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2
仓库存储的货物量: z i z_i zi, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2
货物运输计划: x i j x_{ij} xij, \quad i = 0 , 1 , 2 j = 0 , 1 , 2 i=0,1,2\quad j=0,1,2 i=0,1,2j=0,1,2

对偶变量

π i \pi_i πi, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2
θ j \theta_j θj, \quad j = 0 , 1 , 2 j=0,1,2 j=0,1,2

辅助变量

主问题目标函数的辅助变量: η \eta η
线性化过程中的辅助变量:
v i v_i vi, \quad i = 0 , 1 , 2 i=0,1,2 i=0,1,2
w j w_j wj, \quad j = 0 , 1 , 2 j=0,1,2 j=0,1,2
h i j h_{ij} hij, \quad i = 0 , 1 , 2 j = 0 , 1 , 2 i=0,1,2\quad j=0,1,2 i=0,1,2j=0,1,2

不确定集(uncertainty set)

D = { d : d j = d ‾ j + g j d ~ j , g j ∈ [ 0 , 1 ] , j = 0 , 1 , 2 ; g 0 + g 1 ≤ 1.2 , g 0 + g 1 + g 2 ≤ 1.8 } D={\{d:d_j=\underline{d}_j+g_j\tilde{d}_j},g_j\in [0,1],j=0,1,2;g_0+g_1\leq1.2,g_0+g_1+g_2\leq1.8\} D={d:dj=dj+gjd~j,gj[0,1],j=0,1,2;g0+g11.2,g0+g1+g21.8}

数据

C&CG算法求解两阶段鲁棒优化问题:推导与AMPL/python代码_第1张图片

模型构建

目标函数

min ⁡ y , z ∑ i ( f i y i + a i z i ) + max ⁡ d ∈ D min ⁡ x ∑ i ∑ j c i j x i j \min_{y,z}\sum_i{(f_iy_i+a_iz_i)}+\max_{d\in D}\min_{x}\sum_i\sum_j{c_{ij}x_{ij}} y,zmini(fiyi+aizi)+dDmaxxminijcijxij

约束条件

第一阶段约束

z i ≤ k i y i , ∀ i z_i \leq k_iy_i, \quad \forall i zikiyi,i

注意到论文中4.2节上面一段倒数第二行提到,为了保证存在可行解(即:货物量大于需求量),需要添加一条约束,这里我们采用比论文里( ∑ i k i ≥ max ⁡ { ∑ j d j : d ∈ D } \sum_{i}k_i\geq\max\{\sum_{j}d_j:d\in D\} ikimax{jdj:dD})更强的约束: ∑ i z i ≥ max ⁡ { ∑ j d j : d ∈ D } \sum_{i}z_i\geq\max\{\sum_{j}d_j:d\in D\} izimax{jdj:dD}上式不等号右侧很容易计算出具体值为: 206 + 274 + 220 + 1.8 ∗ 40 = 772 206+274+220+1.8*40=772 206+274+220+1.840=772
y i ∈ { 0 , 1 } , z i ≥ 0 ∀ i y_i\in \{0,1\},z_i\geq 0 \quad \forall i yi{0,1},zi0i

第二阶段约束

∑ j x i j ≤ z i ∀ i \sum_{j}x_{ij}\leq z_i \quad \forall i jxijzii ∑ i x i j ≥ d j ∀ j \sum_{i}x_{ij}\geq d_j \quad \forall j ixijdjj x i j ≥ 0 , ∀ i , j x_{ij}\geq 0, \quad \forall i,j xij0,i,j

推导KKT条件

由于第二阶段目标函数是一个max-min双层优化问题,所以需要使用对偶理论将内层的min问题转化为max问题,这样第二阶段目标函数就可以转化为一个单层max问题。下面使用拉格朗日对偶推导KKT条件:
1.列写内层原问题(约束写成 ≤ 0 \leq0 0的形式): min ⁡ x ∑ i ∑ j c i j x i j \min_{x}\sum_i\sum_j{c_{ij}x_{ij}} xminijcijxij s . t . s.t. s.t. ∑ j x i j − z i ≤ 0 ∀ i → π i ≥ 0 \sum_{j}x_{ij}-z_i\leq 0 \quad \forall i \quad \quad \rightarrow \pi_i \geq0 jxijzi0iπi0 ∑ i − x i j + d j ≤ 0 ∀ j → θ j ≥ 0 \sum_{i}-x_{ij}+d_j\leq 0 \quad \forall j \quad \quad \rightarrow \theta_j \geq0 ixij+dj0jθj0 − x i j ≤ 0 , ∀ i , j → λ i j ≥ 0 -x_{ij}\leq 0, \quad \forall i,j \quad \quad \rightarrow \lambda_{ij} \geq0 xij0,i,jλij0

2.写出拉格朗日函数: L = ∑ i ∑ j c i j x i j + ∑ i π i ( ∑ j x i j − z i ) + ∑ j θ j ( ∑ i − x i j + d j ) + ∑ i ∑ j λ i j ( − x i j ) L=\sum_{i}\sum_{j}{c_{ij}x_{ij}}+\sum_{i}\pi_i(\sum_{j}x_{ij}- z_i)+\sum_{j}\theta_j(\sum_{i}-x_{ij}+d_j)+\sum_{i}\sum_{j}{\lambda_{ij}(-x_{ij})} L=ijcijxij+iπi(jxijzi)+jθj(ixij+dj)+ijλij(xij)
3.根据变量合并“同类项”: L = ∑ i ∑ j ( c i j + π i − θ j − λ i j ) x i j − ∑ i π i z i + ∑ j θ j d j L=\sum_{i}\sum_{j}(c_{ij}+\pi_i-\theta_j-\lambda_{ij})x_{ij}-\sum_{i}\pi_iz_i+\sum_{j}\theta_jd_j L=ij(cij+πiθjλij)xijiπizi+jθjdj
4.求拉格朗日函数的下确界: inf ⁡ x ∑ i ∑ j ( c i j + π i − θ j − λ i j ) x i j − ∑ i π i z i + ∑ j θ j d j \inf_{x}\sum_{i}\sum_{j}(c_{ij}+\pi_i-\theta_j-\lambda_{ij})x_{ij}-\sum_{i}\pi_iz_i+\sum_{j}\theta_jd_j xinfij(cij+πiθjλij)xijiπizi+jθjdj
5.获得对偶目标函数和对偶约束:
(1)上式后面两项与原问题变量 x x x无关,所以为对偶问题的目标函数,即 max ⁡ π , θ − ∑ i π i z i + ∑ j θ j d j \max_{\pi, \theta}-\sum_{i}\pi_iz_i+\sum_{j}\theta_jd_j π,θmaxiπizi+jθjdj
(2)要想上一步4中的式子可以取得最小值,只有 c i j + π i − θ j − λ i j = 0 c_{ij}+\pi_i-\theta_j-\lambda_{ij}=0 cij+πiθjλij=0注意到 λ i j ≥ 0 \lambda_{ij} \geq 0 λij0, 所以 c i j + π i − θ j ≥ 0 c_{ij}+\pi_i-\theta_j \geq 0 cij+πiθj0

6.列写内层对偶问题: max ⁡ π , θ − ∑ i π i z i + ∑ j θ j d j \max_{\pi, \theta}-\sum_{i}\pi_iz_i+\sum_{j}\theta_jd_j π,θmaxiπizi+jθjdj s . t . s.t. s.t. c i j + π i − θ j ≥ 0 ∀ i , j c_{ij}+\pi_i-\theta_j \geq 0 \quad \forall i,j cij+πiθj0i,j π i ≥ 0 ∀ i \pi_i \geq0 \quad \forall i πi0i θ j ≥ 0 ∀ j \theta_j \geq0 \quad \forall j θj0j

可以发现,对偶问题目标函数存在连续变量乘积项,不方便使用求解器求解,我们使用论文中提到的KKT条件进行求解,这样便可规避非线性项对求解效率的影响。根据Stephen Boyd的凸优化教材《Convex Optimization》第2435.5.3节内容,我们可以轻松得到该问题的KKT条件:
①原问题可行性: ∑ j x i j ≤ z i ∀ i \sum_{j}x_{ij}\leq z_i \quad \forall i jxijzii ∑ i x i j ≥ d j ∀ j \sum_{i}x_{ij}\geq d_j \quad \forall j ixijdjj x i j ≥ 0 , ∀ i , j x_{ij}\geq 0, \quad \forall i,j xij0,i,j
②对偶问题可行性: c i j + π i − θ j ≥ 0 ∀ i , j c_{ij}+\pi_i-\theta_j \geq 0 \quad \forall i,j cij+πiθj0i,j π i ≥ 0 ∀ i \pi_i \geq0 \quad \forall i πi0i θ j ≥ 0 ∀ j \theta_j \geq0 \quad \forall j θj0j
③互补松弛条件: ( ∑ j x i j − z i ) π i = 0 (\sum_{j}x_{ij}- z_i)\pi_i=0 (jxijzi)πi=0 ( ∑ i − x i j + d j ) θ j = 0 (\sum_{i}-x_{ij}+d_j)\theta_j=0 (ixij+dj)θj=0 ( − x i j ) λ i j = 0 (-x_{ij})\lambda_{ij}=0 (xij)λij=0注意到 c i j + π i − θ j − λ i j = 0 c_{ij}+\pi_i-\theta_j-\lambda_{ij}=0 cij+πiθjλij=0代入上式,即: ( c i j + π i − θ j ) ( − x i j ) = 0 (c_{ij}+\pi_i-\theta_j)(-x_{ij})=0 (cij+πiθj)(xij)=0可以看到这3个互补松弛条件都是非线性的,所以论文里采用了式(21)所示的线性化方法(同时也应注意式(21)第二项的左边是大于等于零的,而我们推导的互补松弛条件括号内是小于等于零的,所以要加一个负号),即:
π i ≤ M v i \pi_i\leq Mv_i πiMvi z i − ∑ j x i j ≤ M ( 1 − v i ) z_i-\sum_{j}x_{ij}\leq M(1-v_i) zijxijM(1vi) θ j ≤ M w j \theta_j\leq Mw_j θjMwj ∑ i x i j − d j ≤ M ( 1 − w j ) \sum_{i}x_{ij}-d_j\leq M(1-w_j) ixijdjM(1wj) x i j ≤ M h i j x_{ij}\leq Mh_{ij} xijMhij c i j + π i − θ j ≤ M ( 1 − h i j ) c_{ij}+\pi_i-\theta_j\leq M(1-h_{ij}) cij+πiθjM(1hij)

④稳定性条件和对偶约束相同
综上,我们得到了所有KKT条件。

最终主子问题模型

先贴出论文里的伪代码,方便下面说明。
C&CG算法求解两阶段鲁棒优化问题:推导与AMPL/python代码_第2张图片

主问题(即第一阶段)

根据论文中的算法伪代码,主问题可表述为:
M P = min ⁡ y , z , η , x ∑ i ( f i y i + a i z i ) + η MP=\min_{y,z,\eta,x}\sum_i{(f_iy_i+a_iz_i)}+\eta MP=y,z,η,xmini(fiyi+aizi)+η s . t . s.t. s.t. z i ≤ k i y i , ∀ i z_i \leq k_iy_i, \quad \forall i zikiyi,i ∑ i z i ≥ 772 \sum_{i}z_i\geq772 izi772 η ∈ R \eta \in R ηR y i ∈ { 0 , 1 } , z i ≥ 0 ∀ i y_i\in \{0,1\},z_i\geq 0 \quad \forall i yi{0,1},zi0i η ≥ ∑ i ∑ j c i j x i j n ∀ n = 1 , . . . N \eta\geq\sum_{i}\sum_{j}c_{ij}{x_{ij}}^n \quad \forall n=1,...N ηijcijxijnn=1,...N ∑ j x i j n ≤ z i ∀ i , n = 1 , . . . N \sum_{j}{x_{ij}}^n\leq z_i \quad \forall i,n=1,...N jxijnzii,n=1,...N ∑ i x i j n ≥ d j ∗ n ∀ j , n = 1 , . . . N \sum_{i}{x_{ij}}^n\geq{d^\ast_j}^n \quad \forall j,n=1,...N ixijndjnj,n=1,...N最后三个约束表示的是每一次迭代生成的约束(即Constraint),其中 N N N为迭代次数, x i j n {x_{ij}}^n xijn为每次迭代生成的变量(即Column)(这也是Column-and-constraint generation (C&CG) algorithm名字的由来), d j ∗ n {d^\ast_j}^n djn为子问题的最优解(相当于论文中伪代码里的 u k + 1 ∗ u^\ast_{k+1} uk+1),传递到主问题中被当做参数。

子问题(即第二阶段)

根据前文KKT条件的推导结果,以及不确定集的定义,我们可以得到如下子问题:
S P = max ⁡ d , x , g , θ , π , v , w , h ∑ i ∑ j c i j x i j SP=\max_{d,x,g,\theta,\pi,v,w,h}\sum_i\sum_j{c_{ij}x_{ij}} SP=d,x,g,θ,π,v,w,hmaxijcijxij s . t . s.t. s.t. ∑ j x i j ≤ z i ∗ ∀ i \sum_{j}x_{ij}\leq z^\ast_i \quad \forall i jxijzii ∑ i x i j ≥ d j ∀ j \sum_{i}x_{ij}\geq d_j \quad \forall j ixijdjj x i j ≥ 0 , ∀ i , j x_{ij}\geq 0, \quad \forall i,j xij0,i,j c i j + π i − θ j ≥ 0 ∀ i , j c_{ij}+\pi_i-\theta_j \geq 0 \quad \forall i,j cij+πiθj0i,j π i ≥ 0 ∀ i \pi_i \geq0 \quad \forall i πi0i θ j ≥ 0 ∀ j \theta_j \geq0 \quad \forall j θj0j d j = d ‾ j + g j d ~ j ∀ j d_j=\underline{d}_j+g_j\tilde{d}_j \quad \forall j dj=dj+gjd~jj g j ∈ [ 0 , 1 ] ∀ j g_j\in [0,1] \quad \forall j gj[0,1]j g 0 + g 1 ≤ 1.2 g_0+g_1\leq1.2 g0+g11.2 g 0 + g 1 + g 2 ≤ 1.8 g_0+g_1+g_2\leq1.8 g0+g1+g21.8 π i ≤ M v i ∀ i \pi_i\leq Mv_i\quad \forall i πiMvii z i ∗ − ∑ j x i j ≤ M ( 1 − v i ) ∀ i z^\ast_i-\sum_{j}x_{ij}\leq M(1-v_i)\quad \forall i zijxijM(1vi)i θ j ≤ M w j ∀ j \theta_j\leq Mw_j\quad \forall j θjMwjj ∑ i x i j − d j ≤ M ( 1 − w j ) ∀ j \sum_{i}x_{ij}-d_j\leq M(1-w_j)\quad \forall j ixijdjM(1wj)j x i j ≤ M h i j ∀ i , j x_{ij}\leq Mh_{ij}\quad \forall i,j xijMhiji,j c i j + π i − θ j ≤ M ( 1 − h i j ) ∀ i , j c_{ij}+\pi_i-\theta_j\leq M(1-h_{ij})\quad \forall i,j cij+πiθjM(1hij)i,j其中, z i ∗ z^\ast_i zi为主问题的最优解,传递到子问题中被当做参数。

总结

本文对Zeng Bo论文中的算例进行了细致的说明,通过使用AMPL软件调用CPLEX求解器,得到了与论文中相同的结果。
论文中的结果:
求解结果
我的结果:
第一次迭代结果
第二次迭代结果
C&CG算法求解两阶段鲁棒优化问题:推导与AMPL/python代码_第3张图片

具体的代码可以访问我的Github:https://github.com/jysw980/Paper-reproduced-Zeng-Bo-2013-paper-about-CCG-Algorithm.git

本文的撰写和程序的编写过程中,得到了师兄、同门的大力支持,在此向他们表示感谢!

最后,希望这篇文章能够帮助到大家!

python代码

20240702 更新 python代码,使用的是gurobipy

from gurobipy import Model, GRB, quicksum
import numpy as np

# Data
supply_data = {
    "S1": {"f": 400, "a": 18, "k": 800},
    "S2": {"f": 414, "a": 25, "k": 800},
    "S3": {"f": 326, "a": 20, "k": 800}
}

demand_data = {
    "D1": {"d_L": 206, "d_T": 40},
    "D2": {"d_L": 274, "d_T": 40},
    "D3": {"d_L": 220, "d_T": 40}
}

costs = {
    ("S1", "D1"): 22, ("S1", "D2"): 33, ("S1", "D3"): 24,
    ("S2", "D1"): 33, ("S2", "D2"): 23, ("S2", "D3"): 30,
    ("S3", "D1"): 20, ("S3", "D2"): 25, ("S3", "D3"): 27
}

# Big-M parameter
M = 10000

# Initialize model
master = Model("Master")
sub = Model("Sub")

# Sets
supply = list(supply_data.keys())
demand = list(demand_data.keys())
iter = np.arange(1,4,1)

# Global Parameters
f = {i: supply_data[i]["f"] for i in supply}
a = {i: supply_data[i]["a"] for i in supply}
k = {i: supply_data[i]["k"] for i in supply}
d_L = {j: demand_data[j]["d_L"] for j in demand}
d_T = {j: demand_data[j]["d_T"] for j in demand}
c = costs

# Master problem variables
y = master.addVars(supply, vtype=GRB.BINARY, name="y")
z = master.addVars(supply, vtype=GRB.CONTINUOUS, name="z", lb=0.0)
eta = master.addVar(vtype=GRB.CONTINUOUS, name="eta")
x_gen = master.addVars(supply, demand, iter, vtype=GRB.CONTINUOUS, name="x_gen", lb=0.0)

# Subproblem variables
x = sub.addVars(supply, demand, vtype=GRB.CONTINUOUS, name="x", lb=0.0)
d = sub.addVars(demand, vtype=GRB.CONTINUOUS, name="d")
g = sub.addVars(demand, vtype=GRB.CONTINUOUS, name="g", lb=0.0, ub=1.0)
theta = sub.addVars(demand, vtype=GRB.CONTINUOUS, name="theta", lb=0.0)
Pi = sub.addVars(supply, vtype=GRB.CONTINUOUS, name="pi", lb=0.0)
v = sub.addVars(supply, vtype=GRB.BINARY, name="v")
w = sub.addVars(demand, vtype=GRB.BINARY, name="w")
h = sub.addVars(supply, demand, vtype=GRB.BINARY, name="h")

# Objective for master problem
master.setObjective(quicksum(f[i]*y[i] + a[i]*z[i] for i in supply) + eta, GRB.MINIMIZE)

# Constraints for master problem
master.addConstrs((z[i] <= k[i]*y[i] for i in supply), name="C1")
master.addConstr(quicksum(z[i] for i in supply) >= 772, name="C2")

master.update()
# Objective for subproblem
sub.setObjective(quicksum(c[i,j]*x[i,j] for i in supply for j in demand), GRB.MAXIMIZE)

# Constraints for subproblem
# sub.addConstrs((quicksum(x[i,j] for j in demand) <= z_star[i] for i in supply), name="C3")
sub.addConstrs((quicksum(x[i,j] for i in supply) >= d[j] for j in demand), name="C4")
sub.addConstrs((theta[j] - Pi[i] <= c[i,j] for i in supply for j in demand), name="C5")
sub.addConstrs((d[j] == d_L[j] + d_T[j]*g[j] for j in demand), name="C6")
sub.addConstr(quicksum(g[j] for j in demand if j != "D3") <= 1.2, name="C7")
sub.addConstr(quicksum(g[j] for j in demand) <= 1.8, name="C8")
sub.addConstrs((Pi[i] <= M*v[i] for i in supply), name="C9")
# sub.addConstrs((z_star[i] - quicksum(x[i,j] for j in demand) <= M*(1-v[i]) for i in supply), name="C10")
sub.addConstrs((theta[j] <= M*w[j] for j in demand), name="C11")
sub.addConstrs((quicksum(x[i,j] for i in supply) - d[j] <= M*(1-w[j]) for j in demand), name="C12")
sub.addConstrs((x[i,j] <= M*h[i,j] for i in supply for j in demand), name="C13")
sub.addConstrs((c[i,j] - theta[j] + Pi[i] <= M*(1-h[i,j]) for i in supply for j in demand), name="C14")


sub.update()

#Cancel Log To Console
master.setParam('LogToConsole', 0)
sub.setParam('LogToConsole', 0)

# Initialization
LB = -float('inf')
UB = float('inf')
N = 0

print("\n----------------------Main Loop of C&CG------------------------\n")

while UB - LB > 1e-4:
    print(f"\n-------------------------Iteration {N+1}---------------------------\n")
    
    # Solve master problem
    master.optimize()
    
    LB = master.ObjVal
    
    sub.addConstrs((quicksum(x[i,j] for j in demand) <= z[i].X for i in supply), name="C3")
    sub.addConstrs((z[i].X - quicksum(x[i,j] for j in demand) <= M*(1-v[i]) for i in supply), name="C10")
    sub.update()
    
    # Solve subproblem
    sub.optimize()

    UB = min(UB, LB - eta.X + sub.ObjVal)

    print(f"\niter: {N+1}   LB: {LB:.1f}  UB: {UB:.1f}  Gap: {100 * (UB - LB) / UB:.2f}%\n")
    
    if UB - LB <= 1e-4:
        break
    
    N += 1

    master.addConstr(eta >= quicksum(c[i,j]*x_gen[i,j,N] for i in supply for j in demand), name=f"gen_C1_{N}")
    master.addConstrs((quicksum(x_gen[i,j,N] for j in demand) <= z[i] for i in supply), name=f"gen_C2_{N}")
    master.addConstrs((quicksum(x_gen[i,j,N] for i in supply) >= d[j].X for j in demand), name=f"gen_C3_{N}")
    master.update()
    
    for i in supply:
        sub.remove(sub.getConstrByName(f'C3[{i}]'))
        sub.remove(sub.getConstrByName(f'C10[{i}]'))
    sub.update()

print("\n-------------------------The optimal solution has been found!---------------------------\n")
print(f"Optimal value is {LB:.1f}\n")
print(f"Optimal solution:")
for v in master.getVars():
    print(f"{v.varName}: {v.x}")

你可能感兴趣的:(学习方法,算法,笔记,数学建模,python)