Python如何实现关键路径和七格图计算

其他教程   发布日期:2023年08月30日   浏览次数:420

本篇内容介绍了“Python如何实现关键路径和七格图计算”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!

1.主程序

主程序主要实现了一个Project类,其中包含了计算关键路径和七格图的方法。具体实现方式如下:

1. 定义了一个Activity类,包含了活动的id、名称、持续时间和紧前任务列表等属性。

2. 定义了一个Project类,包含了活动列表、项目持续时间、日志等属性,以及计算关键路径、计算七格图、计算总浮动时间、计算自由浮动时间等方法。

3. 从JSON文件中读取活动信息,并创建Project对象并添加活动。

4. 调用Project对象的calculate方法,计算每个活动的最早开始时间、最晚开始时间等数据。

5. 调用Project对象的calculate_critical_path方法,计算关键路径。

6. 调用Project对象的calculate_project_duration方法,计算项目总工期。

7. 使用Jinja2模板引擎生成项目的活动清单,并将关键路径

  1. import json
  2. from datetime import datetime
  3. from typing import List
  4. import graphviz
  5. from jinja2 import Template
  6. from activity import Activity
  7. class Project:
  8. def __init__(self):
  9. self.activities: List[Activity] = []
  10. self.duration = 0
  11. self.logger = []
  12. def log(self, log: str) -> None:
  13. self.logger.append(log)
  14. def add_activity(self, activity: Activity) -> None:
  15. """
  16. 添加一个活动到项目中
  17. :param
  18. activity: 待添加的活动
  19. """ # 将活动添加到项目中
  20. self.activities.append(activity)
  21. def calculate(self) -> None:
  22. """ 计算整个项目的关键信息
  23. :return: None
  24. """ self.calculate_successor()
  25. self._calculate_forward_pass() # 计算正推法
  26. self._calculate_backward_pass() # 计算倒推法
  27. self._calculate_total_floats() # 计算总浮动时间
  28. self._calculate_free_floats() # 计算自由浮动时间
  29. def calculate_successor(self) -> None:
  30. self.log("开始计算紧后活动")
  31. for act in self.activities:
  32. for pred in act.predecessors:
  33. for act_inner in self.activities:
  34. if act_inner.id == pred:
  35. act_inner.successors.append(act.id)
  36. def _calculate_forward_pass(self) -> None:
  37. self.log("## 开始正推法计算")
  38. # 进入 while 循环,只有当所有活动的最早开始时间和最早完成时间都已经计算出来时,才会退出循环
  39. while not self._is_forward_pass_calculated():
  40. # 遍历每个活动
  41. for activity in self.activities:
  42. # 如果活动的最早开始时间已经被计算过,则跳过
  43. if activity.est is not None:
  44. continue
  45. # 如果活动没有前置活动, 则从1开始计算最早开始时间和最早结束时间
  46. if not activity.predecessors:
  47. activity.est = 1
  48. activity.eft = activity.est + activity.duration - 1
  49. self.log(
  50. f"活动 {activity.name} 没有紧前活动,设定最早开始时间为1, 并根据工期计算最早结束时间为{activity.eft}")
  51. else:
  52. # 计算当前活动的所有前置活动的最早完成时间
  53. predecessors_eft = [act.eft for act in self.activities if
  54. act.id in activity.predecessors and act.eft is not None]
  55. # 如果当前活动的所有前置活动的最早完成时间都已经计算出来,则计算当前活动的最早开始时间和最早完成时间
  56. if len(predecessors_eft) == len(activity.predecessors):
  57. activity.est = max(predecessors_eft) + 1
  58. activity.eft = activity.est + activity.duration - 1
  59. self.log(
  60. f"活动 {activity.name} 紧前活动已完成正推法计算, 开始日期按最早开始时间里面最大的," +
  61. f"设定为{activity.est}并根据工期计算最早结束时间为{activity.eft}")
  62. # 更新项目总持续时间为最大最早完成时间
  63. self.duration = max([act.eft for act in self.activities])
  64. def _calculate_backward_pass(self) -> None:
  65. """ 计算倒推法
  66. :return: None
  67. """
  68. self.log("## 开始倒推法计算") # 输出提示信息
  69. # 进入 while 循环,只有当所有活动的最晚开始时间和最晚完成时间都已经计算出来时,才会退出循环
  70. while not self._is_backward_pass_calculated():
  71. # 遍历每个活动
  72. for act in reversed(self.activities):
  73. # 如果活动的最晚开始时间已经被计算过,则跳过
  74. if act.lft is not None:
  75. continue
  76. # 如果活动没有后继活动, 则从总持续时间开始计算最晚开始时间和最晚结束时间
  77. if not act.successors:
  78. act.lft = self.duration
  79. act.lst = act.lft - act.duration + 1
  80. self.log(f"活动 {act.name} 没有紧后活动,按照正推工期设定最晚结束时间为{act.lft}," +
  81. f"并根据工期计算最晚开始时间为{act.lst}")
  82. else:
  83. # 计算当前活动的所有后继活动的最晚开始时间
  84. successors_lst = self._calculate_lst(act)
  85. # 如果当前活动的所有后继活动的最晚开始时间都已经计算出来,则计算当前活动的最晚开始时间和最晚完成时间
  86. if len(successors_lst) == len(act.successors):
  87. act.lft = min(successors_lst) - 1
  88. act.lst = act.lft - act.duration + 1
  89. self.log(f"活动 {act.name} 紧后活动计算完成,按照倒推工期设定最晚结束时间为{act.lft}," +
  90. f"并根据工期计算最晚开始时间为{act.lst}")
  91. # 更新项目总持续时间为最大最晚完成时间
  92. self.duration = max([act.lft for act in self.activities])
  93. def _calculate_lst(self, activity: Activity) -> List[int]:
  94. """计算某一活动的所有最晚开始时间
  95. :param activity: 活动对象
  96. :return: 最晚开始时间列表
  97. """ rst = [] # 初始化结果列表
  98. for act in activity.successors: # 遍历该活动的后继活动
  99. for act2 in self.activities: # 遍历所有活动
  100. if act2.id == act and act2.lst is not None: # 如果找到了该后继活动且其最晚开始时间不为空
  101. rst.append(act2.lst) # 将最晚开始时间加入结果列表
  102. return rst # 返回结果列表
  103. def _is_forward_pass_calculated(self) -> bool:
  104. """ 判断整个项目正推法计算已经完成
  105. :return: 若已计算正向传递则返回True,否则返回False
  106. """
  107. for act in self.activities: # 遍历所有活动
  108. if act.est is None or act.eft is None: # 如果该活动的最早开始时间或最早完成时间为空
  109. return False # 则返回False,表示还未计算正向传递
  110. return True # 如果所有活动的最早开始时间和最早完成时间都已计算,则返回True,表示已计算正向传递
  111. def _is_backward_pass_calculated(self) -> bool:
  112. """ 判断整个项目倒推法计算已经完成
  113. :return: 若已计算倒推法则返回True,否则返回False
  114. """ for act in self.activities: # 遍历所有活动
  115. if act.lst is None or act.lft is None: # 如果该活动的最晚开始时间或最晚完成时间为空
  116. return False # 则返回False,表示还未计算倒推法
  117. return True # 如果所有活动的最晚开始时间和最晚完成时间都已计算,则返回True,表示已计算倒推法
  118. def _calculate_total_floats(self) -> None:
  119. """ 计算所有活动的总浮动时间
  120. :return: None
  121. """
  122. self.log(f"## 开始计算项目所有活动的总浮动时间")
  123. for act in self.activities: # 遍历所有活动
  124. if act.est is not None and act.lst is not None: # 如果该活动的最早开始时间和最晚开始时间都已计算
  125. act.tf = act.lst - act.est # 则计算该活动的总浮动时间
  126. self.log(f"计算{act.name}的总浮动时间" + f"最晚开始时间{act.lst} - 最早开始时间{act.est} = {act.tf}", )
  127. else: # 如果该活动的最早开始时间或最晚开始时间为空
  128. act.tf = None # 则将该活动的总浮动时间设为None
  129. def _calculate_free_floats(self) -> None:
  130. """ 计算所有活动的自由浮动时间
  131. :return: None
  132. """ self.log(f"## 开始计算项目所有活动的自由浮动时间") # 输出提示信息
  133. for act in self.activities: # 遍历所有活动
  134. if act.tf == 0: # 如果该活动的总浮动时间为0
  135. self.log(f"计算{act.name}的自由浮动时间" + f"因为{act.name}的总浮动时间为0,自由浮动时间为0") # 输出提示信息
  136. act.ff = 0 # 则将该活动的自由浮动时间设为0
  137. elif act.tf > 0: # 如果该活动的总浮动时间大于0
  138. self.log(f"计算{act.name}的自由浮动时间") # 输出提示信息
  139. self.log(f"- {act.name}的总浮动时间{act.tf} > 0,") # 输出提示信息
  140. tmp = [] # 初始化临时列表
  141. for act2 in self.activities: # 遍历所有活动
  142. if act2.id in act.successors: # 如果该活动是该活动的紧后活动
  143. self.log(f"- {act.name}的紧后活动{act2.name}的自由浮动动时间为{act2.tf}") # 输出提示信息
  144. tmp.append(act2.tf) # 将该紧后活动的自由浮动时间加入临时列表
  145. if len(tmp) != 0: # 如果临时列表不为空
  146. act.ff = act.tf - max(tmp) # 则计算该活动的自由浮动时间
  147. if act.ff < 0:
  148. act.ff = 0
  149. self.log(f"- 用活动自己的总浮动{act.tf}减去多个紧后活动总浮动的最大值{max(tmp)} = {act.ff}")
  150. else: # 如果临时列表为空
  151. act.ff = act.tf # 则将该活动的自由浮动时间设为总浮动时间
  152. def calculate_critical_path(self) -> List[Activity]:
  153. """ 计算整个项目的关键路径
  154. :return: 整个项目的关键路径
  155. """ ctc_path = [] # 初始化关键路径列表
  156. for act in self.activities: # 遍历所有活动
  157. if act.tf == 0: # 如果该活动的总浮动时间为0
  158. ctc_path.append(act) # 则将该活动加入关键路径列表
  159. return ctc_path # 返回关键路径列表
  160. def calculate_project_duration(self) -> int:
  161. """ 计算整个项目的持续时间
  162. :return: 整个项目的持续时间
  163. """ return max(activity.eft for activity in self.activities) # 返回所有活动的最早完成时间中的最大值,即整个项目的持续时间
  164. # 从JSON文件中读取活动信息
  165. with open('activities.json', 'r', encoding='utf-8') as f:
  166. activities_data = json.load(f)
  167. # 创建Project对象并添加活动
  168. project = Project()
  169. for activity_data in activities_data:
  170. activity = Activity(
  171. activity_data['id'],
  172. activity_data['name'],
  173. activity_data['duration'],
  174. activity_data['predecessors']
  175. ) project.add_activity(activity)
  176. # 计算每个活动的最早开始时间、最晚开始时间等数据
  177. project.calculate()
  178. # 计算关键路径和项目总工期
  179. critical_path = project.calculate_critical_path()
  180. project_duration = project.calculate_project_duration()
  181. # 生成项目的活动清单
  182. with open('template.html', 'r', encoding='utf-8') as f:
  183. template = Template(f.read())
  184. html = template.render(
  185. activities=project.activities,
  186. critical_path=critical_path,
  187. project_duration=project_duration,
  188. log=project.logger
  189. )
  190. # 生成项目进度网络图
  191. aon_graph = graphviz.Digraph(format='png', graph_attr={'rankdir': 'LR'})
  192. for activity in project.activities:
  193. aon_graph.node(str(activity.id), activity.name)
  194. for predecessor in activity.predecessors:
  195. aon_graph.edge(str(predecessor), str(activity.id))
  196. timestamp = datetime.now().strftime('%Y%m%d%H%M%S')
  197. aon_filename = f"aon_{timestamp}"
  198. aon_graph.render(aon_filename)
  199. # 将项目进度网络图插入到HTML文件中
  200. aon_image = f'<img src="{aon_filename}.png" alt="Precedence Diagramming Method: AON">'
  201. html = html.replace('<p>Precedence Diagramming Method: AON: <br/>[image]</p>',
  202. '<p>紧前关系绘图法: AON: <br/>' + aon_image + '</p>')
  203. filename = datetime.now().strftime('%Y%m%d%H%M%S') + '.html'
  204. with open(filename, 'w', encoding='utf-8') as f:
  205. f.write(html)

