700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > 启发式算法求解混合整数线性优化问题—— 生产计划安排和资源分配

启发式算法求解混合整数线性优化问题—— 生产计划安排和资源分配

时间:2023-01-09 06:49:02

相关推荐

启发式算法求解混合整数线性优化问题—— 生产计划安排和资源分配

问题描述和范围限定:

生产计划安排分为两种:静态和动态计划。

静态计划生成的时间距离实际生产时间较长,以假设所有预设条件都满足为前提,在给定优化目标下(比如最小延迟,最低库存金额,etc.)寻找最优计划。静态计划一般采用优化算法实现。

动态计划基于静态计划,是在实际排产出现异常时(比如原材料供应不足,设备突然故障造成停线,上游产品突发质量问题,产线工人罢工,etc.)

这篇文章主要关注生成静态计划的优化算法,包括启发式算法和确切算法。更多动态计划的算法有机会再介绍。

这篇文章的优化算法适用于此类计划问题 1). 任务有截止日期,2). 资源为有限资源并且不可持续(比如设备利用率,不可持续性表现在剩余资源有实效且不可存储,过期无效),3).多资源多任务排产,4)任务目标为以最小库存成本在截止日期前完成生产。

各个任务间可以有层级关系,下游任务可以由多个上游任务合并形成,但是每个上游任务只能对应唯一的下游任务(树形产品结构,成品在根节点,原材料在叶节点,半成品在其他中间节点)。

以测试数据的产品结构为例:a和d为原材料,b,c,g为半成品,h为成品。其中g为assembly。在测试数据中,每个任务有唯一编码。

使用的算法:

Branch and Bound:常用的解决混合整数问题(MIP)的确切算法

确切算法(exact)指能保证找到最优解的一类算法(相对于启发式算法heuristic),在解决问题时往往由于需要搜索庞大的解空间而造成运算量过大运算时间过长。仍然有一些确切算法适合用于求解某一类特定问题,在问题规模可控的条件下能在可接受的时间内给出最优解。

Branch and Bound是一类经常用于求解MIP的确切算法,通过构造树形解空间并以边界形式对解空间剪枝达到有效搜索最优解的目的。在我们的生产日程安排问题中,branch and bound 以非最优有效解作为上边界,松弛问题的最优解(无效解)作为下边界,通过分枝和节点选择规则,将边界解不断分解为子问题分别求解,直到找到最优解。由于存在上下边界,大部分中间节点会因为超过已存在的有效解范围而被剪枝,从而快速缩小解空间,提高算法效率。

详细说明参考百度百科“分枝界限法”条目。

图片来源

Branch and bound算法的核心是如何得到每个节点上的上下边界。常用的方法是Lagrangian relaxation.

Lagrangian Relaxation:常用的线性优化问题降低求解难度的技术

一些由实际场景建模得到的线性优化问题由于存在大量约束条件,使精确求解此类方程组难度增大。Lagrangian relaxation是一类即能保留目标函数线性特点,又能将较难限制条件转化为目标函数一部分的常用松弛方法。此类松弛方程的解可以无限接近于原始方程组的解,因此常常作为原始解的限制边界,应用于组合优化算法中。

比如在我们的生产日程安排问题中,将使用Lagrangian relaxation方法得到节点的下边界,并在此基础上使用启发式算法(Lagrangian heuristic),得到有效的然而可能非最优的解作为节点上边界。

简单来说,假设原始问题为

P: minimize cTxc^TxcTx

subject to:

Ax≥bAx \geq bAx≥b (假设为难约束),Bx≤dBx \leq dBx≤d (假设为简单约束),x∈Z+nx \in \mathbb{Z}_{+}^nx∈Z+n​

可以转化为求解松弛问题

PL: minimize cTx+λT(b−Ax)c^Tx + \lambda^T(b - Ax)cTx+λT(b−Ax)

s.t. Bx≤dBx \leq dBx≤d,x∈Z+nx \in \mathbb{Z}_{+}^nx∈Z+n​

详细说明参考百度百科“拉格朗日松弛技术”条目

其中 $ \lambda $是Lagrangian multiplier(拉格朗日乘数)。对于Lagrangian multiplier的取值,一般采用subgradient方法迭代求解。

Subgradient:不可导函数的梯度法求极值

