Python實(shí)現(xiàn)關(guān)鍵路徑和七格圖計(jì)算詳解
關(guān)鍵路徑計(jì)算是項(xiàng)目管理中關(guān)于進(jìn)度管理的基本計(jì)算。 但是對(duì)于絕大多數(shù)同學(xué)來說, 關(guān)鍵路徑計(jì)算都只是對(duì)一些簡(jiǎn)單情形的計(jì)算。
今天,田老師根據(jù)以往的經(jīng)驗(yàn),用Python實(shí)現(xiàn)了在相對(duì)較復(fù)雜的情形下:
1.關(guān)鍵路徑計(jì)算
2.七格圖計(jì)算,包括
- 最早結(jié)束時(shí)間EFT
- 最晚開始時(shí)間LST
- 最晚結(jié)束時(shí)間LFT
- 總浮動(dòng)時(shí)間TF
- 自由浮動(dòng)時(shí)間FF
3.緊前關(guān)系繪圖法(AON)
并且, 將輸出結(jié)果保存為網(wǎng)頁。 而且在網(wǎng)頁底部附上了完整的計(jì)算過程。

這個(gè)Python程序的實(shí)現(xiàn),可以幫助項(xiàng)目管理者更加方便地進(jìn)行關(guān)鍵路徑計(jì)算和進(jìn)度管理。同時(shí),通過輸出結(jié)果保存為網(wǎng)頁的形式,也可以方便地與團(tuán)隊(duì)成員進(jìn)行共享和交流。
1.主程序
主程序主要實(shí)現(xiàn)了一個(gè)Project類,其中包含了計(jì)算關(guān)鍵路徑和七格圖的方法。具體實(shí)現(xiàn)方式如下:
1. 定義了一個(gè)Activity類,包含了活動(dòng)的id、名稱、持續(xù)時(shí)間和緊前任務(wù)列表等屬性。
2. 定義了一個(gè)Project類,包含了活動(dòng)列表、項(xiàng)目持續(xù)時(shí)間、日志等屬性,以及計(jì)算關(guān)鍵路徑、計(jì)算七格圖、計(jì)算總浮動(dòng)時(shí)間、計(jì)算自由浮動(dòng)時(shí)間等方法。
3. 從JSON文件中讀取活動(dòng)信息,并創(chuàng)建Project對(duì)象并添加活動(dòng)。
4. 調(diào)用Project對(duì)象的calculate方法,計(jì)算每個(gè)活動(dòng)的最早開始時(shí)間、最晚開始時(shí)間等數(shù)據(jù)。
5. 調(diào)用Project對(duì)象的calculate_critical_path方法,計(jì)算關(guān)鍵路徑。
6. 調(diào)用Project對(duì)象的calculate_project_duration方法,計(jì)算項(xiàng)目總工期。
7. 使用Jinja2模板引擎生成項(xiàng)目的活動(dòng)清單,并將關(guān)鍵路徑
import json
from datetime import datetime
from typing import List
import graphviz
from jinja2 import Template
from activity import Activity
class Project:
def __init__(self):
self.activities: List[Activity] = []
self.duration = 0
self.logger = []
def log(self, log: str) -> None:
self.logger.append(log)
def add_activity(self, activity: Activity) -> None:
"""
添加一個(gè)活動(dòng)到項(xiàng)目中
:param
activity: 待添加的活動(dòng)
""" # 將活動(dòng)添加到項(xiàng)目中
self.activities.append(activity)
def calculate(self) -> None:
""" 計(jì)算整個(gè)項(xiàng)目的關(guān)鍵信息
:return: None
""" self.calculate_successor()
self._calculate_forward_pass() # 計(jì)算正推法
self._calculate_backward_pass() # 計(jì)算倒推法
self._calculate_total_floats() # 計(jì)算總浮動(dòng)時(shí)間
self._calculate_free_floats() # 計(jì)算自由浮動(dòng)時(shí)間
def calculate_successor(self) -> None:
self.log("開始計(jì)算緊后活動(dòng)")
for act in self.activities:
for pred in act.predecessors:
for act_inner in self.activities:
if act_inner.id == pred:
act_inner.successors.append(act.id)
def _calculate_forward_pass(self) -> None:
self.log("## 開始正推法計(jì)算")
# 進(jìn)入 while 循環(huán),只有當(dāng)所有活動(dòng)的最早開始時(shí)間和最早完成時(shí)間都已經(jīng)計(jì)算出來時(shí),才會(huì)退出循環(huán)
while not self._is_forward_pass_calculated():
# 遍歷每個(gè)活動(dòng)
for activity in self.activities:
# 如果活動(dòng)的最早開始時(shí)間已經(jīng)被計(jì)算過,則跳過
if activity.est is not None:
continue
# 如果活動(dòng)沒有前置活動(dòng), 則從1開始計(jì)算最早開始時(shí)間和最早結(jié)束時(shí)間
if not activity.predecessors:
activity.est = 1
activity.eft = activity.est + activity.duration - 1
self.log(
f"活動(dòng) {activity.name} 沒有緊前活動(dòng),設(shè)定最早開始時(shí)間為1, 并根據(jù)工期計(jì)算最早結(jié)束時(shí)間為{activity.eft}")
else:
# 計(jì)算當(dāng)前活動(dòng)的所有前置活動(dòng)的最早完成時(shí)間
predecessors_eft = [act.eft for act in self.activities if
act.id in activity.predecessors and act.eft is not None]
# 如果當(dāng)前活動(dòng)的所有前置活動(dòng)的最早完成時(shí)間都已經(jīng)計(jì)算出來,則計(jì)算當(dāng)前活動(dòng)的最早開始時(shí)間和最早完成時(shí)間
if len(predecessors_eft) == len(activity.predecessors):
activity.est = max(predecessors_eft) + 1
activity.eft = activity.est + activity.duration - 1
self.log(
f"活動(dòng) {activity.name} 緊前活動(dòng)已完成正推法計(jì)算, 開始日期按最早開始時(shí)間里面最大的," +
f"設(shè)定為{activity.est}并根據(jù)工期計(jì)算最早結(jié)束時(shí)間為{activity.eft}")
# 更新項(xiàng)目總持續(xù)時(shí)間為最大最早完成時(shí)間
self.duration = max([act.eft for act in self.activities])
def _calculate_backward_pass(self) -> None:
""" 計(jì)算倒推法
:return: None
"""
self.log("## 開始倒推法計(jì)算") # 輸出提示信息
# 進(jìn)入 while 循環(huán),只有當(dāng)所有活動(dòng)的最晚開始時(shí)間和最晚完成時(shí)間都已經(jīng)計(jì)算出來時(shí),才會(huì)退出循環(huán)
while not self._is_backward_pass_calculated():
# 遍歷每個(gè)活動(dòng)
for act in reversed(self.activities):
# 如果活動(dòng)的最晚開始時(shí)間已經(jīng)被計(jì)算過,則跳過
if act.lft is not None:
continue
# 如果活動(dòng)沒有后繼活動(dòng), 則從總持續(xù)時(shí)間開始計(jì)算最晚開始時(shí)間和最晚結(jié)束時(shí)間
if not act.successors:
act.lft = self.duration
act.lst = act.lft - act.duration + 1
self.log(f"活動(dòng) {act.name} 沒有緊后活動(dòng),按照正推工期設(shè)定最晚結(jié)束時(shí)間為{act.lft}," +
f"并根據(jù)工期計(jì)算最晚開始時(shí)間為{act.lst}")
else:
# 計(jì)算當(dāng)前活動(dòng)的所有后繼活動(dòng)的最晚開始時(shí)間
successors_lst = self._calculate_lst(act)
# 如果當(dāng)前活動(dòng)的所有后繼活動(dòng)的最晚開始時(shí)間都已經(jīng)計(jì)算出來,則計(jì)算當(dāng)前活動(dòng)的最晚開始時(shí)間和最晚完成時(shí)間
if len(successors_lst) == len(act.successors):
act.lft = min(successors_lst) - 1
act.lst = act.lft - act.duration + 1
self.log(f"活動(dòng) {act.name} 緊后活動(dòng)計(jì)算完成,按照倒推工期設(shè)定最晚結(jié)束時(shí)間為{act.lft}," +
f"并根據(jù)工期計(jì)算最晚開始時(shí)間為{act.lst}")
# 更新項(xiàng)目總持續(xù)時(shí)間為最大最晚完成時(shí)間
self.duration = max([act.lft for act in self.activities])
def _calculate_lst(self, activity: Activity) -> List[int]:
"""計(jì)算某一活動(dòng)的所有最晚開始時(shí)間
:param activity: 活動(dòng)對(duì)象
:return: 最晚開始時(shí)間列表
""" rst = [] # 初始化結(jié)果列表
for act in activity.successors: # 遍歷該活動(dòng)的后繼活動(dòng)
for act2 in self.activities: # 遍歷所有活動(dòng)
if act2.id == act and act2.lst is not None: # 如果找到了該后繼活動(dòng)且其最晚開始時(shí)間不為空
rst.append(act2.lst) # 將最晚開始時(shí)間加入結(jié)果列表
return rst # 返回結(jié)果列表
def _is_forward_pass_calculated(self) -> bool:
""" 判斷整個(gè)項(xiàng)目正推法計(jì)算已經(jīng)完成
:return: 若已計(jì)算正向傳遞則返回True,否則返回False
"""
for act in self.activities: # 遍歷所有活動(dòng)
if act.est is None or act.eft is None: # 如果該活動(dòng)的最早開始時(shí)間或最早完成時(shí)間為空
return False # 則返回False,表示還未計(jì)算正向傳遞
return True # 如果所有活動(dòng)的最早開始時(shí)間和最早完成時(shí)間都已計(jì)算,則返回True,表示已計(jì)算正向傳遞
def _is_backward_pass_calculated(self) -> bool:
""" 判斷整個(gè)項(xiàng)目倒推法計(jì)算已經(jīng)完成
:return: 若已計(jì)算倒推法則返回True,否則返回False
""" for act in self.activities: # 遍歷所有活動(dòng)
if act.lst is None or act.lft is None: # 如果該活動(dòng)的最晚開始時(shí)間或最晚完成時(shí)間為空
return False # 則返回False,表示還未計(jì)算倒推法
return True # 如果所有活動(dòng)的最晚開始時(shí)間和最晚完成時(shí)間都已計(jì)算,則返回True,表示已計(jì)算倒推法
def _calculate_total_floats(self) -> None:
""" 計(jì)算所有活動(dòng)的總浮動(dòng)時(shí)間
:return: None
"""
self.log(f"## 開始計(jì)算項(xiàng)目所有活動(dòng)的總浮動(dòng)時(shí)間")
for act in self.activities: # 遍歷所有活動(dòng)
if act.est is not None and act.lst is not None: # 如果該活動(dòng)的最早開始時(shí)間和最晚開始時(shí)間都已計(jì)算
act.tf = act.lst - act.est # 則計(jì)算該活動(dòng)的總浮動(dòng)時(shí)間
self.log(f"計(jì)算{act.name}的總浮動(dòng)時(shí)間" + f"最晚開始時(shí)間{act.lst} - 最早開始時(shí)間{act.est} = {act.tf}", )
else: # 如果該活動(dòng)的最早開始時(shí)間或最晚開始時(shí)間為空
act.tf = None # 則將該活動(dòng)的總浮動(dòng)時(shí)間設(shè)為None
def _calculate_free_floats(self) -> None:
""" 計(jì)算所有活動(dòng)的自由浮動(dòng)時(shí)間
:return: None
""" self.log(f"## 開始計(jì)算項(xiàng)目所有活動(dòng)的自由浮動(dòng)時(shí)間") # 輸出提示信息
for act in self.activities: # 遍歷所有活動(dòng)
if act.tf == 0: # 如果該活動(dòng)的總浮動(dòng)時(shí)間為0
self.log(f"計(jì)算{act.name}的自由浮動(dòng)時(shí)間" + f"因?yàn)閧act.name}的總浮動(dòng)時(shí)間為0,自由浮動(dòng)時(shí)間為0") # 輸出提示信息
act.ff = 0 # 則將該活動(dòng)的自由浮動(dòng)時(shí)間設(shè)為0
elif act.tf > 0: # 如果該活動(dòng)的總浮動(dòng)時(shí)間大于0
self.log(f"計(jì)算{act.name}的自由浮動(dòng)時(shí)間") # 輸出提示信息
self.log(f"- {act.name}的總浮動(dòng)時(shí)間{act.tf} > 0,") # 輸出提示信息
tmp = [] # 初始化臨時(shí)列表
for act2 in self.activities: # 遍歷所有活動(dòng)
if act2.id in act.successors: # 如果該活動(dòng)是該活動(dòng)的緊后活動(dòng)
self.log(f"- {act.name}的緊后活動(dòng){act2.name}的自由浮動(dòng)動(dòng)時(shí)間為{act2.tf}") # 輸出提示信息
tmp.append(act2.tf) # 將該緊后活動(dòng)的自由浮動(dòng)時(shí)間加入臨時(shí)列表
if len(tmp) != 0: # 如果臨時(shí)列表不為空
act.ff = act.tf - max(tmp) # 則計(jì)算該活動(dòng)的自由浮動(dòng)時(shí)間
if act.ff < 0:
act.ff = 0
self.log(f"- 用活動(dòng)自己的總浮動(dòng){act.tf}減去多個(gè)緊后活動(dòng)總浮動(dòng)的最大值{max(tmp)} = {act.ff}")
else: # 如果臨時(shí)列表為空
act.ff = act.tf # 則將該活動(dòng)的自由浮動(dòng)時(shí)間設(shè)為總浮動(dòng)時(shí)間
def calculate_critical_path(self) -> List[Activity]:
""" 計(jì)算整個(gè)項(xiàng)目的關(guān)鍵路徑
:return: 整個(gè)項(xiàng)目的關(guān)鍵路徑
""" ctc_path = [] # 初始化關(guān)鍵路徑列表
for act in self.activities: # 遍歷所有活動(dòng)
if act.tf == 0: # 如果該活動(dòng)的總浮動(dòng)時(shí)間為0
ctc_path.append(act) # 則將該活動(dòng)加入關(guān)鍵路徑列表
return ctc_path # 返回關(guān)鍵路徑列表
def calculate_project_duration(self) -> int:
""" 計(jì)算整個(gè)項(xiàng)目的持續(xù)時(shí)間
:return: 整個(gè)項(xiàng)目的持續(xù)時(shí)間
""" return max(activity.eft for activity in self.activities) # 返回所有活動(dòng)的最早完成時(shí)間中的最大值,即整個(gè)項(xiàng)目的持續(xù)時(shí)間
# 從JSON文件中讀取活動(dòng)信息
with open('activities.json', 'r', encoding='utf-8') as f:
activities_data = json.load(f)
# 創(chuàng)建Project對(duì)象并添加活動(dòng)
project = Project()
for activity_data in activities_data:
activity = Activity(
activity_data['id'],
activity_data['name'],
activity_data['duration'],
activity_data['predecessors']
) project.add_activity(activity)
# 計(jì)算每個(gè)活動(dòng)的最早開始時(shí)間、最晚開始時(shí)間等數(shù)據(jù)
project.calculate()
# 計(jì)算關(guān)鍵路徑和項(xiàng)目總工期
critical_path = project.calculate_critical_path()
project_duration = project.calculate_project_duration()
# 生成項(xiàng)目的活動(dòng)清單
with open('template.html', 'r', encoding='utf-8') as f:
template = Template(f.read())
html = template.render(
activities=project.activities,
critical_path=critical_path,
project_duration=project_duration,
log=project.logger
)
# 生成項(xiàng)目進(jìn)度網(wǎng)絡(luò)圖
aon_graph = graphviz.Digraph(format='png', graph_attr={'rankdir': 'LR'})
for activity in project.activities:
aon_graph.node(str(activity.id), activity.name)
for predecessor in activity.predecessors:
aon_graph.edge(str(predecessor), str(activity.id))
timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
aon_filename = f"aon_{timestamp}"
aon_graph.render(aon_filename)
# 將項(xiàng)目進(jìn)度網(wǎng)絡(luò)圖插入到HTML文件中
aon_image = f'<img src="{aon_filename}.png" alt="Precedence Diagramming Method: AON">'
html = html.replace('<p>Precedence Diagramming Method: AON: <br/>[image]</p>',
'<p>緊前關(guān)系繪圖法: AON: <br/>' + aon_image + '</p>')
filename = datetime.now().strftime('%Y%m%d%H%M%S') + '.html'
with open(filename, 'w', encoding='utf-8') as f:
f.write(html)
2.活動(dòng)類
程序名:activity.py
class Activity:
"""
活動(dòng)類,用于表示項(xiàng)目中的一個(gè)活動(dòng)。
Attributes: id (int): 活動(dòng)的唯一標(biāo)識(shí)符。
name (str): 活動(dòng)的名稱。
duration (int): 活動(dòng)的持續(xù)時(shí)間。
predecessors (List[int]): 活動(dòng)的前置活動(dòng)列表,存儲(chǔ)前置活動(dòng)的id。
est (int): 活動(dòng)的最早開始時(shí)間。
lst (int): 活動(dòng)的最晚開始時(shí)間。
eft (int): 活動(dòng)的最早完成時(shí)間。
lft (int): 活動(dòng)的最晚完成時(shí)間。
tf (int): 活動(dòng)的總浮動(dòng)時(shí)間。
ff (int): 活動(dòng)的自由浮動(dòng)時(shí)間。
successors (List[int]): 活動(dòng)的后繼活動(dòng)列表,存儲(chǔ)后繼活動(dòng)的Activity對(duì)象。
"""
def __init__(self, id: int, name: str, duration: int, predecessors: List[int]):
"""
初始化活動(dòng)對(duì)象。
Args: id (int): 活動(dòng)的唯一標(biāo)識(shí)符。
name (str): 活動(dòng)的名稱。
duration (int): 活動(dòng)的持續(xù)時(shí)間。
predecessors (List[int]): 活動(dòng)的前置活動(dòng)列表,存儲(chǔ)前置活動(dòng)的id。
""" self.id = id
self.name = name
self.duration = duration
self.predecessors = predecessors
self.est = None
self.lst = None
self.eft = None
self.lft = None
self.tf = None
self.ff = None
self.successors = []
def __str__(self):
return f"id: {self.id}, name: {self.name}, est: {self.est}, lst: {self.lst}, eft: {self.eft}, lft: {self.lft},"
+ f"successors: {self.successors}"
3.任務(wù)列表JSON文件
文件名:activities.json
[
{ "id": 1,
"name": "A",
"duration": 2,
"predecessors": []
},
{
"id": 9,
"name": "A2",
"duration": 3,
"predecessors": []
},
{
"id": 10,
"name": "A3",
"duration": 2,
"predecessors": []
},
{
"id": 2,
"name": "B",
"duration": 3,
"predecessors": [
1,
9
]
},
{
"id": 3,
"name": "C",
"duration": 4,
"predecessors": [
1
]
},
{
"id": 4,
"name": "D",
"duration": 2,
"predecessors": [
2,10
]
},
{
"id": 5,
"name": "E",
"duration": 3,
"predecessors": [
2
]
},
{
"id": 6,
"name": "F",
"duration": 2,
"predecessors": [
3
]
},
{
"id": 7,
"name": "G",
"duration": 3,
"predecessors": [
4,
5
]
},
{
"id": 8,
"name": "H",
"duration": 2,
"predecessors": [
6,
7
]
},
{
"id": 11,
"name": "H2",
"duration": 4,
"predecessors": [
6,
7
]
}
]
4.輸出模板文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>PMP關(guān)鍵路徑計(jì)算</title>
<style> table {
border-collapse: collapse;
width: 100%;
}
th, td {
border: 1px solid black;
padding: 8px;
text-align: center;
}
th {
background-color: #4CAF50;
color: white;
}
.critical {
background-color: #ffcccc;
}
</style>
</head>
<body>
<h1>活動(dòng)清單</h1>
<table>
<tr>
<th>ID</th>
<th>活動(dòng)名</th>
<th>持續(xù)時(shí)間</th>
<th>緊前活動(dòng)</th>
<th>緊后活動(dòng)</th>
<th>最早開始時(shí)間EST</th>
<th>最早結(jié)束時(shí)間EFT</th>
<th>最晚開始時(shí)間LST</th>
<th>最晚結(jié)束時(shí)間LFT</th>
<th>總浮動(dòng)時(shí)間TF</th>
<th>自由浮動(dòng)時(shí)間FF</th>
</tr>
{% for activity in activities %}
<tr {% if activity in critical_path %}class="critical" {% endif %}>
<td>{{ activity.id }}</td>
<td>{{ activity.name }}</td>
<td>{{ activity.duration }}</td>
<td> {% for predecessor in activity.predecessors %}
{% for act in activities %}
{% if act.id == predecessor %}
{{ act.name }}
{% endif %}
{% endfor %}
{% if not loop.last %}, {% endif %}
{% endfor %}
</td>
<td> {% for successor in activity.successors %}
{% for act in activities %}
{% if act.id == successor %}
{{ act.name }}
{% endif %}
{% endfor %}
{% if not loop.last %}, {% endif %}
{% endfor %}
</td>
<td>{{ activity.est }}</td>
<td>{{ activity.eft }}</td>
<td>{{ activity.lst }}</td>
<td>{{ activity.lft }}</td>
<td>{{ activity.tf }}</td>
<td>{{ activity.ff }}</td>
</tr>
{% endfor %}
</table>
<p>關(guān)鍵路徑是: {% for activity in critical_path %}{{ activity.name }}{% if not loop.last %} -> {% endif %}{% endfor
%}</p>
<p>項(xiàng)目總工期: {{ project_duration }}</p>
<p>Precedence Diagramming Method: AON: <br/>[image]</p>
<p>
<table>
<tr>
<th>執(zhí)行過程</th>
</tr>
{% for i in log %}
<tr>
<td style="text-align: left">{{i}}</td>
</tr>
{% endfor %}
</table>
</p>
</body>
</html>
到此這篇關(guān)于Python實(shí)現(xiàn)關(guān)鍵路徑和七格圖計(jì)算詳解的文章就介紹到這了,更多相關(guān)Python關(guān)鍵路徑 七格圖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
tensorflow實(shí)現(xiàn)簡(jiǎn)單的卷積網(wǎng)絡(luò)
這篇文章主要為大家詳細(xì)介紹了tensorflow實(shí)現(xiàn)簡(jiǎn)單的卷積網(wǎng)絡(luò),使用的數(shù)據(jù)集是MNIST,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-05-05
tensorflow模型文件(ckpt)轉(zhuǎn)pb文件的方法(不知道輸出節(jié)點(diǎn)名)
這篇文章主要介紹了tensorflow模型文件(ckpt)轉(zhuǎn)pb文件(不知道輸出節(jié)點(diǎn)名),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04
Python對(duì)象轉(zhuǎn)JSON字符串的方法
這篇文章主要介紹了Python對(duì)象轉(zhuǎn)JSON字符串的方法,涉及Python基于json模塊實(shí)現(xiàn)json轉(zhuǎn)換的實(shí)現(xiàn)技巧,非常簡(jiǎn)便易懂,需要的朋友可以參考下2016-04-04
基于Pygame中Pygame模塊的大戰(zhàn)外星人實(shí)戰(zhàn)
本文主要介紹了基于Pygame中Pygame模塊的大戰(zhàn)外星人實(shí)戰(zhàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-12-12
如何使用Python進(jìn)行OCR識(shí)別圖片中的文字
這篇文章主要介紹了使用Python進(jìn)行OCR識(shí)別圖片中的文字 ,本文通過實(shí)例代碼加文字說明的形式給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-04-04
python進(jìn)程池的簡(jiǎn)單實(shí)現(xiàn)
本文主要介紹了python進(jìn)程池的簡(jiǎn)單實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03