2.活动类

程序名:

  1. activity.py
  1. class Activity:
  2. """
  3. 活动类,用于表示项目中的一个活动。
  4. Attributes: id (int): 活动的唯一标识符。
  5. name (str): 活动的名称。
  6. duration (int): 活动的持续时间。
  7. predecessors (List[int]): 活动的前置活动列表,存储前置活动的id。
  8. est (int): 活动的最早开始时间。
  9. lst (int): 活动的最晚开始时间。
  10. eft (int): 活动的最早完成时间。
  11. lft (int): 活动的最晚完成时间。
  12. tf (int): 活动的总浮动时间。
  13. ff (int): 活动的自由浮动时间。
  14. successors (List[int]): 活动的后继活动列表,存储后继活动的Activity对象。
  15. """
  16. def __init__(self, id: int, name: str, duration: int, predecessors: List[int]):
  17. """
  18. 初始化活动对象。
  19. Args: id (int): 活动的唯一标识符。
  20. name (str): 活动的名称。
  21. duration (int): 活动的持续时间。
  22. predecessors (List[int]): 活动的前置活动列表,存储前置活动的id。
  23. """ self.id = id
  24. self.name = name
  25. self.duration = duration
  26. self.predecessors = predecessors
  27. self.est = None
  28. self.lst = None
  29. self.eft = None
  30. self.lft = None
  31. self.tf = None
  32. self.ff = None
  33. self.successors = []
  34. def __str__(self):
  35. return f"id: {self.id}, name: {self.name}, est: {self.est}, lst: {self.lst}, eft: {self.eft}, lft: {self.lft},"
  36. + f"successors: {self.successors}"