梯度法是常用的求解方程的方法。在给定目标下,首先随机选取一组方程的初始(无效)解,根据计算目标值和实际目标值的差异判断下一次迭代中尝试的解应该向哪个方向调整。梯度可以标识调整方向,是通过对损失函数求一阶导得到的。

然而在优化问题中,由于有限制条件的存在,损失函数往往不可导。所以不能采用梯度法,而是使用很相似的次梯度法标识每次迭代解的调整方向。

详细说明参考百度百科“次导数”条目

案例:工厂生产计划安排

测试数据

假设产品结构如上图。

a, d为独立原材料,b,c,g为生产过程中的半成品,h为成品。

作为测试数据,假设需要生产4件相同产品,唯一编号分别为任务名+1-4,产品结构以“上游任务”和“下游任务”两个字段连接,其中“下游任务”只可能有一个产品编号;“上游任务”可以有多个编号,代表装配任务。执行任务的设备分别为mk11-16,上游设备编号不能小于下游设备编号(启发式算法将从最下游设备开始排产)。其他如运行时间,库存成本,订单截止日期 etc.如下表所示:

建模构造原始问题

i: 当前任务编号

k: 当前设备编号

ϕ(i)\phi(i)ϕ(i): 当前任务的紧邻下游任务集(immediate successors)

φ(i)\varphi(i)φ(i): 当前任务对应的最终成品编号

Λ(i)\Lambda(i)Λ(i): 当前任务的紧邻上游任务集(immediate predecessors)

Mk: 将在当前设备上排产的任务集

F: 成品集

pip_ipi​: 任务 i 的运行时长

did_idi​: 任务 i 的成品截止时间

hih_ihi​: 任务 i 的每件每单位时间库存成本

L: 一个足够大的正值

求解任务sequence和开始时间:

sis_isi​: 任务 i 的开始时间

yijy_{ij}yij​: 0/1,如果为1则代表 i 在 j 前发生

原始日程安排问题为:

(P):minimizez=∑i∉Fhi(sϕ(i)−si)+∑i∈Fhi(di−si)(\mathrm{P}): \text{minimize } z = \sum_{i \notin F} h_i(s_{\phi(i)} - s_i) + \sum_{i \in F} h_i(d_i - s_i)(P):minimizez=∑i∈/​F​hi​(sϕ(i)​−si​)+∑i∈F​hi​(di​−si​)

s.t.

si+pi−sj≤L⋅(1−yij)fori,j∈Mk(i&lt;j)and∀ks_i + p_i - s_j \leq L \cdot (1 - y_{ij}) \text{ for } i, j \in M_k(i &lt; j) \text{ and } \forall ksi​+pi​−sj​≤L⋅(1−yij​)fori,j∈Mk​(i<j)and∀k, (2)

sj+pj−si≤L⋅yijfori,j∈Mk(i&lt;j)and∀ks_j + p_j - s_i \leq L \cdot y_{ij} \text{ for } i, j \in M_k(i &lt; j) \text{ and } \forall ksj​+pj​−si​≤L⋅yij​fori,j∈Mk​(i<j)and∀k, (3)

si+pi≤di,fori∈Fs_i + p_i \leq d_i, \text{for } i \in Fsi​+pi​≤di​,fori∈F, (4)

si+pi≤sϕ(i),fori∉Fs_i + p_i \leq s_{\phi(i)}, \text{for } i \notin Fsi​+pi​≤sϕ(i)​,fori∈/​F, (5)

si≥0,∀is_i \geq 0, \forall isi​≥0,∀i, (6)

yij=0,1fori,j∉Mk(i&lt;j)and∀ky_{ij} = {0,1} \text{ for } i,j \notin M_k(i &lt; j) \text{ and } \forall kyij​=0,1fori,j∈/​Mk​(i<j)and∀k. (7)

通过定义阶梯库存成本echelon inventory,可以进一步简化原始问题 § 为:

(P):minimizez=∑iei(dφ(i)−si)(\mathrm{P}): \text{minimize } z = \sum_{i} e_i(d_{\varphi(i)} - s_i)(P):minimizez=∑i​ei​(dφ(i)​−si​), (8)

其中:

ei≡hi−∑j∈Λ(i)hj,∀ie_i \equiv h_i - \sum_{j \in \Lambda(i)} h_j, \forall iei​≡hi​−∑j∈Λ(i)​hj​,∀i,

