运筹学练习Python精解——网络计划技术

练习1

某新产品研制项目的各项工序、所需时间及相互关系如下表所示,试画出该项目的网络图,并求出关键路线。

工序 产品及工艺设计 外购配套件 下料、锻件 工装制造1 木模、铸件 机械加工1 工装制造2 机械加工2 机械加工3 装配调试
工序代号 A B C D E F G H I K
所需时间 60 45 10 20 40 18 30 15 25 35
紧后工序 B, C, D, E K F G, H H K I K K -

1.1 绘制网络计划图(数学模型)

1.2 Python求解

#网络计划的最长路算法
import networkx as nx

# Define the edges with the new data structure
edges = {
    'A': {'nodes': ('1', '2'), 'weight': 60},
    'B': {'nodes': ('2', '7'), 'weight': 45},
    'C': {'nodes': ('2', '3'), 'weight': 10},
    'D': {'nodes': ('2', '4'), 'weight': 20},
    'E': {'nodes': ('2', '5'), 'weight': 40},
    'F': {'nodes': ('3', '7'), 'weight': 18},
    'G': {'nodes': ('4', '6'), 'weight': 30},
    'L': {'nodes': ('4', '5'), 'weight': 0},
    'H': {'nodes': ('5', '7'), 'weight': 15},
    'I': {'nodes': ('6', '7'), 'weight': 25},
    'K': {'nodes': ('7', '8'), 'weight': 35}
}

# Initialize a directed graph
G = nx.DiGraph()

# Add edges to the graph using the new data structure
for edge_name, edge_data in edges.items():
    start, end = edge_data['nodes']
    weight = edge_data['weight']
    G.add_edge(start, end, weight=weight, name=edge_name)

# Compute the longest path using the networkx library
length, path = nx.algorithms.dag.dag_longest_path_length(G, weight='weight'), nx.algorithms.dag.dag_longest_path(G, weight='weight')

# Extract the names of the edges in the critical path
critical_path_edges = []
for i in range(len(path) - 1):
    critical_path_edges.append(G[path[i]][path[i + 1]]['name'])

# Format the critical path output
formatted_critical_path = " -> ".join(critical_path_edges) + "."

print(f"Length of the critical path: {length}")
print(f"Critical path nodes: {path}")
print(f"Critical path edges: {formatted_critical_path}")
Length of the critical path: 170
Critical path nodes: ['1', '2', '4', '6', '7', '8']
Critical path edges: A -> D -> G -> I -> K.

练习2

import networkx as nx

# Define the edges with the new data structure
edges = {
    'A': {'nodes': ('1', '2'), 'weight': 4},
    'B': {'nodes': ('2', '3'), 'weight': 5},
    'C': {'nodes': ('2', '4'), 'weight': 3},
    'D': {'nodes': ('2', '6'), 'weight': 2},
    'E': {'nodes': ('3', '4'), 'weight': 2},
    'F': {'nodes': ('3', '5'), 'weight': 1},
    'G': {'nodes': ('4', '6'), 'weight': 4},
    'H': {'nodes': ('5', '8'), 'weight': 3},
    'I': {'nodes': ('6', '7'), 'weight': 2},
    'J': {'nodes': ('7', '8'), 'weight': 4}
}

# Initialize a directed graph
G = nx.DiGraph()

# Add edges to the graph using the new data structure
for edge_name, edge_data in edges.items():
    start, end = edge_data['nodes']
    weight = edge_data['weight']
    G.add_edge(start, end, weight=weight, name=edge_name)

# Compute the longest path using the networkx library
length, path = nx.algorithms.dag.dag_longest_path_length(G, weight='weight'), nx.algorithms.dag.dag_longest_path(G, weight='weight')

# Extract the names of the edges in the critical path
critical_path_edges = []
for i in range(len(path) - 1):
    critical_path_edges.append(G[path[i]][path[i + 1]]['name'])

# Format the critical path output
formatted_critical_path = " -> ".join(critical_path_edges) + "."

print(f"Length of the critical path: {length}")
print(f"Critical path nodes: {path}")
print(f"Critical path edges: {formatted_critical_path}")
Length of the critical path: 21
Critical path nodes: ['1', '2', '3', '4', '6', '7', '8']
Critical path edges: A -> B -> E -> G -> I -> J.

练习3

某项目的作业明细表如下,试计算其工序的作业时间参数,最后作甘特图表示关键路线。

工作 A B C D E F G H I J
持续时间 2 3 5 2 3 3 2 3 6 2
紧前工作 - A A B B D F E,F C,E,F G,H

3.1 网络计划图

3.2 Python程序

  • 作业明细表数据输入结构
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch

# 第一部分:数据结构
# 定义任务字典('partment': 'IT'仅仅为了后面作图采用不同的颜色表示,无实际意义)
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 2, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': 'A', '持续时间': 3, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': 'A', '持续时间': 5, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 2, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'B', '持续时间': 3, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'D', '持续时间': 3, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'F', '持续时间': 2, 'partment': 'MKT'}
dictH = {'工作名称': 'H', '紧前工作': 'E,F', '持续时间': 3, 'partment': 'PROD'}
dictI = {'工作名称': 'I', '紧前工作': 'C,E,F', '持续时间': 6, 'partment': 'FIN'}
dictJ = {'工作名称': 'J', '紧前工作': 'G,H', '持续时间': 2, 'partment': 'MKT'}

lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG, dictH, dictI,dictJ]

# 创建 DataFrame
df = pd.DataFrame(lis)
  • 工序时间参数的计算
# 初始化 CPM 分析的列
df['Earliest_Start'] = 0
df['Earliest_Finish'] = 0
df['Latest_Start'] = 0
df['Latest_Finish'] = 0
df['Total_Float'] = 0
df['Free_Float'] = 0

# 第二部分:网络参数计算
# 正向传递:计算最早开始和最早结束时间
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['持续时间']

# 反向传递:计算最迟开始和最迟结束时间
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['持续时间']

# 计算总浮动和自由浮动
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']

# 将DataFrame转置并完整显示
pd.set_option('display.max_columns', None)
print(df.T)
工作名称         A    B    C     D    E   F    G     H      I    J
紧前工作         -    A    A     B    B   D    F   E,F  C,E,F  G,H
持续时间         2    3    5     2    3   3    2     3      6    2
partment         IT  MKT  ENG  PROD  FIN  IT  MKT  PROD    FIN  MKT
Earliest_Start    0    2    2     5    5   7   10    10     10   13
Earliest_Finish   2    5    7     7    8  10   12    13     16   15
Latest_Start      0    2    5     5    7   7   12    11     10   14
Latest_Finish     2    5   10     7   10  10   14    14     16   16
Total_Float       0    0    3     0    2   0    2     1      0    1
Free_Float        0    0    3     0    2   0    1     0      0    1
  • 可视化甘特图
# 反转 DataFrame 的顺序用于绘图
df = df.iloc[::-1]

# 绘制甘特图
fig, ax = plt.subplots(1, figsize=(16, 6))

# 绘制任务条
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)

# 添加百分比文本
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)

# 部门图例
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()

关键工序的百分比为0%

!!!工序的百分比表示该工序机动时间占其完工时间的百分比,关键工序的百分比为0%。

练习4

某公司的产前分析网络工程图的工序明细表和网络图如下图所示,求出网络中每个节点的最早开始时间、最迟开始时间、关键路线及工程工期。

工作 A B C D E F G H I J K
持续时间 5 10 11 4 4 15 21 35 25 15 20
紧前工作 - - - B A C,D B,E B,E B,E F,G,I F,G

4.1 网络计划图

4.2 Python程序

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch

# 第一部分:数据结构
# 定义任务字典('partment': 'IT'仅仅为了后面作图采用不同的颜色表示,无实际意义)
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 5, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': '-', '持续时间': 10, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': '-', '持续时间': 11, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 4, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'A', '持续时间': 4, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'C,D', '持续时间': 15, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'B,E', '持续时间': 21, 'partment': 'MKT'}
dictH = {'工作名称': 'H', '紧前工作': 'B,E', '持续时间': 35, 'partment': 'PROD'}
dictI = {'工作名称': 'I', '紧前工作': 'B,E', '持续时间': 25, 'partment': 'FIN'}
dictJ = {'工作名称': 'J', '紧前工作': 'F,G,I', '持续时间': 15, 'partment': 'MKT'}
dictK = {'工作名称': 'K', '紧前工作': 'F,G', '持续时间': 20, 'partment': 'FIN'}

lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG, dictH, dictI,dictJ,dictK]

# 创建 DataFrame
df = pd.DataFrame(lis)

#后面语句与练习3完全相同

练习5

已知网络计划各工作的正常完工、极限完工及相应费用如下表所示,试分析其最小成本日程。

工序 紧前工序 作业正常完工(天) 作业极限完工(天) 工序正常完工(直接费用/元) 工序极限完工(直接费用/元) 费率
A - 3 3 800 800 -
B - 5 3 1600 1900 150
C A 7 3 2000 2800 200
D B 6 3 2000 2300 100
E B 5 2 500 860 120
F E 3 3 1000 1000 -
G D 4 3 900 1100 200
合计 - - - 8800 - -
间接费用 - - - 200 元/天 - -

5.1 网络计划图

5.2 工序的时间参数

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch

# 第一部分:数据结构
# 定义任务字典('partment': 'IT'仅仅为了后面作图采用不同的颜色表示,无实际意义)
dictA = {'工作名称': 'A', '紧前工作': '-', '持续时间': 3, 'partment': 'IT'}
dictB = {'工作名称': 'B', '紧前工作': '-', '持续时间': 5, 'partment': 'MKT'}
dictC = {'工作名称': 'C', '紧前工作': 'A', '持续时间': 7, 'partment': 'ENG'}
dictD = {'工作名称': 'D', '紧前工作': 'B', '持续时间': 6, 'partment': 'PROD'}
dictE = {'工作名称': 'E', '紧前工作': 'B', '持续时间': 5, 'partment': 'FIN'}
dictF = {'工作名称': 'F', '紧前工作': 'E', '持续时间': 3, 'partment': 'IT'}
dictG = {'工作名称': 'G', '紧前工作': 'D', '持续时间': 4, 'partment': 'MKT'}

lis = [dictA, dictB, dictC, dictD, dictE, dictF, dictG]

# 创建 DataFrame
df = pd.DataFrame(lis)

# 初始化 CPM 分析的列
df['Earliest_Start'] = 0
df['Earliest_Finish'] = 0
df['Latest_Start'] = 0
df['Latest_Finish'] = 0
df['Total_Float'] = 0
df['Free_Float'] = 0

# 第二部分:网络参数计算
# 正向传递:计算最早开始和最早结束时间
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['持续时间']

# 反向传递:计算最迟开始和最迟结束时间
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['持续时间']

# 计算总浮动和自由浮动
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']

# 将DataFrame转置并完整显示
pd.set_option('display.max_columns', None)
print(df.T)
工作名称          A    B    C     D    E   F    G
紧前工作          -    -    A     B    B   E    D
持续时间          3    5    7     6    5   3    4

Earliest_Start    0    0    3     5    5  10   11
Earliest_Finish   3    5   10    11   10  13   15
Latest_Start      5    0    8     5    7  12   11
Latest_Finish     8    5   15    11   12  15   15
Total_Float       5    0    5     0    2   2    0
Free_Float        0    0    5     0    0   2    0

5.3 最小成本日程

要找到最小成本日程,我们需要计算在各种情况下的直接费用和间接费用的总和,选择总成本最小的方案。首先我们需要列出每个工序的可能工期,计算其直接费用,然后再加上间接费用。按正常工时从下图中计算出总工期为15天。关键路线为①→③→④→⑥。

要寻找最小成本日程,计算按下列步骤进行:

(1)从关键工作中选出缩短工时所需直接费用最少的方案,并确定该方案可能缩短的天数;
(2)按照工作的新工时,重新计算网络计划的关键路线及关键工作;
(3)计算由于缩短工时所增加的直接费用。
不断重复上述三个步骤,直到工期不能再缩短为止。

从网络图看出,关键路线上的三道关键工作(1,3)、(3,4)、(4,6)中,工作(3,4)的成本斜率相比之下最小,应选择在工作(3,4)上缩短工时,查表可知,最多可缩短3天,即取工作(3,4)新工时为6-3=3(天)。重新计算网络图时间参数,结果如图a所示,关键路线为①→③→⑤→⑥,工期为13天,实际只缩短了2天。这意味着(3,4)工作没有必要减少3天,(3,4)工时应取6-2=4(天)。重新计算,结果如图b,总工期为13天,有两条关键路线:①→③→④→⑥与①→③→⑤→⑥,此次调整增加直接费用2×100=200(元)。

图a 图b

重复步骤(1)、(2)、(3),必须注意两条关键路线应同时缩短。有如下几个方案可选择:
①在B工作缩短1天,需要费用150(元);
②在D、E上同时缩短1天,需费用100+120=220(元);
③在G、E上同时缩短1天,需费用200+120=320(元);

取费用最小方案①,最多缩短2天,工时变为5-2=3(天)。重新计算网络时间参数,结果如图c所示。总工期为11天,这时的关键路径仍为两条:①→③→④→⑥与①→③→⑤→⑥,增加直接费用2×150=300(元)。

图c 图d

再进行第三次调整,可供选择的方案如下:
①在D、E上同时缩短1天,需费用100+120=220(元);
②在G、E上同时缩短1天,需费用200+120=320(元);
因为间接费用每减少1天缩短200元,而上述方案每缩短一天增加的费用均大于间接费用,所以继续缩短工期并不能减少费用,则停止计算,计算结果如图d所示。

过程名称 工作名称 可缩短天数/d 实际缩短天数/d 总直接费用/元 总间接费用/元 总成本/元 总工期/d
0 - - - 8800 3000 11800 15
1 D 3 2 9000 2600 11600 13
2 B 2 2 9300 2200 11500 11
posted @   郝hai  阅读(100)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
点击右上角即可分享
微信分享提示