网络计划技术——关键路线法(Python)
关键路径法是基于进度网络模型的方法,用网络图表示各项活动之间的相互关系,获得在一定工期、成本、资源约束条件下的最优进度安排。关键路径法源于美国杜邦公司对于项目管理控制成本、减少工期的研究。1959年,Kelly 和 Walker 在论文 Critical Path Planning and Scheduling 中提出了关键路径法的基本原理和方法,将该方法应用到杜邦新工厂的建设,使该工程提前两个月完工。杜邦公司采用此法安排施工和维修等计划,每年节约资金100多万美金,取得了良好的经济效益,现今是项目管理技术的核心方法。
一、关键路径法
一个大型工程或项目包括很多活动,关键路径是项目中时间最长的活动顺序,决定着可能的项目最短工期。关键路径法将项目分解成为多个独立的活动并确定每个活动的工期,然后用逻辑关系(结束-开始、结束-结束、开始-开始和开始-结束)将活动连接。首先使用正推法(Forward pass),从起点开始向后计算,依次计算每个顶点(事件)的最早开始时间 ES;然后再使用逆推法(Backward pass),从终点开始向前计算,依次计算每个顶点(事件)的最迟结束时间 LF。进而可以求出每条边(工序)的最早结束时间 EF 和最迟开始时间 LS。最早开始时间 ES 和最迟开始时间 LS 相等的边,就是关键路径上的边,对应的工序是关键工序。
1.1 作业明细表
作业(或工序)明细表是管理活动与网络技术应用的接口,其给出各工序间的前后逻辑关系的以及资源的使用情况,它是管理实践经验的结晶。网络计划图一般用在复杂的项目管理中,绘制网络计划图必须好下列准备工作。
(1)确定计划目标
网络计划的目标是多个方面和综合的目标,正是这个特点决定了网络计划的复杂性。一般按照不同的要求可以分成三类:一是时间要求;一是资源要求;一是费用要求。
(2)项目任务活动的分解
将整个项目根据技术上的需要分解为若干个互相独立的可执行的活动,通常把这种互相独立的活动称为工序或作业
(3)确定各工序的衔接顺序
明确各项工序之间的先后逻辑关系。工序之间存在执行的先后顺序关系,如需先完成A工序才能进行B工序,则称A为B紧前工序,B为A紧后工序,列出工序明细,以便建立整个项目的网络图以表示各项工序之间的相互关系。
(4)确定各个工序时间
确定各项工序所需的时间、人力、物力。一般将完成工序所需要的时间,称为工时。
后面三项准备工作一般以工序明细表或作业明细表的形式给出,所有的网络计划分析都在这个前提下执行。
作业 | 计划完成时间/天 | 紧前作业 | 作业 | 计划完成时间/天 | 紧前作业 |
---|---|---|---|---|---|
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 |
1.2 工序时间参数的计算
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,单代号网络图)逐渐成为主流方法。
1.3 关键路线的确定
总时差为零的工序就是关键工序,由他们就组成项目的关键路线。
二、关键路线法示例
案例:某项目的作业明细表
工作 | 紧前工作 | 作业时间 | 服务部门 | 工作 | 紧前工作 | 作业时间 | 服务部门 |
---|---|---|---|---|---|---|---|
A | - | 3 | IT | F | C | 4 | IT |
B | A | 3 | MKT | G | D,E | 4 | MKT |
C | A | 3 | ENG | H | E,F | 2 | PROD |
D | B | 8 | PROD | I | G,H | 2 | FIN |
E | B,C | 5 | FIN |
双代号网络图
三、关键路线法的计算(python)
3.1 数据的组织与输入
import pandas as pd
# Define the task dictionaries
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 3, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': 'A', '持续时间': 3, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': 'A', '持续时间': 3, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 8, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'B,C', '持续时间': 5, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'C', '持续时间': 4, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'D,E', '持续时间': 4, 'partment': 'MKT'}
dictH = {'工作名称': 'H', '紧前工作': 'E,F', '持续时间': 2, 'partment': 'PROD'}
dictI = {'工作名称': 'I', '紧前工作': 'H,G', '持续时间': 2, 'partment': 'FIN'}
lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG, dictH, dictI]
3.2 时间参数的计算
# Create DataFrame
df = pd.DataFrame(lis)
# Initialize columns for CPM analysis
df['Earliest_Start'] = 0
df['Earliest_Finish'] = 0
df['Latest_Start'] = 0
df['Latest_Finish'] = 0
df['Total_Float'] = 0
df['Free_Float'] = 0
# Forward Pass: Calculate Earliest Start and Earliest Finish
for i, row in df.iterrows():
if row['紧前工作'] == '-':
df.at[i, 'Earliest_Finish'] = row['持续时间']
else:
predecessors = row['紧前工作'].split(',')
max_ef = 0
for pred in predecessors:
pred_ef = df.loc[df['工作名称'] == pred, 'Earliest_Finish'].values[0]
if pred_ef > max_ef:
max_ef = pred_ef
df.at[i, 'Earliest_Start'] = max_ef
df.at[i, 'Earliest_Finish'] = max_ef + row['持续时间']
# Backward Pass: Calculate Latest Start and Latest Finish
project_duration = df['Earliest_Finish'].max()
for i in range(len(df) - 1, -1, -1):
row = df.iloc[i]
if i == len(df) - 1:
df.at[i, 'Latest_Finish'] = project_duration
df.at[i, 'Latest_Start'] = project_duration - row['持续时间']
else:
successors = df[df['紧前工作'].str.contains(row['工作名称'])]
min_ls = project_duration
for j, succ_row in successors.iterrows():
succ_ls = df.at[j, 'Latest_Start']
if succ_ls < min_ls:
min_ls = succ_ls
df.at[i, 'Latest_Finish'] = min_ls
df.at[i, 'Latest_Start'] = min_ls - row['持续时间']
# Calculate Total Float and Free Float
for i, row in df.iterrows():
df.at[i, 'Total_Float'] = row['Latest_Start'] - row['Earliest_Start']
successors = df[df['紧前工作'].str.contains(row['工作名称'])]
if len(successors) == 0:
df.at[i, 'Free_Float'] = df.at[i, 'Total_Float']
else:
min_es = min(successors['Earliest_Start']) if not successors.empty else row['Earliest_Finish']
df.at[i, 'Free_Float'] = min_es - row['Earliest_Finish']
# Print the final DataFrame (optional)
print(df)
工序时间参数计算结果如下表所示:
Task | Earliest_Start | Earliest_Finish | Latest_Start | Latest_Finish | Total_Flote_Time | Free_Flote_Time |
---|---|---|---|---|---|---|
A | 0 | 3 | 0 | 3 | 0 | 0 |
B | 3 | 6 | 3 | 6 | 0 | 0 |
C | 3 | 6 | 6 | 9 | 3 | 0 |
D | 6 | 14 | 6 | 14 | 0 | 0 |
E | 6 | 11 | 9 | 14 | 3 | 0 |
F | 6 | 10 | 12 | 16 | 6 | 1 |
G | 14 | 18 | 14 | 18 | 0 | 0 |
H | 11 | 13 | 16 | 18 | 5 | 5 |
I | 18 | 20 | 18 | 20 | 0 | 0 |
3.3 关键路线的可视化——甘特图
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch
# Adding additional metrics
df['During'] = df['持续时间']
df['Department'] = df['partment']
df['Flexible'] = 1 - df['During'] / (df['Latest_Finish'] - df['Earliest_Start'])
df['days_start_to_end'] = df['Latest_Finish'] - df['Earliest_Start']
df['current_num'] = df['days_start_to_end'] * (1 - df['Flexible'])
# Define the function to assign colors based on department
def color(row):
c_dict = {'MKT': '#E64646', 'FIN': '#E69646', 'ENG': '#34D05C', 'PROD': '#34D0C3', 'IT': '#3475D0'}
return c_dict[row['Department']]
df['color'] = df.apply(color, axis=1)
# Reverse the DataFrame order for plotting
df = df.iloc[::-1]
# Plotting Gantt Chart
fig, ax = plt.subplots(1, figsize=(16, 6))
# Bars for tasks
ax.barh(df['工作名称'], df['current_num'], left=df['Earliest_Start'], color=df['color'])
ax.barh(df['工作名称'], df['days_start_to_end'], left=df['Earliest_Start'], color=df['color'], alpha=0.5)
# Add percentage text
for idx, row in df.iterrows():
ax.text(row['Latest_Finish'] + 0.1, len(df) - idx - 1, f"{int(row['Flexible'] * 100)}%", va='center', alpha=0.8)
# Legends for departments
c_dict = {'MKT': '#E64646', 'FIN': '#E69646', 'ENG': '#34D05C', 'PROD': '#34D0C3', 'IT': '#3475D0'}
legend_elements = [Patch(facecolor=c_dict[i], label=i) for i in c_dict]
plt.legend(handles=legend_elements)
plt.xlabel('Days')
plt.ylabel('Tasks')
plt.title('Gantt Chart')
plt.show()
*!!!注意:图中百分数数字表示各工序机动时间占比。百分比为零的就是关键工序,由关键工序组成的路线就是关键路线。
四、总结
关键路径法(CPM),是一种算法用于调度的一组项目的活动。通常与程序评估和审查技术(PERT)结合使用。关键路径是通过确定最长的依存活动范围并测量从头到尾完成这些活动所需的时间来确定的。使用关键路径法的基本技术是构建项目管理计划,其中包括以下内容:
完成项目所需的所有活动的列表(通常在工作分解结构中分类),
每个活动完成所需的时间(持续时间),
活动之间的依赖关系以及
逻辑终点,例如里程碑或可交付项。
关键路径法使用这些值来计算计划的活动到逻辑终点或项目结束的最长路径,以及每个活动可以开始和完成而不必延长项目时间的最早和最新路径。该过程确定哪些活动是“关键的”(即,在最长的路径上),哪些活动具有“全部浮动”(即,可以在不延长项目时间的情况下将其延迟)。华罗庚教授为网络计划技术在我国的引进与发展做出了巨大的贡献,他的名字也将永远闪耀在运筹优化学科的历史长河中。美国著名数学史家贝特曼称:“华罗庚是中国的爱因斯坦,足够成为全世界所有著名科学院的院士”。
参考文献
1.Python小白的数学建模课-21.关键路径法
2. 项目管理:关键路径法
3. 根据逻辑关系图以及双代号网络图编写求时间参数(python版)
4. Python使用Matplotlib绘制甘特图的实践