3.任务列表JSON文件

文件名:

  1. activities.json
  1. [
  2. { "id": 1,
  3. "name": "A",
  4. "duration": 2,
  5. "predecessors": []
  6. },
  7. {
  8. "id": 9,
  9. "name": "A2",
  10. "duration": 3,
  11. "predecessors": []
  12. },
  13. {
  14. "id": 10,
  15. "name": "A3",
  16. "duration": 2,
  17. "predecessors": []
  18. },
  19. {
  20. "id": 2,
  21. "name": "B",
  22. "duration": 3,
  23. "predecessors": [
  24. 1,
  25. 9
  26. ]
  27. },
  28. {
  29. "id": 3,
  30. "name": "C",
  31. "duration": 4,
  32. "predecessors": [
  33. 1
  34. ]
  35. },
  36. {
  37. "id": 4,
  38. "name": "D",
  39. "duration": 2,
  40. "predecessors": [
  41. 2,10
  42. ]
  43. },
  44. {
  45. "id": 5,
  46. "name": "E",
  47. "duration": 3,
  48. "predecessors": [
  49. 2
  50. ]
  51. },
  52. {
  53. "id": 6,
  54. "name": "F",
  55. "duration": 2,
  56. "predecessors": [
  57. 3
  58. ]
  59. },
  60. {
  61. "id": 7,
  62. "name": "G",
  63. "duration": 3,
  64. "predecessors": [
  65. 4,
  66. 5
  67. ]
  68. },
  69. {
  70. "id": 8,
  71. "name": "H",
  72. "duration": 2,
  73. "predecessors": [
  74. 6,
  75. 7
  76. ]
  77. },
  78. {
  79. "id": 11,
  80. "name": "H2",
  81. "duration": 4,
  82. "predecessors": [
  83. 6,
  84. 7
  85. ]
  86. }
  87. ]