dφ(i)d_{\varphi(i)}dφ(i)​ 是 i 对应成品的截止时间。

建模和算法实现基于这篇论文。

将原始问题转化为松弛问题,并拆解为独立的单设备多任务排产松弛问题,分别求解:

通过松弛(4), (5)两项限制,可以得到新的松弛问题:

(LRλ):minimize∑∀i(λi−ei−∑j∈Λ(i)λj)si+∑∀i(eidφ(i)+λipi)−∑i∈Fλidi(\mathrm{LR}_\lambda): \text{ minimize } \sum_{\forall i} \big( \lambda_i - e_i - \sum_{j \in \Lambda(i)} \lambda_j \big) s_i + \sum_{\forall i} \big( e_i d_{\varphi(i)} + \lambda_i p_i \big) - \sum_{i \in F} \lambda_i d_i(LRλ​):minimize∑∀i​(λi​−ei​−∑j∈Λ(i)​λj​)si​+∑∀i​(ei​dφ(i)​+λi​pi​)−∑i∈F​λi​di​ (9)

s.t. (2), (3), (6), (7), 并且λi≥0∀i\lambda_i \geq 0 \forall iλi​≥0∀i.

用 L(λ)L(\lambda)L(λ)表示松弛问题(LRλ)(\mathrm{LR}_\lambda)(LRλ​)的最优解,即原始问题(P)(\mathrm{P})(P) 的下边界。

任意给定一组 λn\lambda_nλn​(拉格朗日乘数),都可以用来求解公式(9)而得出一个最优解 L(λn)L(\lambda_n)L(λn​)。所以松弛问题 (LRλ)(\mathrm{LR}_\lambda)(LRλ​) 转化为求解 (LRλ)(\mathrm{LR}_\lambda)(LRλ​) 的双对问题 :

(PL):maximizeL(λn)(\mathrm{PL}): \text{ maximize } L(\lambda_n)(PL):maximizeL(λn​) s.t. λ≥0\lambda \geq 0λ≥0。

继续把松弛问题 (LRλ)(\mathrm{LR}_\lambda)(LRλ​) 的双对问题 (PL)(\mathrm{PL})(PL) 分解,可以得到K个独立的单设备多任务排产问题

(DPk):minimize∑i∈Mk(λi−ei−∑j∈Λ(i)λj)si(\mathrm{DP}_k): \text{ minimize } \sum_{i \in M_k} \big( \lambda_i - e_i - \sum_{j \in \Lambda(i)} \lambda_j \big) s_i(DPk​):minimize∑i∈Mk​​(λi​−ei​−∑j∈Λ(i)​λj​)si​ (10)

s.t.

