Python小白的數學建模課-21.關鍵路徑法
- 關鍵路徑法是基于進度網路模型的方法,用網路圖表示各項活動之間的相互關系,獲得在一定工期、成本、資源約束條件下的最優進度安排,
- NetworkX 提供了拓撲序列和關鍵路徑的函式,但沒有給出計劃網路分析的時間引數,如事件的最早開工時間、最晚結束時間,因此不能實作對計劃網路圖的分析和優化,
- 本文的案例給出了關鍵路徑演算法的完整例程,并同時計算事件的最早開工時間、最晚完成時間,以便讀者使用,
- 『Python小白的數學建模課 @ Youcans』帶你從數模小白成為國賽達人,
1. 關鍵路徑法簡介
一個大型工程或專案包括很多活動,關鍵路徑是專案中時間最長的活動順序,決定著可能的專案最短工期,
關鍵路徑法(Critical path method,CPM) 是一種基于進度網路模型的方法,用網路圖表示各項活動之間的相互關系,獲得在一定工期、成本、資源約束條件下的最優進度安排,
關鍵路徑法源于美國杜邦公司對于專案管理控制成本、減少工期的研究,1959年,Kelly 和 Walker 在論文 Critical Path Planning and Scheduling 中提出了關鍵路徑法的基本原理和方法:計算所有活動的工期,確定其最早開始 ES 和最早結束 EF、最晚開始 LS 和最晚結束 LF 的時間,按斬訓動的相互關系形成順序的網路邏輯 圖,找到必須的最長路徑即為關鍵路徑,
關鍵路徑法將專案分解成為多個獨立的活動并確定每個活動的工期,然后用邏輯關系(結束-開始、結束-結束、開始-開始和開始-結束)將活動連接,首先使用正推法(Forward pass),從起點開始向后計算,依次計算每個頂點(事件)的最早開始時間 ES;然后再使用逆推法(Backward pass),從終點開始向前計算,依次計算每個頂點(事件)的最遲結束時間 LF,進而可以求出每條邊(工序)的最早結束時間 EF 和最遲開始時間 LS,最早開始時間 ES 和最晚開始時間 LS 相等的邊,就是關鍵路徑上的邊,對應的工序是關鍵工序,
- ES:最早開始時間(Earliest Start),指某項活動能夠開始的最早時間,取決于該項活動的所有緊前作業的結束時間,由順推法計算 ES = max{EF(preceding activities)},
- EF:最早結束時間(Earliest Finish),指某項活動能夠完成的最早時間,EF = ES+DU, DU為該活動的持續時間,
- LF:最遲結束時間(Latest Finish),指為了保證整個專案按期完成的某項活動必須完成的最晚時間,取決于該項活動的所有緊后作業的最遲開始時間,由逆推法計算 LF = min{LS(successor activities)},
- LS:最遲開始時間(Latest Start),指為了保證整個專案按期完成的某項活動必須開始的最遲時間,LS = LF -DU,DU為該活動的持續時間,
- TF:總時差(Total float time),指在不影響總工期的條件下,一個活動可以利用的機動時間,TF = LF - EF,
- FF:自由時差(Free float time),指在不影響緊后作業最早開始時間的條件下,一個活動可能被延遲的時間,FF = min{ES(successor activities)} - EF,
由關鍵路徑法得到的最早/最晚的開始/結束時間并不一定就是專案進度計劃,而是把既定的引數(活動持續時間、邏輯關系、提前量、滯后量和其它制約條件)輸入進度模型后所得到的結果,表明活動可以在該時段內實施,
早期關鍵路徑法的表示方法都是箭線法(ADM),隨著計算機的發展,前導圖(PDM)逐漸成為主流方法,
2. 拓撲序列與關鍵路徑
2.1 拓撲序列
大型專案包括很多子專案,有些子專案沒有先決條件,可以安排在任何時間開始,有些子專案必須安排在其它子專案完成后才能開始,也就是需要以所有前序子專案的結束為先決條件,
通過有向圖可以直觀反映專案中各個子專案之間的關系,圖中的頂點代表活動,有向邊代表活動的先后關系,有向邊的起點活動是終點活動的先決條件,只有當邊的起點活動完成之后,才能開始終點活動,
這種以頂點表示活動、邊表示活動間先后關系的有向圖,稱為頂點活動網(Activity on vertex network,AOV網),
AOV網是一個有向無環圖,即不存在回路,有向無環圖的所有活動可排列成一個線性序列,使每個活動的所有前驅活動都排在該活動之前,稱為拓撲序列(Topological order),
拓撲序列的意義是,如果按照拓撲序列中的頂點次序開始活動,每個活動開始時它的所有前驅活動都已完成,從而使整個工程得以順序進行,
AOV 網和拓撲序列只考慮網路拓結構,也就是只有各個活動的先后順序,不考慮活動所需的時間和費用,因此,AOV 網的拓撲序列通常不是唯一的,而只是各種可行順序之一,
2.2 活動網路
**帶權的活動網路(Activity on edge network,AOE網),頂點表示事件或狀態,有向邊表示活動,邊上的權值通常表示活動的持續時間,**AOE網可以用來估算專案的完成時間,
注意 AOV網與 AOE網的區別,不僅在于邊是否帶權,AOV網的頂點代表活動(工序), 邊只表示先后關系;AOE網的頂點表示事件,邊表示工序,邊的權值表示完成工序所需的時間,
**AOE 網中從起點到終點的最長的加權路徑長度,稱為關鍵路徑(Critical path,CP) ,**關鍵路徑是專案中時間最長的活動順序,決定著可能的專案最短工期 ,優化關鍵路徑可以有效地加快設工程實施的進度,
3. NetworkX 的拓撲序列和關鍵路徑演算法
NetworkX 提供了有向無環圖的拓撲序列和關鍵路徑的函式,
3.1 拓撲序列函式 topological_sort()
| 函式 | 功能 |
|---|---|
| topological_sort(DG) | 回傳按拓撲排序的節點生成器 |
| all_topological_sorts(DG) | 回傳所有按拓撲排序的節點生成器 |
| is_directed_acyclic_graph(DG) | 檢查 DG 是否為有向無環圖 |
topological_sort(DG) 回傳有向無環圖 DG 的一個拓撲序列,回傳值的型別為 <class ‘generator’>,可以轉化為串列型別方便使用,
有向無環圖 DG 的拓撲序列不是唯一的,all_topological_sorts(DG) 回傳有向無環圖 DG 的全部拓撲序列,回傳值的型別為 <class ‘generator’>,可以轉化為二維串列型別方便使用,
如果 DG 不是有向圖,函式拋出錯誤提示"NetworkXNotImplemented",如果 DG 不是無環圖,函式拋出錯誤提示"NetworkXUnfeasible",
is_directed_acyclic_graph(DG) 可以檢查 DG 是否為有向無環圖,當 DG 為有向無環圖時,回傳值為 True,否則回傳 False,
3.2 關鍵路徑和路徑長度函式 dag_longest_path()
| 函式 | 功能 |
|---|---|
| dag_longest_path(DG) | 回傳 DG 的最長路徑 |
| dag_longest_path_length(DG) | 回傳 DG 的最長路徑長度 |
dag_longest_path(G, weight=‘weight’, default_weight=1, topo_order=None)
dag_longest_path_length(G, weight=‘weight’, default_weight=1)
主要引數:
- G(NetworkX graph):有向無環圖,
- weight (str, optional):按該字串查找邊的屬性作為權重,默認值 weight=“weight”,
回傳值:
- dag_longest_path() 的回傳值是 DG 最長路徑的頂點串列,也即關鍵路徑的節點串列,
- dag_longest_path_length() 的回傳值是 DG 最長路徑的組成邊的加權長度,也即關鍵路徑的長度,
引數和回傳值都非常簡單,用起來是很方便的,但是,NetworkX 工具包沒有提供計劃網路分析所需的事件時間引數,因此不能進行網路優化,
3.3 Python 例程:關鍵路徑法
問題描述:
某專案工程由 11項作業組成(分別用 A、B、…K表示),其計劃完成時間及作業間相互關系如下表所示,建立計劃網路圖,并計算完成該專案的最短時間,
| 作業 | 計劃完成天數 | 緊前工序 | 作業 | 計劃完成天數 | 緊前工序 |
|---|---|---|---|---|---|
| A | 5 | 無 | G | 21 | B,E |
| B | 10 | 無 | H | 35 | B,E |
| C | 11 | 無 | I | 25 | B,E |
| D | 4 | B | J | 15 | F,G,I |
| E | 4 | A | K | 20 | F,G |
| F | 15 | C,D |
本案例問題來自:司守奎、孫兆亮,數學建模演算法與應用(第2版),P62-68,例4.16-4.18,國防工業出版社,
問題分析:
用如下圖所示的計劃網路圖表示問題描述的各項作業及作業間的相互關系,圖中的頂點表示作業開始或結束的事件,頂點之間的邊(箭線)表示一項作業,邊的權值表示該項作業的完成時間,虛線邊表示虛擬作業,
該計劃網路圖的關鍵路徑長度,即為完成該專案的最短時間,