4.输出模板文件

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>PMP关键路径计算</title>
  6. <style> table {
  7. border-collapse: collapse;
  8. width: 100%;
  9. }
  10. th, td {
  11. border: 1px solid black;
  12. padding: 8px;
  13. text-align: center;
  14. }
  15. th {
  16. background-color: #4CAF50;
  17. color: white;
  18. }
  19. .critical {
  20. background-color: #ffcccc;
  21. }
  22. </style>
  23. </head>
  24. <body>
  25. <h2>活动清单</h2>
  26. <table>
  27. <tr>
  28. <th>ID</th>
  29. <th>活动名</th>
  30. <th>持续时间</th>
  31. <th>紧前活动</th>
  32. <th>紧后活动</th>
  33. <th>最早开始时间EST</th>
  34. <th>最早结束时间EFT</th>
  35. <th>最晚开始时间LST</th>
  36. <th>最晚结束时间LFT</th>
  37. <th>总浮动时间TF</th>
  38. <th>自由浮动时间FF</th>
  39. </tr>
  40. {% for activity in activities %}
  41. <tr {% if activity in critical_path %}class="critical" {% endif %}>
  42. <td>{{ activity.id }}</td>
  43. <td>{{ activity.name }}</td>
  44. &n

以上就是Python如何实现关键路径和七格图计算的详细内容,更多关于Python如何实现关键路径和七格图计算的资料请关注九品源码其它相关文章!