Dantzig&Wolfe分解(简称DW分解)1是一种列生成技巧,可以把一类特殊形式线性规划问题分解成若干子问题进行求解.
问题描述
我们考虑如下形式的线性规划问题:
min c 1 T x 1 + c 2 T x 2 + ⋯ + c m T x m s.t. A 0 , 1 x 1 + A 0 , 2 x 2 + ⋯ + A 0 , m x m = b 0 A 1 , 1 x 1 = b 1 A 2 , 2 x 2 = b 2 . . . A n , m x m = b m x j ≥ 0 , ∀ j \begin{aligned} \min ~ & c_1^Tx_1 & + ~ & c_2^Tx_2 & + ~ & \cdots & + ~ & c^T_m x_m & \\ \text{s.t. } & A_{0,1}x_1 & + ~ & A_{0,2}x_2 & + ~ & \cdots & + ~& A_{0,m} x_m & = b_0 \\ & A_{1,1} x_1 & & & & & & & = b_1 \\ & & & A_{2,2} x_2 & & & & & = b_2 \\ & & & & & ... & & & \\ & & & & & & & A_{n,m}x_m & = b_m \\ & x_j \geq 0, & \forall j & \end{aligned} min s.t. c1Tx1A0,1x1A1,1x1xj≥0,+ + ∀jc2Tx2A0,2x2A2,2x2+ + ⋯⋯...+ + cmTxmA0,mxmAn,mxm=b0=b1=b2=bm
其中 A i , j ∈ R m i × n j A_{i,j}\in \mathbb{R}^{m_i \times n_j} Ai,j∈Rmi×nj, b i ∈ R m i b_i \in\mathbb{R}^{m_i} bi∈Rmi, c j , x j ∈ R n j c_j, x_j \in \mathbb{R}^{n_j} cj,xj∈Rnj.
说明
- 上述规划共 m + 1 m+1 m+1组约束. 第一组约束包含了所有的决策变量, 称为连接约束(Linking constraints), 接下来是 m m m组独立的约束.
- c j , x j , b i c_j, x_j, b_i cj,xj,bi都是向量.
- 上述规划如果写成标准形式 min x { c T x ∣ A x = b , x ≥ 0 } \min_x \{c^Tx | Ax = b, x\geq 0\} minx{cTx∣Ax=b,x≥0}, 其系数矩阵 A A A的维度是 ∑ i = 0 m m i × ∑ j = 1 n n j \sum_{i=0}^m m_i \times \sum_{j=1}^n n_j ∑i=0mmi×∑j=1nnj. 我们发现 A A A实际上是相对稀疏的. 当 m m m, n n n的非常大时, 矩阵的规模可能大到无法直接求解上述规划. 在这种情况下, 我们考虑把它分解成多个子问题进行求解(DW分解).
应用场景
例1 一个公司有 m m m个部门, 各部门有独立的约束, 部门之间也有约束. 目标是最小化所有部门的总成本.
例2 一个零售公司有 m m m个仓库, 需要决定每个仓库存放的商品. 每个仓库中对商品有一些约束. 商品关于各仓库也需要满足一些约束. 目标是最小化总的成本(例如配送成本/时效等).
主问题
令
P
i
=
{
x
i
∈
R
n
i
∣
A
i
,
i
x
i
=
b
i
}
P_i = \{ x_i \in \mathbb{R}^{n_i} \mid A_{i,i}x_i = b_i\}
Pi={xi∈Rni∣Ai,ixi=bi}. 如果
P
i
P_i
Pi是有界的, 那么
x
i
x_i
xi可以表示成
P
i
P_i
Pi顶点的凸组合. 设
v
i
,
1
,
v
i
,
2
,
…
,
v
i
,
p
i
v_{i,1}, v_{i,2}, \ldots, v_{i, p_i}
vi,1,vi,2,…,vi,pi代表
P
i
P_i
Pi的顶点(Extreme point), 则存在
λ
i
,
j
≥
0
\lambda_{i,j} \geq 0
λi,j≥0且
∑
j
=
1
p
i
λ
i
,
j
=
1
\sum_{j=1}^{p_i} \lambda_{i,j}=1
∑j=1piλi,j=1使得
x
i
=
∑
j
=
1
p
i
λ
i
,
j
v
i
,
j
.
x_i = \sum_{j=1}^{p_i} \lambda_{i,j} v_{i,j}.
xi=j=1∑piλi,jvi,j.
在一般情况下(
P
i
P_i
Pi无界或有界时), 它可以用顶点和极射线的线性组合来表示(参考 Minkowski表示定理). 具体来说, 令
w
i
,
1
,
w
i
,
2
,
…
,
w
i
,
q
i
w_{i,1}, w_{i,2}, \ldots, w_{i,q_i}
wi,1,wi,2,…,wi,qi代表极射线(Extreme ray). 则存在
λ
i
,
j
≥
0
\lambda_{i,j} \geq 0
λi,j≥0且
∑
j
=
1
p
i
λ
i
,
j
=
1
\sum_{j=1}^{p_i} \lambda_{i,j}=1
∑j=1piλi,j=1,
μ
i
,
j
≥
0
\mu_{i,j}\geq 0
μi,j≥0使得
x
i
=
∑
j
=
1
p
i
λ
i
,
j
v
i
,
j
+
∑
j
=
1
q
i
μ
i
,
j
w
i
,
j
.
x_i = \sum_{j=1}^{p_i} \lambda_{i,j} v_{i,j} + \sum_{j=1}^{q_i}\mu_{i,j} w_{i,j}.
xi=j=1∑piλi,jvi,j+j=1∑qiμi,jwi,j.
假设我们枚举 P 1 , P 2 , … , P m P_1, P_2, \ldots, P_m P1,P2,…,Pm所有的顶点和极射线. 把上式代入原问题得到主问题(Master problem):
主问题
min ∑ i = 1 m ∑ j = 1 p i λ i , j ( c i T v i , j ) + ∑ i = 1 m ∑ j = 1 q i μ i , j ( c i T w i , j ) s.t. ∑ i = 1 m ∑ j = 1 p i λ i , j A 0 , i v i , j + ∑ i = 1 m ∑ j = 1 p i μ i , j A 0 , i w i , j = b 0 ∑ j = 1 p i λ i , j = 1 , ∀ i λ i , j , μ i , j ≥ 0 , ∀ i , j . \begin{aligned} \min\ & \sum_{i=1}^m\sum_{j=1}^{p_i}\lambda_{i,j}(c_i^Tv_{i,j}) + \sum_{i=1}^m\sum_{j=1}^{q_i}\mu_{i,j}(c_i^Tw_{i,j}) \\ \text{s.t. } & \sum_{i=1}^m \sum_{j=1}^{p_i}\lambda_{i,j} A_{0,i}v_{i,j} + \sum_{i=1}^m \sum_{j=1}^{p_i}\mu_{i,j} A_{0,i}w_{i,j} = b_0 \\ & \sum_{j=1}^{p_i} \lambda_{i,j} =1, \quad \forall i \\ & \lambda_{i,j}, \mu_{i,j} \geq0, \quad \forall i, j. \end{aligned} min s.t. i=1∑mj=1∑piλi,j(ciTvi,j)+i=1∑mj=1∑qiμi,j(ciTwi,j)i=1∑mj=1∑piλi,jA0,ivi,j+i=1∑mj=1∑piμi,jA0,iwi,j=b0j=1∑piλi,j=1,∀iλi,j,μi,j≥0,∀i,j.
说明
- λ i , j \lambda_{i,j} λi,j, μ i , j \mu_{i,j} μi,j是决策变量.
- 约束的数量为 m 0 + m m_0+m m0+m(第一个等式包含了 m 0 m_0 m0个约束). 原问题的约束数量是 ∑ i = 0 m m i \sum_{i=0}^m m_i ∑i=0mmi. 因此主问题的约束数量明显减少了.
- 但是主问题的变量显著增加了( λ i , j , μ i , j \lambda_{i,j}, \mu_{i,j} λi,j,μi,j对应所有的顶点和极射线). 与列生成技巧相似, 主问题一开始只考虑两个可行解(对应顶点和极射线). 通过求解子问题得到新的顶点或极射线.
- 在实际问题中, 一般情况下
P
i
P_i
Pi都是有界的. 此时主问题可以简化成如下形式.
min ∑ i = 1 m ∑ j = 1 p i λ i , j ( c i T v i , j ) s.t. ∑ i = 1 m ∑ j = 1 p i λ i , j A 0 , i v i , j = b 0 ∑ j = 1 p i λ i , j = 1 , ∀ i λ i , j ≥ 0 , ∀ i , j . \begin{aligned} \min\ & \sum_{i=1}^m\sum_{j=1}^{p_i}\lambda_{i,j}(c_i^Tv_{i,j}) \\ \text{s.t. } & \sum_{i=1}^m \sum_{j=1}^{p_i}\lambda_{i,j} A_{0,i}v_{i,j} = b_0 \\ & \sum_{j=1}^{p_i} \lambda_{i,j} =1, \quad \forall i \\ & \lambda_{i,j} \geq 0, \quad \forall i, j. \end{aligned} min s.t. i=1∑mj=1∑piλi,j(ciTvi,j)i=1∑mj=1∑piλi,jA0,ivi,j=b0j=1∑piλi,j=1,∀iλi,j≥0,∀i,j.
子问题
定义主问题的对偶变量 y ∈ R m 0 y\in\mathbb{R}^{m_0} y∈Rm0, z i ∈ R z_i \in \mathbb{R} zi∈R, i = 1 , 2 , … , m i=1, 2, \ldots, m i=1,2,…,m. 我们计算变量 λ i , j , μ i , j \lambda_{i,j}, \mu_{i,j} λi,j,μi,j的Reduced Cost:
α i , j = c i T v i , j − y T A 0 , i v i , j − z i β i , j = c i T w i , j − y T A 0 , i w i , j \begin{aligned} & \alpha_{i,j} = c_i^T v_{i,j} - y^TA_{0,i}v_{i,j} - z_i \\ & \beta_{i, j} = c_i^Tw_{i,j} - y^TA_{0,i}w_{i,j} \end{aligned} αi,j=ciTvi,j−yTA0,ivi,j−ziβi,j=ciTwi,j−yTA0,iwi,j
其中 α i , j \alpha_{i,j} αi,j, β i , j \beta_{i,j} βi,j分别代表 λ i , j , μ i , j \lambda_{i,j}, \mu_{i,j} λi,j,μi,j的reduced cost. 当 α i , j \alpha_{i,j} αi,j, β i , j ≥ 0 \beta_{i,j}\geq 0 βi,j≥0时得到原问题的最优解. 因此在子问题中, 我们需要计算 v i , j v_{i,j} vi,j(或 w i , j w_{i,j} wi,j)使得 α i , j , β i , j \alpha_{i,j}, \beta_{i,j} αi,j,βi,j的值尽可能小.
子问题 - i i i
min f i : = ( c i − A 0 , i T y ) T x i s.t. A i , i x i = b i x i ≥ 0. \begin{aligned} \min\ & f_{i} := (c_i - A_{0,i}^Ty)^T x_i \\ \text{s.t. } & A_{i,i} x_i = b_i \\ & x_i \geq 0. \end{aligned} min s.t. fi:=(ci−A0,iTy)TxiAi,ixi=bixi≥0.
求解子问题时考虑三种情况.
- 最优解的目标值为 − ∞ -\infty −∞. 此时 α i , j , β i , j < 0 \alpha_{i,j}, \beta_{i,j} < 0 αi,j,βi,j<0, 子问题的解是极射线 w i , j w_{i,j} wi,j. 我们把 A 0 , i w i , j A_{0,i}w_{i,j} A0,iwi,j加入到主问题进行求解.
- 最优解的目标值有界且 f i < z i f_i < z_i fi<zi. 此时 α i , j < 0 \alpha_{i,j} < 0 αi,j<0, 子问题的解是顶点 v i , j v_{i,j} vi,j. 我们把 A 0 , i v i , j A_{0,i}v_{i,j} A0,ivi,j加入到主问题进行求解.
- 最优解的目标值有界且 f i , j ≥ z i f_{i,j} \geq z_i fi,j≥zi. 此时 0 ≤ α i , j ≤ β i , j 0\leq \alpha_{i,j} \leq \beta_{i,j} 0≤αi,j≤βi,j(注意到实际上 z i ≥ 0 z_i\geq 0 zi≥0), 得到最优解.
例子: 一个选品问题
考虑 m m m个零售品牌商, 每个零售品牌商有自己的商品(SKU), 例如可口可乐, 雪碧和芬达对应同一家品牌商. 一家电商平台需要从 m m m个品牌中选择一些商品做营销活动. 已知每个商品的营销成本, 商品预期的收益和营销的预算. 在总营销费用不超过预算且每个品牌选中商品数量有限制的前提下, 如何选择商品使得预期的收益最大化?
我们先考虑一个直观的数学模型.
indices
- i i i – 品牌
- j j j – 商品
parameters
- a i , j ∈ { 0 , 1 } a_{i, j} \in \{0, 1\} ai,j∈{0,1} – 商品 j j j是否属于品牌 i i i
- p j p_j pj – 商品 j j j的预期收益
- c j c_j cj – 商品 j j j的营销成本
- b i b_i bi – 选中品牌 i i i的商品数量上限
- d d d – 营销费用的总预算
decision variables
- x j ∈ { 0 , 1 } x_j\in \{0,1\} xj∈{0,1} – 是否选择商品 j j j
模型1
max ∑ j p j x j s.t. ∑ j c j x j ≤ d ∑ j a i , j x j ≤ b i , ∀ i x j ∈ { 0 , 1 } . \begin{aligned} \max\ & \sum_j p_j x_j \\ \text{s.t. } & \sum_j c_j x_j \leq d \\ & \sum_j a_{i,j} x_j \leq b_i, \quad \forall i\\ & x_j \in \{0, 1\}. \end{aligned} max s.t. j∑pjxjj∑cjxj≤dj∑ai,jxj≤bi,∀ixj∈{0,1}.
当品牌和商品数量较大时, 例如1000品牌和10万商品, 这时参数 a i , j a_{i,j} ai,j的规模是1亿, 因此直接求解非常困难. 注意到每个品牌的商品是不一样的, 因而矩阵 A = ( a i , j ) A = (a_{i,j}) A=(ai,j)非常稀疏, 我们可以把每个品牌中的商品分开考虑, 得到如下模型.
indices
- i i i – 品牌
- j j j – 商品
parameters
- n i n_i ni – 品牌 i i i的商品数量
- p i , j p_{i,j} pi,j – 品牌 i i i中商品 j j j的预期收益, j = 1 , 2 , … , n i j=1, 2, \ldots, n_i j=1,2,…,ni
- c i , j c_{i,j} ci,j – 品牌 i i i中商品 j j j的营销成本, j = 1 , 2 , … , n i j=1, 2, \ldots, n_i j=1,2,…,ni
- b i b_i bi – 品牌 i i i可以被选中的商品数量上限
- d d d – 营销费用的总预算
decision variables
- x i , j ∈ { 0 , 1 } x_{i,j}\in \{0, 1\} xi,j∈{0,1} – 是否选择品牌 i i i中的商品 j j j, j = 1 , 2 , … , n i j=1, 2, \ldots, n_i j=1,2,…,ni
模型2
max
∑
i
=
1
m
∑
j
=
1
n
i
p
i
,
j
x
i
,
j
s.t.
∑
i
=
1
m
∑
j
=
1
n
i
c
i
,
j
x
i
,
j
≤
d
∑
j
=
1
n
i
x
i
,
j
≤
b
i
,
∀
i
x
i
,
j
∈
{
0
,
1
}
.
\begin{aligned} \max\ & \sum_{i=1}^m\sum_{j=1}^{n_i} p_{i,j} x_{i,j} \\ \text{s.t. } & \sum_{i=1}^m\sum_{j=1}^{n_i} c_{i,j} x_{i,j} \leq d \\ & \sum_{j=1}^{n_i} x_{i,j} \leq b_i, \quad \forall i\\ & x_{i,j} \in \{0, 1\}. \end{aligned}
max s.t. i=1∑mj=1∑nipi,jxi,ji=1∑mj=1∑nici,jxi,j≤dj=1∑nixi,j≤bi,∀ixi,j∈{0,1}.
模型1和模型2本质上是相同的, 因此当品牌数和商品数量非常大时直接求解模型2依然非常困难. 下面我们使用DW分解进行求解.
令
P
i
=
{
x
i
,
j
∣
∑
j
=
1
n
i
x
i
,
j
≤
b
i
}
P_i = \{x_{i,j} \mid \sum_{j=1}^{n_i} x_{i,j} \leq b_i \}
Pi={xi,j∣∑j=1nixi,j≤bi},
i
=
1
,
2
,
…
,
m
i=1,2, \ldots, m
i=1,2,…,m. 注意到
P
i
P_i
Pi是有界的, 我们用
v
i
,
1
(
k
)
,
v
i
,
2
(
k
)
,
…
,
v
i
,
p
i
(
k
)
,
k
=
1
,
2
,
…
,
p
i
v_{i,1}^{(k)}, v_{i,2}^{(k)}, \ldots, v_{i,p_i}^{(k)}, \quad k=1, 2, \ldots, p_i
vi,1(k),vi,2(k),…,vi,pi(k),k=1,2,…,pi
代表
P
i
P_i
Pi的顶点, 因此
x
i
,
j
x_{i,j}
xi,j可以被表示成如下形式:
x
i
,
j
=
∑
k
=
1
p
i
λ
i
,
k
⋅
v
i
,
j
(
k
)
,
其中
∑
k
=
1
p
i
λ
i
,
k
=
1
,
λ
i
,
k
≥
0.
x_{i,j} = \sum_{k=1}^{p_i}\lambda_{i,k}\cdot v_{i,j}^{(k)}, \quad \text{其中 } \sum_{k=1}^{p_i} \lambda_{i,k} = 1,\ \lambda_{i,k}\geq 0.
xi,j=k=1∑piλi,k⋅vi,j(k),其中 k=1∑piλi,k=1, λi,k≥0.
把它代入模型2中我们得到主问题的形式.
主问题
max
∑
i
=
1
m
∑
j
=
1
n
i
∑
k
=
1
p
i
λ
i
,
k
(
p
i
,
j
v
i
,
j
(
k
)
)
s.t.
∑
i
=
1
m
∑
j
=
1
n
i
∑
k
=
1
p
i
λ
i
,
k
(
c
i
,
j
v
i
,
j
(
k
)
)
≤
d
∑
k
=
1
p
i
λ
i
,
k
=
1
,
∀
i
λ
i
,
k
≥
0
,
∀
i
,
j
,
k
.
\begin{aligned} \max\ & \sum_{i=1}^m\sum_{j=1}^{n_i} \sum_{k=1}^{p_i}\lambda_{i,k} (p_{i,j}v_{i,j}^{(k)}) \\ \text{s.t. } & \sum_{i=1}^m \sum_{j=1}^{n_i} \sum_{k=1}^{p_i}\lambda_{i,k}(c_{i,j}v_{i,j}^{(k)}) \leq d \\ & \sum_{k=1}^{p_i} \lambda_{i,k} =1, \quad \forall i \\ & \lambda_{i,k} \geq 0, \quad \forall i, j, k. \end{aligned}
max s.t. i=1∑mj=1∑nik=1∑piλi,k(pi,jvi,j(k))i=1∑mj=1∑nik=1∑piλi,k(ci,jvi,j(k))≤dk=1∑piλi,k=1,∀iλi,k≥0,∀i,j,k.
定义对偶变量
y
y
y和
z
i
z_i
zi,
i
=
1
,
2
,
…
,
m
i=1,2,\ldots, m
i=1,2,…,m. 计算
λ
i
,
k
\lambda_{i,k}
λi,k对应的reduced cost:
α
i
(
k
)
=
∑
j
=
1
n
i
p
i
,
j
v
i
,
j
(
k
)
−
∑
j
=
1
n
i
y
⋅
c
i
,
j
v
i
,
j
(
k
)
−
z
i
.
\alpha_i^{(k)} = \sum_{j=1}^{n_i} p_{i,j}v_{i,j}^{(k)} - \sum_{j=1}^{n_i}y\cdot c_{i,j}v_{i,j}^{(k)} - z_i.
αi(k)=j=1∑nipi,jvi,j(k)−j=1∑niy⋅ci,jvi,j(k)−zi.
注意: 主问题是最大化问题, 因此 α i ( k ) > 0 \alpha_i^{(k)}>0 αi(k)>0意味着可以提升主问题的目标函数值. 我们有:
- 子问题是最大化问题.
- 当所有为 α i ( k ) ≤ 0 \alpha_i^{(k)} \leq 0 αi(k)≤0时主问题达到最优.
子问题 -
i
i
i
min
∑
j
=
1
n
i
(
p
i
,
j
−
y
c
i
,
j
)
x
i
,
j
s.t.
∑
j
=
1
n
i
x
i
,
j
≤
b
i
x
i
,
j
∈
{
0
,
1
}
.
\begin{aligned} \min\ & \sum_{j=1}^{n_i}( p_{i,j}-yc_{i,j})x_{i,j} \\ \text{s.t. } & \sum_{j=1}^{n_i}x_{i,j} \leq b_i \\ & x_{i,j} \in \{0, 1\}. \end{aligned}
min s.t. j=1∑ni(pi,j−yci,j)xi,jj=1∑nixi,j≤bixi,j∈{0,1}.
初始化
对任意的
i
=
1
,
2
,
…
,
m
i=1, 2, \ldots, m
i=1,2,…,m, 定义向量:
v
i
=
(
0
,
0
,
…
,
0
)
T
∈
R
b
i
.
v_i = (0, 0, \ldots, 0)^T \in \mathbb{R}^{b_i}.
vi=(0,0,…,0)T∈Rbi.
显然
v
i
v_i
vi是每个约束
i
i
i的可行解, 即
∑
j
=
1
b
i
v
i
,
j
≤
b
i
\sum_{j=1}^{b_i} v_{i,j} \leq b_i
∑j=1bivi,j≤bi,
∀
i
\forall i
∀i. 我们用
v
1
,
v
2
,
…
,
v
m
v_1, v_2, \ldots, v_m
v1,v2,…,vm作为主问题初始化的顶点.
Remark. 前文的推导过程要求 v i v_i vi是多面体的顶点, 但上面 v i v_i vi的定义并不满足此条件. 这么做可行的原因是任意可行解本身就是多面体顶点的凸组合.
求解
求解的基本步骤如下:
- 初始化主问题, 求解子问题的输入参数 y y y
- 求解 m m m个子问题,分别计算 λ i , k \lambda_{i,k} λi,k对应的Reduced Cost α i ( k ) \alpha_i^{(k)} αi(k). 如果 α i ( k ) > 0 \alpha_i^{(k)}>0 αi(k)>0, 则把对应的解 v i ( k ) v_i^{(k)} vi(k)加入到主问题. ( k k k可以理解为迭代的次数)
- 如果所有的 α i ( k ) < 0 \alpha_i^{(k)} < 0 αi(k)<0, 则停止迭代;否则迭代求解主问题和子问题直到满足停止条件.
Python实现
主问题模型
# master_model.py
from ortools.linear_solver import pywraplp
class MasterModel(object):
def __init__(self, p, v, c, d):
"""
:param p: p[i][j]代表品牌i中商品j的预期收益
:param v: v[i]代表第i个子问题的解
:param c: c[i][j]代表品牌i中商品j的营销成本
:param d: scalar, 总预算
"""
self._solver = pywraplp.Solver('MasterModel',
pywraplp.Solver.GLOP_LINEAR_PROGRAMMING)
self._p = p
self._v = v
self._c = c
self._d = d
self._la = None # 决策变量lambda
self._constraint_y = None # 约束
self._constraint_z = [] # 约束
self._solution_la = None # 计算结果
def _init_decision_variables(self):
self._la = [[]] * len(self._v)
self._solution_la = [[]] * len(self._v) # 初始化保存结果的变量
for i in range(len(self._v)):
self._la[i] = [[]] * len(self._v[i])
self._solution_la[i] = [[]] * len(self._v[i]) # 初始化保存结果的变量
for k in range(len(self._v[i])):
self._la[i][k] = self._solver.NumVar(0, 1, 'la[%d][%d]' % (i, k))
def _init_constraints(self):
self._constraint_y = self._solver.Constraint(0, self._d)
for i in range(len(self._v)):
for k in range(len(self._v[i])):
f = 0
for j in range(len(self._v[i][k])):
f += self._c[i][j] * self._v[i][k][j]
self._constraint_y.SetCoefficient(self._la[i][k], f)
self._constraint_z = [None] * len(self._v)
for i in range(len(self._v)):
self._constraint_z[i] = self._solver.Constraint(1, 1)
for k in range(len(self._la[i])):
self._constraint_z[i].SetCoefficient(self._la[i][k], 1)
def _init_objective(self):
obj = self._solver.Objective()
for i in range(len(self._v)):
for k in range(len(self._v[i])):
f = 0
for j in range(len(self._v[i][k])):
f += self._p[i][j] * self._v[i][k][j]
obj.SetCoefficient(self._la[i][k], f)
obj.SetMaximization()
def solve(self):
self._init_decision_variables()
self._init_constraints()
self._init_objective()
self._solver.Solve()
# 保存计算结果
for i in range(len(self._v)):
for k in range(len(self._v[i])):
self._solution_la[i][k] = self._la[i][k].solution_value()
def get_solution_value(self):
return self._solution_la
def get_y(self):
""" 获取对偶变量y的值
"""
return self._constraint_y.dual_value()
def get_zi(self, i):
""" 获取对偶变量z[i]的值
"""
return self._constraint_z[i].dual_value()
def get_obj_value(self):
res = 0
for i in range(len(self._p)):
for k in range(len(self._v[i])):
for j in range(len(self._p[i])):
res += self._solution_la[i][k] * self._p[i][j] * self._v[i][k][j]
return res
def get_solution_x(self):
""" 得到原问题的解. x[i][j] = sum(la[i][k] * v[i][k][j]) over k.
"""
x = [[]] * len(self._v)
for i in range(len(self._v)):
x[i] = [0] * len(self._v[i][0])
for i in range(len(self._v)):
for k in range(len(self._v[i])):
for j in range(len(self._v[i][k])):
x[i][j] += self._solution_la[i][k] * self._v[i][k][j]
return x
子问题模型
# sub_model.py
from ortools.linear_solver import pywraplp
import numpy as np
class SubModel(object):
""" 子问题i
"""
def __init__(self, pi, ci, y, bi):
""" 下标i忽略
:param pi: list, pi := p[i] = [p1, p2, ..., ]
:param ci: list, ci := c[i] = [c1, c2, ..., ]
:param y: scalar
:param bi: scalar
"""
self._solver = pywraplp.Solver('MasterModel',
pywraplp.Solver.CBC_MIXED_INTEGER_PROGRAMMING)
self._pi = pi
self._ci = ci
self._y = y
self._bi = bi
self._x = None # 决策变量
self._solution_x = None # 计算结果
def _init_decision_variables(self):
self._x = [None] * len(self._pi)
for j in range(len(self._pi)):
self._x[j] = self._solver.IntVar(0, 1, 'x[%d]' % j)
def _init_constraints(self):
ct = self._solver.Constraint(0, self._bi)
for j in range(len(self._pi)):
ct.SetCoefficient(self._x[j], 1)
def _init_objective(self):
obj = self._solver.Objective()
for j in range(len(self._pi)):
obj.SetCoefficient(self._x[j], self._pi[j] - self._y * self._ci[j])
obj.SetMaximization()
def solve(self):
self._init_decision_variables()
self._init_constraints()
self._init_objective()
self._solver.Solve()
self._solution_x = [s.solution_value() for s in self._x]
def get_solution_x(self):
return self._solution_x
def get_obj_value(self):
p = np.array(self._pi)
c = np.array(self._ci)
x = np.array(self._solution_x)
return sum((p - self._y * c) * x)
DW分解的求解过程
# dw_proc.py
from master_model import MasterModel
from sub_model import SubModel
class DWProc(object):
def __init__(self, p, c, d, b, max_iter=1000):
"""
:param p: p[i][j]代表品牌i中商品j的预期收益
:param c: c[i][j]代表品牌i中商品j的营销成本
:param d: 总营销成本, int
:param b: b[i]代表选中品牌i的商品数量限制
"""
self._p = p
self._c = c
self._d = d
self._b = b
self._v = None # 待初始化
self._max_iter = max_iter
self._iter_times = 0
self._status = -1
self._reduced_costs = [1] * len(self._p)
self._solution_x = None # 计算结果
self._obj_value = 0 # 目标函数值
def _stop_criteria_is_satisfied(self):
""" 根据reduced cost判断是否应该停止迭代.
注意: 这是最大化问题, 因此所有子问题对应的reduced cost <= 0时停止.
"""
st = [0] * len(self._reduced_costs)
for i in range(len(self._reduced_costs)):
if self._reduced_costs[i] < 1e-6:
st[i] = 1
if sum(st) == len(st):
self._status = 0
return True
if self._iter_times >= self._max_iter:
if self._status == -1:
self._status = 1
return True
return False
def _init_v(self):
""" 初始化主问题的输入
"""
self._v = [[]] * len(self._p)
for i in range(len(self._p)):
self._v[i] = [[0] * len(self._p[i])]
def _append_v(self, i, x):
""" 把子问题i的解加入到主问题中
:param x: 子问题i的解
"""
self._v[i].append(x)
def solve(self):
# 初始化主问题并求解
self._init_v()
mp = MasterModel(self._p, self._v, self._c, self._d)
mp.solve()
self._iter_times += 1
# 迭代求解主问题和子问题直到满足停止条件
while not self._stop_criteria_is_satisfied():
# 求解子问题
print("==== iter %d ====" % self._iter_times)
for i in range(len(self._p)):
# 求解子问题
sm = SubModel(self._p[i], self._c[i], mp.get_y(), self._b[i])
sm.solve()
# 更新reduced cost
self._reduced_costs[i] = sm.get_obj_value() - mp.get_zi(i)
# 把子问题中满足条件的解加入到主问题中
if self._reduced_costs[i] > 0:
self._append_v(i, sm.get_solution_x())
print(">>> Solve sub problem %d, reduced cost = %f" % (i, self._reduced_costs[i]))
# 求解主问题
mp = MasterModel(self._p, self._v, self._c, self._d)
mp.solve()
self._iter_times += 1
self._solution_x = mp.get_solution_x()
self._obj_value = mp.get_obj_value()
status_str = {-1: "error", 0: "optimal", 1: "attain max iteration"}
print(">>> Terminated. Status:", status_str[self._status])
def print_info(self):
print("==== Result Info ====")
print(">>> objective value =", self._obj_value)
print(">>> Solution")
sku_list = [[]] * len(self._solution_x)
for i in range(len(self._solution_x)):
sku_list[i] = [j for j in range(len(self._solution_x[i])) if self._solution_x[i][j] > 0]
for i in range(len(self._solution_x)):
print("brand %d, sku list:" % i, sku_list[i])
主函数
# main.py
from data import p, c, b, d # instance data
from dw_proc import DWProc
if __name__ == '__main__':
dw = DWProc(p, c, d, b)
dw.solve()
dw.print_info()
参考文献
George B. Dantzig; Philip Wolfe. Decomposition Principle for Linear Programs. Operations Research. Vol 8: 101–111, 1960. ↩︎