Python 例程:
# mathmodel23_v1.py
# Demo20 of mathematical modeling algorithm
# Demo of critical path method (CPM) with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-25
import numpy as np
import matplotlib.pyplot as plt # 匯入 Matplotlib 工具包
import networkx as nx # 匯入 NetworkX 工具包
# 1. 拓撲序列(topological sequence) 和 關鍵路徑(critical path)
# Activity on edge network(AOE), 頂點表示事件或狀態,有向邊表示活動
DG = nx.DiGraph() # 創建:空的 有向圖
DG.add_weighted_edges_from([(1, 2, 5), (1, 3, 10), (1, 4, 11),
(2, 5, 4),
(3, 4, 4), (3, 5, 0),
(4, 6, 15),
(5, 6, 21), (5, 7, 25), (5, 8, 35),
(6, 7, 0), (6, 8, 20),
(7, 8, 15)]) # 向圖中添加多條賦權邊: (n1,n2,weight)
lenNodes = len(DG.nodes) # 頂點數量
topoSeq = list(nx.topological_sort(DG)) # 拓撲序列
nodeCP = list(nx.dag_longest_path(DG)) # 關鍵路徑(節點)
lenCP = nx.dag_longest_path_length(DG) # 關鍵路徑的長度
edgesCP=[]
for k in range(1,len(nodeCP)):
edgesCP.append((nodeCP[k-1],nodeCP[k]))
print("拓撲序列:{}".format(topoSeq)) # [1, 3, 4, 2, 5, 6, 7, 8]
print("關鍵路徑的頂點:{}".format(nodeCP)) # [1, 3, 5, 6, 8]
print("關鍵路徑的邊:{}".format(edgesCP)) # [(1, 3), (3, 5), (5, 6), (6, 8)]
print("關鍵路徑長度:{}".format(lenCP)) # 51
fig, ax = plt.subplots(figsize=(8,6))
pos = {1:(0,4), 2:(5,7), 3:(5,4), 4:(5,1), 5:(10,7), 6:(10,1), 7:(15,4), 8:(20,4)} # 指定頂點位置
edgesDG = DG.edges
edgesDashed = [(3,5),(6,7)]
edgesSolid = list(set(edgesDG)-set(edgesDashed))
labels = nx.get_edge_attributes(DG, 'weight')
# nx.draw(DG, pos, with_labels=True, node_color='skyblue') # 繪制有向圖
nx.draw_networkx_nodes(DG, pos, node_color='orange',node_size=400) # 設定指定頂點的顏色、寬度
nx.draw_networkx_labels(DG, pos) # 設定指定頂點的標簽
nx.draw_networkx_edges(DG, pos, edgelist=edgesSolid, edge_color='dimgrey', style='solid') # 設定指定邊的顏色、線型
nx.draw_networkx_edges(DG, pos, edgelist=edgesDashed, edge_color='grey', style='dashed') # 設定指定邊,虛線
nx.draw_networkx_edge_labels(DG, pos, edge_labels=labels, font_color='dimgrey') # 顯示邊的權值
ax.set_title("Project network graph by youcans@xupt")
ax.text(16, 0, "youcans-xupt", color='gainsboro')
plt.xlim(-2, 22)
plt.ylim(-1, 9)
plt.axis('on')
plt.show() # YouCans, XUPT
程式運行結果:
拓撲序列:[1, 3, 4, 2, 5, 6, 7, 8]
關鍵路徑的頂點:[1, 3, 5, 6, 8]
關鍵路徑的邊:[(1, 3), (3, 5), (5, 6), (6, 8)]
關鍵路徑長度:51
4. 案例:計劃網路分析與優化
4.1 問題描述
本案例問題的內容與本文 3.3 中案例相同,
某專案工程由 11項作業組成(分別用 A、B、…K表示),其計劃完成時間及作業間相互關系如下表所示,求該專案的關鍵路徑,并計算每項作業的最早開工時間 ES、最晚開工時間 LS,
4.2 問題分析
NetworkX 雖然提供了拓撲序列和關鍵路徑的函式,但是沒有給出網路分析所需的時間引數,如事件的最早開工時間、最晚完成時間,不能實作對計劃網路圖的分析和優化,
網路上關于計劃網路圖的分析與優化的 Python 語言例程不多,有的例程并不正確或者并沒有調通,有的例程并不完整不能直接使用,
作者詳細研究了 NetworkX 相關內容的說明檔案,發現官方檔案及例程也有問題,主要是由于 NetworkX 版本更新導致檔案不匹配,
為此,本文的案例給出了關鍵路徑演算法的完整例程,并同時計算事件的最早開工時間、最晚完成時間,以便讀者使用,
為了便于閱讀、使用和修改程式,本程式采用了比較簡單易讀的程式結構——一些地方原本可以寫的更簡練,
4.3 程式說明
- AOE 圖的輸入,本例為稀疏的帶權有向圖,使用 G.add_weighted_edges_from() 函式可以使用串列向圖中添加多條賦權邊,每個賦權邊以元組 (node1,node2,weight) 表示,
- 圖中的頂點表示事件(狀態),邊表示問題中的作業工序,邊的權值表示完成作業所需的時間,注意,(3, 5, 0),(6, 7, 0) 表示虛作業,完成該作業所需時間(資源)為0,只是表示工序的前后關系,
- nx.topological_sort(DG) 生成一個拓撲序列,
- for e in DG.in_edges(topoSeq[i]) 表示遍歷頂點 topoSeq[i] 的入邊,由此可以得到其所有相鄰的前向頂點,各前向頂點的最早開始時間與連接邊的權值之和 VEij 最大者即為該頂點的最早開始時間,頂點的最早開始時間,要從起點開始,依次向后計算,直到終點結束,
- for e in DG.out_edges(revSeq[i]) 表示遍歷頂點revSeq[i] 的出邊,由此可以得到其所有相鄰的后向頂點,各后向頂點的最晚開始時間與連接邊的權值之差 VLij 最小者即為該頂點的最晚開始時間,頂點的最晚開始時間,要從終點開始,依次向前計算,直到起點結束,
- 各條邊(作業工序)的最早開始時間,是這條邊的起點的最早開始時間,各條邊(作業工序)的最晚開始時間,是這條邊的終點的最晚開始時間減去邊的權值,
- 關鍵路徑的計算:如果一條邊的最早、最晚開工時間相同,則這條邊是關鍵路徑上的邊,
4.4 Python 程式
# mathmodel23_v1.py
# Demo23 of mathematical modLSing algorithm
# Demo of critical path method (CPM) with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-26
import numpy as np
import matplotlib.pyplot as plt # 匯入 Matplotlib 工具包
import networkx as nx # 匯入 NetworkX 工具包
# 2. 關鍵路徑的時間引數(critical path method)
DG = nx.DiGraph() # 創建:空的 有向圖
# Activity on edge network(AOE), 頂點表示事件或狀態,有向邊表示活動
DG.add_nodes_from(range(1, 8), VE=0, VL=0)
DG.add_weighted_edges_from([(1, 2, 5), (1, 3, 10), (1, 4, 11),
(2, 5, 4),
(3, 4, 4), (3, 5, 0),
(4, 6, 15),
(5, 6, 21), (5, 7, 25), (5, 8, 35),
(6, 7, 0), (6, 8, 20),
(7, 8, 15)]) # 向圖中添加多條賦權邊: (node1,node2,weight)
lenNodes = len(DG.nodes) # 頂點數量 YouCans
topoSeq = list(nx.topological_sort(DG)) # 拓撲序列: [1, 3, 4, 2, 5, 7, 6, 8]
# --- 計算各頂點的 VE:事件最早時間 ---
VE = [0 for i in range(lenNodes)] # 初始化 事件最早時間
for i in range(lenNodes):
for e in DG.in_edges(topoSeq[i]): # 遍歷頂點 topoSeq[i] 的 入邊
VEij = DG.nodes[e[0]]["VE"] + DG[e[0]][e[1]]["weight"] # 該路線的最早時間
if VEij > VE[i]: VE[i] = VEij # 該路線所需時間更長
DG.add_node(topoSeq[i], VE=VE[i]) # 頂點(事件)的最早時間
# --- 計算各頂點的 VL:事件最晚時間 ---
revSeq = list(reversed(topoSeq)) # 翻轉拓撲序列,以便從終點倒推計算 VL
VL = [DG.nodes[revSeq[0]]["VE"] for i in range(lenNodes)] # 初始化 事件最晚時間為 VE 最大值
for i in range(lenNodes):
for e in DG.out_edges(revSeq[i]): # 遍歷頂點 revSeq[i] 的 出邊
VLij = DG.nodes[e[1]]["VL"] - DG[e[0]][e[1]]["weight"] # 該路線的最晚時間
if VLij < VL[i]: VL[i] = VLij # 該路線所需時間更長
DG.add_node(revSeq[i], VL=VL[i]) # 頂點(事件)的最晚時間
print("\n頂點(事件)的最早時間 VE, 最晚時間 VL:")
for n in DG.nodes: # 遍歷有向圖的頂點
print("\t事件 {}:\tVE= {}\tVL= {}".format(n, DG.nodes[n]["VE"], DG.nodes[n]["VL"]))
# --- 計算各條邊的 ES, LS:各項活動的最早開始時間、最晚開始時間 ---
cpDG = nx.DiGraph() # 創建空的有向圖, 保存關鍵路徑
print("\n邊(活動)的最早開始時間 ES, 最晚開始時間 LS:")
for e in DG.edges: # 遍歷有向圖的邊
DG[e[0]][e[1]]["ES"] = DG.nodes[e[0]]["VE"] # 邊的頭頂點的 VE
# Wij = DG[e[0]][e[1]]['weight']
DG[e[0]][e[1]]["LS"] = DG.nodes[e[1]]["VL"] - DG[e[0]][e[1]]["weight"] # 邊的尾頂點的 VL 減去邊的權值
if DG[e[0]][e[1]]["ES"] == DG[e[0]][e[1]]["LS"]: # 如果最早、最晚開工時間相同,則為關鍵路徑上的邊
cpDG.add_edge(e[0], e[1], weight=DG[e[0]][e[1]]["weight"]) # 加入 關鍵路徑
# print("\t作業 {}:\tES= {}\tLS= {}".format(e, DG[e[0]][e[1]]["ES"], DG[e[0]][e[1]]["LS"]))
print("\t作業 {}:\tES= {}\tLS= {}\tDU= {}".format(e, DG[e[0]][e[1]]["ES"], DG[e[0]][e[1]]["LS"], DG[e[0]][e[1]]["weight"]))
lenCP = sum(cpDG[e[0]][e[1]]["weight"] for e in cpDG.edges)
print("\n關鍵路徑:{}".format(cpDG.edges)) # YouCans, XUPT
print("專案最短工期:{}".format(lenCP))
# 繪制有向網路圖
fig, ax = plt.subplots(figsize=(8,6))
pos = {1:(0,4), 2:(5,7), 3:(5,4), 4:(5,1), 5:(10,7), 6:(10,1), 7:(15,4), 8:(20,4)} # 指定頂點位置
edgesDG = DG.edges
edgesDashed = [(3,5),(6,7)]
edgesSolid = list(set(edgesDG)-set(edgesDashed))
labels = nx.get_edge_attributes(DG, "weight") # YouCans, XUPT
nx.draw_networkx_nodes(DG, pos, node_color='orange',node_size=400) # 設定指定頂點的顏色、寬度
nx.draw_networkx_labels(DG, pos) # 設定指定頂點的標簽
nx.draw_networkx_edges(DG, pos, edgelist=edgesSolid, edge_color='dimgrey', style='solid') # 設定指定邊的顏色、線型
nx.draw_networkx_edges(DG, pos, edgelist=edgesDashed, edge_color='grey', style='dashed') # 設定指定邊,虛線
nx.draw_networkx_edge_labels(DG, pos, edge_labels=labels, font_color='dimgrey') # 顯示邊的權值
nx.draw_networkx_edges(DG, pos, edgelist=cpDG.edges, edge_color='r', width=2) # 設定指定邊的顏: 顯示關鍵路徑
ax.set_title("Project network graph by youcans@xupt")
ax.text(16, 0, "youcans-xupt", color='gainsboro')
plt.xlim(-2, 22)
plt.ylim(-1, 9)
plt.axis('on')
plt.show() # YouCans, XUPT
4.5 程式運行結果
頂點(事件)的最早時間 VE, 最晚時間 VL:
事件 1: VE= 0 VL= 0
事件 2: VE= 5 VL= 6
事件 3: VE= 10 VL= 10
事件 4: VE= 14 VL= 16
事件 5: VE= 10 VL= 10
事件 6: VE= 31 VL= 31
事件 7: VE= 35 VL= 36
事件 8: VE= 51 VL= 51
邊(活動)的最早開始時間 ES, 最晚開始時間 LS:
作業 (1, 2): ES= 0 LS= 1 DU= 5
作業 (1, 3): ES= 0 LS= 0 DU= 10
作業 (1, 4): ES= 0 LS= 5 DU= 11
作業 (2, 5): ES= 5 LS= 6 DU= 4
作業 (3, 4): ES= 10 LS= 12 DU= 4
作業 (3, 5): ES= 10 LS= 10 DU= 0
作業 (4, 6): ES= 14 LS= 16 DU= 15
作業 (5, 6): ES= 10 LS= 10 DU= 21
作業 (5, 7): ES= 10 LS= 11 DU= 25
作業 (5, 8): ES= 10 LS= 16 DU= 35
作業 (6, 7): ES= 31 LS= 36 DU= 0
作業 (6, 8): ES= 31 LS= 31 DU= 20
作業 (7, 8): ES= 35 LS= 36 DU= 15
關鍵路徑:[(1, 3), (3, 5), (5, 6), (6, 8)]
專案最短工期:51

