Python实现动态规划Labeling算法求解SPPRC问题

本文中的课件来自清华大学深圳国际研究生院,物流与交通学部戚铭尧教授《物流地理信息系统》课程。

SPPRC问题

  带资源约束的最短路径问题(shortest path problem with resource constraints)是一个众所周知的NP-Hard问题。 除了作为网络问题直接应用外,SPPRC还用作列生成解决方案方法的基础,用于解决车辆路径规划问题和人员排班问题等。
  考虑一个有向图 G = ( N , A ) G = (N,A) G=(N,A) N = { v 1 , v 2 , . . . , v i , . . . , v n } N=\{v_1,v_2,...,v_i,...,v_n\} N={v1,v2,...,vi,...,vn}表示节点的集合,并且 A = { ( i , j ) ∣ v i ∈ N , v j ∈ N , i ≠ j } A=\{(i,j)|v_i \in N,v_j \in N ,i \ne j\} A={(i,j)viN,vjN,i=j}表示弧的集合。对于每一段弧 ( i , j ) ∈ A (i,j)\in A (i,j)A都有一个非负的权重(vrptw问题中可能为负,需要作特殊处理), c i j c_{ij} cij t i j t_{ij} tij,表示通过这段弧的成本和资源消耗。
  SPPRC问题包括找到从起始节点 v s ∈ N v_s \in N vsN到结束节点 v t ∈ N v_t \in N vtN的一条路径 P P P,使该路径的总成本最小化,但不超过最大资源消耗 T T T。即使只存在一种资源,SPPRC也是一个NP-Hard。

Labeling算法

  直接求解SPPRC问题是比较困难的,故通常采用一种动态规划的方法: l a b e l   c o r r e c t i o n   a l g o r i t h m label \ correction \ algorithm label correction algorithm算法。
通常我们考虑有 L L L种资源,可能包括时间、最大装载重量、最大装载体积等。
  对于每一条从起始节点 v s v_s vs扩展到点 v i v_i vi的路径 X p i X_{pi} Xpi都有一个标签 ( R i , C i ) (R_i,C_i) (Ri,Ci)与之相关联。 R i ≡ ( T i 1 , T i 2 , . . . , T i L ) R_i \equiv (T_i^1,T_i^2,...,T_i^ L) Ri(Ti1,Ti2,...,TiL)是路径使用的每L个资源的数量; C i C_i Ci c o s t cost cost
  此外,我们还需要设置一些优超规则,称为 d o m i n a n c e   r u l e dominance\ rule dominance rule,以帮助减少不必要的扩展。令 X p i X_{pi} Xpi X p i ∗ X_{pi}^* Xpi是从 v s v_s vs扩展到 v i v_i vi的两条不同的路径,与之关联的标签分别为 ( R i , C i ) (R_i,C_i) (Ri,Ci) ( R i ∗ , C i ∗ ) (R_i^*,C_i^*) (Ri,Ci)。路径 X p i X_{pi} Xpi可以 d o m i n a t e s dominates dominates路径 X p i ∗ X_{pi}^* Xpi当且仅当:
C i ≤ C i ∗ , T i l ≤ T i l ∗ , ∀   l ∈ ( 1 , . . . , L ) , ( R i , C i ) ≠ ( R i ∗ , C i ∗ ) C_i \le C_i^*,T_i^l\le T_i^{l*} ,\forall \ l \in (1,...,L), (R_i,C_i) \ne (R_i^*,C_i^*) CiCi,TilTil, l(1,...,L),(Ri,Ci)=(Ri,Ci)

下面介绍一个简单的小例子:
在这里插入图片描述
一个有向图如上图所示,我们可以看到每一段弧上都有一个非负的权重,分别表示通过该弧的 t i m e time time c o s t cost cost。首先初始化出发节点 p p p的标签,设置为 [ 0 , 0 ] [0,0] [0,0],随后令其向可达节点 v 2 v_2 v2 v 3 v_3 v3进行扩展,则分别设置一个新的标签 [ 2 , 2 ] [2,2] [2,2]。再由 v 2 v_2 v2 v 3 v_3 v3 d d d进行扩展,则 v 3 v_3 v3得到一个新的标签 [ 3 , 0 ] [3,0] [3,0] d d d得到一个新的标签 [ 4 , 4 ] [4,4] [4,4],同理继续扩展到 d d d,生成一个新的标签 [ 5 , 2 ] [5,2] [5,2]。然后回过头由 v 3 v_3 v3扩展到 v 2 v_2 v2 v 2 v_2 v2得到一个新的标签 [ 4 , 1 ] [4,1] [4,1]。我们可以发现没有继续向 d d d扩展了,因为其生成的标签为 [ 6 , 2 ] [6,2] [6,2],与 d d d已有的标签 [ 5 , 2 ] [5,2] [5,2]相比满足了 d o m i n a n c e   r u l e dominance\ rule dominance rule的要求,不必再扩展。

Python编程实现

在这里插入图片描述
考虑这样一个有向图。