si+pi−sj≤L⋅(1−yij,∀i,j∈Mk(i&lt;j)s_i + p_i - s_j \leq L \cdot (1 - y_{ij}, \forall i, j \in M_k(i &lt; j)si​+pi​−sj​≤L⋅(1−yij​,∀i,j∈Mk​(i<j), (2’)

sj+pj−si≤L⋅yij,∀i,j∈Mk(i&lt;j)s_j + p_j - s_i \leq L \cdot y_{ij}, \forall i, j \in M_k(i &lt; j)sj​+pj​−si​≤L⋅yij​,∀i,j∈Mk​(i<j), (3’)

yij=0,1,∀i,j∈Mk(i&lt;j)y_{ij} = {0,1}, \forall i, j \in M_k(i &lt; j)yij​=0,1,∀i,j∈Mk​(i<j), (7’)

λi≥0,∀i∈Mk\lambda_i \geq 0, \forall i \in M_kλi​≥0,∀i∈Mk​ (11)

si≥lk,∀i∈Mks_i \geq l_k, \forall i \in M_ksi​≥lk​,∀i∈Mk​, (12)

si+pi≤uk,∀i∈Mks_i + p_i \leq u_k, \forall i \in M_ksi​+pi​≤uk​,∀i∈Mk​. (13)

由于在给定 λ\lambdaλ的情况下,松弛问题(LRλ)(\mathrm{LR}_\lambda)(LRλ​)的第二项和第三项为常数,所以在新的独立问题(DPk)(\mathrm{DP}_k)(DPk​)中省略了这两项,而只用 Lk(λ)L_k(\lambda)Lk​(λ)表示 (DPk)(\mathrm{DP}_k)(DPk​) 的解值,并且有:

L(λ)=∑k=1KLk(λ)+∑i(eidφ(i)+λipi)−∑i∈FλidiL(\lambda) = \sum_{k=1}^{K} L_k(\lambda) + \sum_i(e_i d_{\varphi(i)} + \lambda_i p_i) - \sum_{i \in F} \lambda_i d_iL(λ)=∑k=1K​Lk​(λ)+∑i​(ei​dφ(i)​+λi​pi​)−∑i∈F​λi​di​.

为了得到单设备多任务排产的近似解,使用了"通用加权最短时长优先"排序法(GWSPT)。

“加权”指 (DPk)(\mathrm{DP}_k)(DPk​) 中的权重项:

wi=(λi−ei−∑j∈Λ(i)λj)w_i = \big( \lambda_i - e_i - \sum_{j \in \Lambda(i)} \lambda_j \big)wi​=(λi​−ei​−∑j∈Λ(i)​λj​)

可以证明,当排序顺序按照 w / p 降序排列时,排序顺序为最优,即:

yij=1andyji=0ifwipi≥wjpjy_{ij} = 1 \text{ and } y_{ji} = 0 \text{ if } \cfrac{w_i}{p_i} \geq \cfrac{w_j}{p_j}yij​=1andyji​=0ifpi​wi​​≥pj​wj​​

根据wiw_iwi​的符号,可以将k设备的任务集 MkM_kMk​ 分为三个数据子集:

Mk+=i:wi&gt;0andi∈MkM_k^{+} = {i: w_i &gt; 0 \text{ and } i \in M_k}Mk+​=i:wi​>0andi∈Mk​

Mk0=i:wi=0andi∈MkM_k^{0} = {i: w_i = 0 \text{ and } i \in M_k}Mk0​=i:wi​=0andi∈Mk​

Mk−=i:wi&lt;0andi∈MkM_k^{-} = {i: w_i &lt; 0 \text{ and } i \in M_k}Mk−​=i:wi​<0andi∈Mk​

可以进一步证明,如果

Mk+M_k^{+}Mk+​ 子集的任务排序在 [lk,lk+∑i∈Mk+pi]\big[ l_k, l_k + \sum_{i \in M_k^{+}} p_i \big][lk​,lk​+∑i∈Mk+​​pi​] 区间,

Mk0M_k^{0}Mk0​ 子集的任务排序在 [lk+∑i∈Mk+pi,uk−∑i∈Mk−pi]\big[ l_k + \sum_{i \in M_k^{+}} p_i, u_k - \sum_{i \in M_k^{-}} p_i \big][lk​+∑i∈Mk+​​pi​,uk​−∑i∈Mk−​​pi​] 区间,

Mk−M_k^{-}Mk−​ 子集的任务排序在 [uk−∑i∈Mk−pi,uk]\big[ u_k - \sum_{i \in M_k^{-}} p_i, u_k \big][uk​−∑i∈Mk−​​pi​,uk​] 区间,

并分别按照GWSPT排序法排序,则(DPk)(\mathrm{DP}_k)(DPk​)可以得到最优近似解;

其中,对于每个设备的排产时间上下界lk,ukl_k, u_klk​,uk​的计算如下:

uk=maxi∈Mk{dφ(i)−∑j∈Φ(i)pj}u_k = max_{i \in M_k}\big\{d_{\varphi(i)} - \sum_{j \in \Phi(i)} p_j\big\}uk​=maxi∈Mk​​{dφ(i)​−∑j∈Φ(i)​pj​}, 其中 Φ(i)\Phi(i)Φ(i) 集合包含任务 i 和所有任务 i 的下游任务。

lk=mini∈Mk{minj∈Ψ(i)(∑l∈Θ(i,j)pl−pi)}l_k = min_{i \in M_k} \big\{min_{j \in \Psi(i)}\big(\sum_{l \in \Theta(i,j)} p_l - p_i \big)\big\}lk​=mini∈Mk​​{minj∈Ψ(i)​(∑l∈Θ(i,j)​pl​−pi​)} 其中 Ψ(i)\Psi(i)Ψ(i) 是所有任务 i 的上游任务中的原材料集合,Θ(i,j)\Theta(i,j)Θ(i,j)是从原材料到任务 i 的路径上的所有其他任务集合。

"通用加权最短时长优先"排序法(GWSPT)代码实现 (本文中的代码示例仅供参考。由于与实际代码的结构不同,可能有参数定义的偏差。另外比较长的函数由于篇幅原因没有展示:

def doGWSPT(df, lk, uk, plusTotal, minusTotal):mkGroup = df['mkGroup'].tolist()[0]df = df.sort_values(by='wOverp', ascending=False)sIdx = df.columns.tolist().index('startTime')pIdx = df.columns.tolist().index('processTime')if mkGroup == 'plus':startPoint = lkelif mkGroup == 'zero':startPoint = lk + plusTotalelse:startPoint = uk - minusTotaldf.iloc[0, sIdx] = startPointp = df.iloc[0, pIdx]for i in np.arange(df.shape[0] - 1):df.iloc[i+1, sIdx] = startPoint + pp += df.iloc[i+1, pIdx]return df

计算权重 ∑j∈Λ(i)λj\sum_{j \in \Lambda(i)} \lambda_j∑j∈Λ(i)​λj​的代码实现:

def calculateSigmaLambda_lambdaj(waitingList, row):immPre = row['immPre']if immPre == 'rawMaterial':return 0if not isinstance(immPre, list):immPre = [immPre]lambdaTotal = 0for pre in immPre:lambdaTotal += waitingList.loc[waitingList.parts == pre, 'lambdaIter'].tolist()[0]return lambdaTotal

计算单设备排产上下边界 uk,lku_k, l_kuk​,lk​的代码实现:

计算 ∑l∈Θ(i,j)pl−pi\sum_{l \in \Theta(i,j)} p_l - p_i∑l∈Θ(i,j)​pl​−pi​:

def findPhi(waitingList, product, stack=[]):row = waitingList.loc[waitingList.parts == product, :]immSuc = row['immSuc'].tolist()[0]processTime = row['processTime'].tolist()[0]finalProd = row['finalProduct'].tolist()[0]stack.append((product, processTime))if product != finalProd:findPhi(immSuc, stack)return stackdef findSigmaPhiPj(stack):Phi_pj = [x[1] for x in stack]sigmaPhi_pj = np.sum(Phi_pj)return sigmaPhi_pjdef calculateSigmaThetaPl(waitingList):tmp = waitingList.apply(lambda row:findSigmaPhiPj(row['parts']), axis=1)tmp.columns = ['sigmaPhi_pj', 'stack']waitingList = pd.concat([waitingList.reset_index(drop=True), tmp], axis=1)tmpList = waitingList.loc[waitingList.immPre=='rawMaterial','stack'].tolist()for i in np.arange(len(tmpList)):tmp = tmpList[i]rm = tmp[0][0] # raw material indexsigmaP = 0 # cumulative process time of all tasks between i and jfor j in np.arange(len(tmp) - 1):sigmaP += tmp[j][1]tmpTheta = pd.DataFrame([[tmp[j+1][0], rm, sigmaP]])tmpTheta.columns = ['i', 'j', 'sigmaThetaPl']sigmaThetaTbl = pd.concat([sigmaThetaTbl, tmpTheta], axis=0)return sigmaThetaTbl

计算 minj∈Ψ(i)(∑l∈Θ(i,j)pl−pi)min_{j \in \Psi(i)}\big(\sum_{l \in \Theta(i,j)} p_l - p_i \big)minj∈Ψ(i)​(∑l∈Θ(i,j)​pl​−pi​) :

def findMinPsiPj(product, sigmaThetaTbl):Psi_i = sigmaThetaTbl[sigmaThetaTbl['i'] == product]if Psi_i.shape[0] == 0: # rawMaterial node without further predecessorreturn 0else:return np.min(Psi_i['sigmaThetaPl'])

计算次梯度

给定任务 i 对应的拉格朗日乘数的初始值 λi0\lambda_i^0λi0​, 第n次迭代的结果如下:

λin+1={max{0,λin+tn(sin+pi−di)}∀i∈F,max{0,λin+tn(sin+Pi−sϕ(i)n)}∀i∉F,\lambda_i^{n+1} = \begin{cases} max\{0, \lambda_i^n + t_n(s_i^n + p_i - d_i)\} &amp; \quad \forall i \in F, \\ max\{0, \lambda_i^n + t_n(s_i^n + P_i - s_{\phi(i)}^n)\} &amp; \quad \forall i \notin F, \end{cases}λin+1​={max{0,λin​+tn​(sin​+pi​−di​)}max{0,λin​+tn​(sin​+Pi​−sϕ(i)n​)}​∀i∈F,∀i∈/​F,​

其中 (s1n,s2n,…sln)(s_1^n, s_2^n, \dots s_l^n)(s1n​,s2n​,…sln​) 是松弛问题 (LRλ)(\mathrm{LR}_\lambda)(LRλ​)在给定数组 λn\lambda^nλn下的一组最优解,

步长 tn=μn(z∗−L(λn))∑i∈F(sin+pi−d+i)2+∑i∉F(sin+pi−sϕ(i)n)2t_n = \cfrac{\mu_n(z^{*} - L(\lambda^n))}{\sum_{i \in F}(s_i^n + p_i - d+i)^2 + \sum_{i \notin F}(s_i^n + p_i - s_{\phi(i)}^n)^2}tn​=∑i∈F​(sin​+pi​−d+i)2+∑i∈/​F​(sin​+pi​−sϕ(i)n​)2μn​(z∗−L(λn))​,

μn\mu_nμn​是一个范围在(0, 2]的标量,当接近最优解时减小μn\mu_nμn​保证收敛速度,

z∗z^{*}z∗是 (PL)问题的一个上边界(有效解),迭代计算得出;L(λn)L(\lambda^n)L(λn) 是迭代计算的下边界。

另外设定 ω\omegaω为最大迭代次数;ϵ\epsilonϵ为迭代停止条件:当 (z∗−L(λn)/L(λn)&lt;ϵ(z^{*} - L(\lambda^n) / L(\lambda^n) &lt; \epsilon(z∗−L(λn)/L(λn)<ϵ时停止迭代;ζ\zetaζ为控制 μ\muμ值变小的参数:当最优解在 ζ\zetaζ次迭代中没有进步,则减小 μ\muμ值。典型设置为 e.g. ω=1000,ζ=10,ϵ=0.001\omega = 1000, \zeta = 10, \epsilon = 0.001ω=1000,ζ=10,ϵ=0.001。

计算次梯度的代码实现:

def updateSubgradient(waitingList, zStar, L_lambda):""" find best lambda value iteratively.input:zStar: upper bound on the optimal solution value.L(lambda): solution value to LR(lambda) given lambda.output:updated lambda value stored in waitingList as lambdaIter."""tn = calculateTn(zStar, L_lambda)f = waitingList[waitingList['immSuc']=='finalProduct']nf = waitingList[waitingList['immSuc']!='finalProduct']f['lambdaIter'] = f['lambdaIter'] + tn * (f['startTime'] + f['processTime'] - f['dueDate'])nf['lambdaIter'] = nf['lambdaIter'] + tn * (nf['startTime']+ nf['processTime'] - nf['s_phi']) f['lambdaIter'] = np.where(f['lambdaIter'] < 0, 0, f['lambdaIter'])nf['lambdaIter'] = np.where(nf['lambdaIter'] < 0, 0, nf['lambdaIter'])waitingList = pd.concat([f, nf], axis=0)return waitingListdef calculateTn(waitingList, zStar, L, mu, ):numerator = mu * (zStar - L)f = waitingList[waitingList.immSuc == 'finalProduct']f = np.power(f.startTime + f.processTime - f.dueDate, 2)nf = waitingList[waitingList.immSuc != 'finalProduct']nf = np.power(nf.startTime + nf.processTime - nf.s_phi, 2)tn = numerator / (np.sum(f) + np.sum(nf))return tn

求解上边界z∗z^{*}z∗ :

拉格朗日启发式算法伪代码:

在设备 k 上给定MkM_kMk​集的初始排产顺序 ρk\rho_kρk​

根据同一设备的任务集MkM_kMk​的下边界解,调整各任务的开始时间如下:

设所有在 k 设备上,且任务开始时间晚于 i 的任务集为 Γ(i)={j:yij=1},∀i∈Mk\Gamma(i) = \{j: y_{ij} = 1\}, \forall i \in M_kΓ(i)={j:yij​=1},∀i∈Mk​,重设任务 i 的截止时间为 di=sϕ(i),∀i∈Mkd_i = s_{\phi(i)}, \forall i \in M_kdi​=sϕ(i)​,∀i∈Mk​重设任务 i 的开始时间为 si=min{di,minj∈Γ(i)sj}−pi,∀i∈Mks_i = min \{d_i, min_{j \in \Gamma(i)} s_j \} - p_i, \forall i \in M_ksi​=min{di​,minj∈Γ(i)​sj​}−pi​,∀i∈Mk​

从MkM_kMk​的最晚任务 l 开始,遍历MkM_kMk​集合的所有任务 i :

如果任务 i 的截止时间 di&gt;dl+pid_i &gt; d_l + p_idi​>dl​+pi​:把任务 i 排到MkM_kMk​的最后一位(最晚开始) 否则:从 l 开始,逐一检查排在 l 和 i 中间的任务 h,如果有 sh−(sh−1+ph−1)≥pis_h - (s_{h-1} + p_{h-1}) \geq p_ish​−(sh−1​+ph−1​)≥pi​ 并且 (sh−1+ph−1)+pi≤di(s_{h-1} + p_{h-1}) + p_i \leq d_i(sh−1​+ph−1​)+pi​≤di​,则把任务 i 插在h 和 h-1之间。

迭代更新 sϕ(i)s_{\phi(i)}sϕ(i)​ 代码实现:

def updateSphi(waitingList, _tbl):tbl = _tbl.copy()try: tbl.drop('s_phi', axis=1, inplace=True)except ValueError: passs = waitingList.loc[:, ['parts', 'startTime']]s.columns = ['immSuc', 's_phi']tbl = tbl.merge(s, how='left', on='immSuc')tbl.loc[tbl['s_phi'].isnull(), 's_phi'] = (tbl.loc[tbl['s_phi'].isnull(), 'dueDate'])return tbl

迭代计算minj∈Γ(i)sjmin_{j \in \Gamma(i)} s_jminj∈Γ(i)​sj​的代码实现:

def calculateMinGammaSj(_mkTbl, sortedAsc=False):if sortedAsc is False:mkTbl = _mkTbl.sort_values(by='startTime', ascending=True)else:mkTbl = _mkTbl.copy()sj = mkTbl['startTime'].tolist()sj.pop(0)sj.append(99999)mkTbl['minGammaSj'] = sjreturn mkTbl

重设任务 i 开始时间的代码实现:

def adjustStartTimeForUB(row):if row['parts'] == row['finalProduct']:si = (np.min([row['dueDate'], row['minGammaSj']]) - row['processTime'])else:si = (np.min([row['s_phi'], row['minGammaSj']])- row['processTime'])return si

求解上边界的启发算法代码实现:

def findUpperBound(waitingList):""" find upper bound (feasible solution) to the problem (PL).mk's are indexed such that a machine does not have a larger index than its upstream predecessor machines."""mk = list(set(waitingList.equipment))mk.sort() # start from smallest index (latest down-stream machine)newTbl = pd.DataFrame()Lk_lambda = []for equipment in mk:mkTbl = waitingList[waitingList.equipment == equipment]mkTblIdx = mkTbl.indexmkTbl = updateSphi(mkTbl) # will change index. be ware.mkTbl.index = mkTblIdx# step 0mkTbl = mkTbl.sort_values(by='startTime', ascending=True)rho = mkTbl.index.tolist()# step 1# calculate minimum start time of j in set Gamma(i), # as basis to finding start times for upper bound# (equation 16 in paper)mkTbl = calculateMinGammaSj(mkTbl, sortedAsc=True)# based on minGammaSj, calculate new start time simkTbl['startTime'] = mkTbl.apply(lambda row: adjustStartTimeForUB(row), axis=1)mkTbl['dueDate_dummy'] = mkTbl['s_phi']mkTbl.loc[mkTbl.parts==mkTbl.finalProduct, 'dueDate_dummy'] = mkTbl.loc[mkTbl.parts==mkTbl.finalProduct,'dueDate']L = mkTbl.shape[0] - 1# step 2if L > 0: # if there are at least two items in mkTbl:for l in np.arange(L-1, -1, step=-1, dtype=int):# step 3:dIdx = mkTbl.columns.tolist().index('dueDate_dummy')pIdx = mkTbl.columns.tolist().index('processTime')sIdx = mkTbl.columns.tolist().index('startTime')d_l = mkTbl.iloc[l, dIdx]d_mk = mkTbl.iloc[-1, dIdx]p_l = mkTbl.iloc[l, pIdx]if d_l < d_mk + p_l:# step 4:for h in np.arange(L, l, step=-1, dtype=int):s_h = mkTbl.iloc[h, sIdx]s_h_1 = mkTbl.iloc[h-1, sIdx]p_h_1 = mkTbl.iloc[h-1, pIdx]p_l = mkTbl.iloc[l, pIdx]if (s_h - (s_h_1 + p_h_1) >= p_land (s_h_1 + p_h_1) + p_l <= d_l):tmp = rho.pop(l)rho.insert(h, tmp)mkTbl = mkTbl.loc[rho, :]# update task start timemkTbl.iloc[-1, sIdx] = (mkTbl.iloc[-2, sIdx]+ mkTbl.iloc[-2, pIdx])# update minGammaSj for allmkTbl = calculateMinGammaSj(mkTbl, sortedAsc=True)# adjust start time based on equation 16mkTbl['startTime'] = mkTbl.apply(lambda row: adjustStartTimeForUB(row),axis=1)else:# move l-th task to the endtmp = rho.pop(l)rho.append(tmp)mkTbl = mkTbl.loc[rho, :]# update task start timemkTbl.iloc[-1, sIdx] = (mkTbl.iloc[-2, sIdx] + mkTbl.iloc[-2, pIdx])# update minGammaSj for allmkTbl = calculateMinGammaSj(mkTbl, sortedAsc=True)# adjust start time based on equation 16mkTbl['startTime'] = mkTbl.apply(lambda row: adjustStartTimeForUB(row), axis=1)# update waitingList with new down-stream start times,# so the s_phi of upstream machines can be updated in the # following iterations.waitingList.loc[mkTbl.index, 'startTime'] = mkTbl['startTime']mkTbl.drop('dueDate_dummy', axis=1, inplace=True)# calculate solution value to the independent problems (DPk)mkTbl['DPkValue'] = (mkTbl['weight'] * mkTbl['startTime']) Lk_lambda.append(np.sum(mkTbl['DPkValue']))newTbl = pd.concat([newTbl, mkTbl], axis=0)waitingList = newTbl.copy() # calculate upper bound of solution value to the # problem (PL): maximum(L_lambda)zStar = (np.sum(Lk_lambda) + np.sum(newTbl['secondTerm'])- np.sum(newTbl['thirdTerm']))return waitingList, zStar

求解Lagrangian relaxation的代码实现过长,此处不再赘述。

实现branch and bound

节点选择伪代码:

给定当前节点的设备 mk给定当前节点的随机任务 row给定已确定的下游排产计划 set S给定仍未排产的上游任务集 set P’给定判断上边界 UB

createNode:

将row添加到set S,假设作为已确定任务排产将row从set P’中删除将set P’作为Lagrangian heuristic的输入,由启发式算法迭代得到新解的上下边界

分枝伪代码:

给定初始有效解(来自Lagrangian heuristic)z*给定初始上边界UB给定包含全部设备的数组machineList,按升序排列

branchOut:

将z*中最下游设备上开始时间最晚的任务作为根节点添加到空集set S将根节点对应的任务从set P’中删除设当前节点设备mk为machinList的第一个元素迭代,直到迭代结果的上边界不再进步,或达到最大迭代次数: 设当前设备mk上所有未排产的任务为数据集 s_k如果s_k的长度为1: 直接定义s_k中的任务生产日期将s_k中的任务添加到set S中将s_k中的任务从set P’中删除清空s_k 如果s_k的长度为0: 设mk为machineList中的下一个元素 否则: 将s_k中的每个任务分别作为输入执行createNode,返回所有新解,及其上下界如果没有找到有效解,或新解的下边界大于已存在的有效解的上边界,则删除节点,不再分枝否则: 如果新解的上边界小于(优于)已存在的有效解,则用新解替换作为新的最优有效解更新上边界 选择所有保留的新节点中下边界最小的节点的set S和set P’,代替对应的旧数据集进入下一轮迭代

实现branch and bound的代码过长,此处不再赘述。

测试案例结果:

使用分枝限界法可以得到比单纯解松弛问题,或使用启发式算法更好的优化结果。而基于松弛问题和启发式算法的结果,可以使分枝限界法的计算效率大幅度提高。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。