【本節完】
著作權宣告:
歡迎關注『Python小白的數學建模課 @ Youcans』 原創作品
原創作品,轉載必須標注原文鏈接:(https://blog.csdn.net/youcans/article/details/118754623),
Copyright 2021 Youcans, XUPT
Crated:2021-07-26
歡迎關注 『Python小白的數學建模課 @ Youcans』 系列,持續更新
Python小白的數學建模課-01.新手必讀
Python小白的數學建模課-02.資料匯入
Python小白的數學建模課-03.線性規劃
Python小白的數學建模課-04.整數規劃
Python小白的數學建模課-05.0-1規劃
Python小白的數學建模課-06.固定費用問題
Python小白的數學建模課-07.選址問題
Python小白的數學建模課-09.微分方程模型
Python小白的數學建模課-10.微分方程邊值問題
Python小白的數學建模課-12.非線性規劃
Python小白的數學建模課-15.圖論的基本概念
Python小白的數學建模課-16.最短路徑演算法
Python小白的數學建模課-17.條件最短路徑演算法
Python小白的數學建模課-18.最小生成樹問題
Python小白的數學建模課-19.網路流優化問題
Python小白的數學建模課-20.網路流優化案例
Python小白的數學建模課-21.關鍵路徑法
Python小白的數學建模課-A1.國賽賽題型別分析
Python小白的數學建模課-A2.2021年數維杯C題探討
Python小白的數學建模課-A3.12個新冠疫情數模競賽賽題及短評
Python小白的數學建模課-B2. 新冠疫情 SI模型
Python小白的數學建模課-B3. 新冠疫情 SIS模型
Python小白的數學建模課-B4. 新冠疫情 SIR模型
Python小白的數學建模課-B5. 新冠疫情 SEIR模型
Python小白的數學建模課-B6. 新冠疫情 SEIR改進模型
Python數模筆記-PuLP庫
Python數模筆記-StatsModels統計回歸
Python數模筆記-Sklearn
Python數模筆記-NetworkX
Python數模筆記-模擬退火演算法
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/290439.html
標籤:python