首先在python中对该图进行定义:

import numpy as np
import networkx as nx
from time import *
# 点中包含时间窗属性
Nodes = {'s': (0, 0)
         , '1': (6, 14)
         , '2': (9, 12)
         , '3': (8, 12)
         , 't': (9, 15)
        }
# 弧的属性包括travel_time与distance
Arcs = {('s','1'): (8, 3)
        ,('s','2'): (5, 5)
        ,('s','3'): (12, 2)
        ,('1','t'): (4, 7)
        ,('2','t'): (2, 6)
        ,('3','t'): (4, 3)
       }

# create the directed Graph
Graph = nx.DiGraph()
cnt = 0
# add nodes into the graph
for name in Nodes.keys():
    cnt += 1
    Graph.add_node(name
                   , time_window = (Nodes[name][0], Nodes[name][1])
                   , min_dis = 0  
                   )
# add edges into the graph
for key in Arcs.keys():
    Graph.add_edge(key[0], key[1]
                   , travel_time = Arcs[key][0]
                   , length = Arcs[key][1]
                   )

创建Label类

class Label:
    path = []
    travel_time = 0
    distance = 0

# dominance rule
def dominate(Q, Path_set):
    QCopy = copy.deepcopy(Q)
    PathsCopy = copy.deepcopy(Path_set)
    
    # dominate Q
    for label in QCopy:
        for another_label in Q:
            if (label.path[-1] == another_label.path[
                -1] and label.time < another_label.time and label.dis < another_label.dis):
                Q.remove(another_label)
            print('dominated path (Q) : ', another_label.path)

    # dominate Paths
    for key_1 in PathsCopy.keys():
        for key_2 in PathsCopy.keys():
            if (PathsCopy[key_1].path[-1] == PathsCopy[key_2].path[-1]
                    and PathsCopy[key_1].travel_time < PathsCopy[key_2].travel_time
                    and PathsCopy[key_1].length < PathsCopy[key_2].length
                    and (key_2 in Path_set.keys())):
                Path_set.pop(key_2)
                print('dominated path (P) : ', PathsCopy[key_1].path)
                
    return Q, Path_set
    
# labeling algorithm
def Labeling_SPPRC(Graph, org, des):
    # initial Q
    Q = []
    Path_set = {}

    # creat initial label
    label = Label()
    label.path = [org]
    label.travel_time = 0
    label.distance = 0
    Q.append(label)

    count = 0

    while(len(Q) > 0):
        count += 1
        current_path = Q.pop()

        # extend the current label
        last_node  = current_path.path[-1]
        for child in Graph.successors(last_node):
            extended_path = copy.deepcopy(current_path)
            arc = (last_node, child)

            # check the feasibility
            arrive_time = current_path.travel_time + Graph.edges[arc]['travel_time']
            time_window = Graph.nodes[child]['time_window']
            if(arrive_time >= time_window[0] and arrive_time <= time_window[1] and last_node != des):
                extended_path.path.append(child)
                extended_path.travel_time += Graph.edges[arc]['travel_time']
                extended_path.distance += Graph.edges[arc]['length']
                Q.append(extended_path)

    Path_set[count] = current_path
    # 调用dominance rule
    Q, Path_set = dominance(Q, Path_set)
    
 # filtering Paths, only keep feasible solutions
    Path_set_copy = copy.deepcopy(Path_set)
    for key in Path_set_copy.keys():
        if(Path_set[key].path[-1] != des):
            Path_set.pop(key)

    # choose optimal solution
    opt_path = {}
    min_dis = 1e6
    for key in Path_set.keys():
        if(Path_set[key].distance < min_dis):
            min_dis = Path_set[key].distance
            opt_path[1] = Path_set[key]
            
    return Graph, Q, Path_set, opt_path

调用labeling算法计算

org = 's'
des = 't'
begin_time = time()
Graph, Q, Path_set, opt_path = Labeling_SPPRC(Graph, org, des)
end_time = time()
print("计算时间: ", end_time - begin_time)
print('optimal path : ', opt_path[1].path )
print('optimal path (distance): ', opt_path[1].distance)
print('optimal path (time): ', opt_path[1].travel_time)

最终运行结果如图所示:
在这里插入图片描述
我们也可以尝试计算更多的点,并且加入 l o a d   c a p a c i t y load \ capacity load capacity的约束,对 S o l o m o n   c 101. t x t Solomon \ c101.txt Solomon c101.txt算例的计算结果为:
在这里插入图片描述
S o l o m o n   c 1 _ 10 _ 1. T X T Solomon \ c1\_10\_1.TXT Solomon c1_10_1.TXT算例取300个客户点的计算结果为:
在这里插入图片描述
至于500个点往上,我的电脑就跑不出来啦。。。
感谢大家的阅读。

作者:夏旸,清华大学,工业工程系/深圳国际研究生院 (硕士在读)
刘兴禄,清华大学,清华伯克利深圳学院(博士在读)

欢迎大家关注公众号运小筹。
在这里插入图片描述

  • 7
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值