运输能力限制的中转运输问题——Python实现(一)
在供应链中,中转运输是一项关键活动,用于解决商品在运输过程中的各种限制和需求。商业部门承担中转运输的责任,组织商品的再次发运,以确保顺利的货物流动。中转运输在供应链中具有重要作用,主要原因如下:
物流条件限制:由于运输条件的限制,商品可能无法直接一次性运送到目的地。这可能涉及到交通网络不完善、交通工具容量有限或道路状况不佳等问题。为了克服这些限制,需要将商品先运至适当的中转地点,再进行分运。
运输成本和效率:尽管有直达运输工具可用,但直接运输可能会面临高昂的运输费用或较长的发货时间。为了节约运输成本并加快商品的发运速度,可以选择合装整车运输到合适的中转地点,然后进行分运。这样可以通过优化装载效率和减少中转次数来提高整体运输效率。
联运服务的需求:在某些地区,交通运输部门可能没有提供联运服务。在这种情况下,商业部门需要承担中转运输的责任,以确保货物能够顺利到达目的地。
一、纯中转运输的数学模型
在供应链运输管理中经常要处理物资中转的运输问题,例如,物资从产地运到销地必须使用不同的运输工具,这样就需要首先将物资从产地运到某地(称为中转站),更换运输工具后再运往销地。又如,由于运输能力的限制或价格因素(转运运价小于直接运价),需要将不同产地的物资首先集中到某个中转站,再由中转站发往销地。应链网络问题大多属于混合非线性整数规划问题,本质上是NP-Hard问题,所以启发式算法通常是最有效的方法,遗传算法因为具有很好的鲁棒性和较强的全局搜索而在启发式算法中备受青睐。运输优化是供应链管理的一个重要分支,它在现实生活中有着广泛的应用。
1.1 纯中转问题
纯中转运输问题的一般提法是: 设有 \(r\) 个中转 站 \(T_1, T_2, \cdots, T_r\), 物资的运输过程是先从产地 \(A_i\) 运到某个中转站 \(T_k\), 再运往销地 \(B_j\) 。已知 \(A_i\) 到 \(T_k\) 的运价为 \(c_{i k}, T_k\) 到 \(B_j\) 的运价为 \(c_{k j}, A_i\) 的供给量为 \(a_i\), 通过 \(T_k\) 的最大运输能力为 \(d_k\), \(B_j\) 的需求量为 \(b_j\) 。不妨设
也就是说, 供需是平衡的且所有的物 资经转运后都能送达销地。现在要求一转运方案, 使得运输的总费用最小。
为建立转运问题的模型, 设决策变量如下:
\(x_{i k}\) :从 \(A_i\) 到 \(T_k\) 的调运量, \(i=1,2, \cdots, m ; k=1,2, \cdots, r\)
\(x_{k j}\) : 从 \(T_k\) 到 \(B_j\) 的调运量, \(k=1,2, \cdots, r ; j=1,2, \cdots, n\)
每个变量均非负。目标函数为两个阶段费用之和达到最小, 即
约束条件分为以下几组。
供给约束:
运输能力约束:
中转站平衡:
需求约束:
根据纯中转问题模型的结构可以看出, 转运问题也可转化为运输问题模型求解, 其方法是将每个中转站 \(T_k\) 既看成产地也看成销地, 从而形成一个有 \(m+r\) 个产地, 有 \(r+n\) 个销地的运输问题。由于物资不能由 \(A_i\) 直接到达 \(B_j\), 故 \(B_j\) 对 \(A_i\) 封锁。同样, 不同的中转站之间也互相封锁。
1.2 纯中转问题转换为平衡型
鉴于中转转的转运能力一般比实际的转运量要大,如果将转运站既看成产地又看成销地的话,综合的运转量是产量或销量的两倍,或者空转量应为
为此引入一个虚拟的产地\(A_{m+1}\),其产量为\(sup\);同时引入一个虚拟的销地\(B_{n+1}\),其销量为\(sup\),涉及这两个产地或销地的运费都为0,这样就保证了产销两端的产销平衡,可将该问题转化为\(m+1+r\)个产地,有\(n+1+r\)个销地的平衡型运输问题,\(T_k\) 的供给量和需求量均为 \(d_k\), 从而总供给量为 \(\sum_{i=1}^{m+1}a_i+\sum_{k=1}^r d_k\), 总需求量为 \(\sum_{j=1}^{n+1} b_j+\sum_{k=1}^r d_k\),以实现供需平衡。
1.3 示例
将某种物资从 \(A_1 、 A_2\) 和 \(A_3\) 运往 \(B_1 、 B_2 、 B_3, B_4\) 和 \(B_5\) 5个销地, 物资必须经过 \(T_1 、 T_2 、 T_3\) 和 \(T_4\) 中的任意一个中转站转运, 有关数据见表1和表2。试求解该中转问题。
解:表1:
\(T_1\) | \(T_2\) | \(T_3\) | \(T_4\) | 供 应 量 | |
---|---|---|---|---|---|
\(A_1\) | 4 | 5 | 7 | 6 | 70 |
\(A_2\) | 7 | 12 | 10 | 11 | 80 |
\(A_3\) | 6 | 11 | 8 | 9 | 90 |
中转站容量 | 60 | 90 | 120 | 70 | 340/240 |
表2:
\(B_1\) | \(B_2\) | \(B_3\) | \(B_4\) | \(B_5\) | |
---|---|---|---|---|---|
\(T_1\) | 4 | 4 | 3 | 7 | 3 |
\(T_2\) | 8 | 3 | 4 | 9 | 11 |
\(T_3\) | 4 | 3 | 4 | 7 | 7 |
\(T_4\) | 6 | 2 | 2 | 8 | 8 |
销地需求 | 30 | 20 | 80 | 50 | 60 |
表1化为平衡型运输问题:
\(T_1\) | \(T_2\) | \(T_3\) | \(T_4\) | 供 应 量 | |
---|---|---|---|---|---|
\(A_1\) | 4 | 5 | 7 | 6 | 70 |
\(A_2\) | 7 | 12 | 10 | 11 | 80 |
\(A_3\) | 6 | 11 | 8 | 9 | 90 |
\(A_4\) | 0 | 0 | 0 | 0 | 100 |
中转站容量 | 60 | 90 | 120 | 70 | 340/340 |
表2化为平衡型运输问题:
\(B_1\) | \(B_2\) | \(B_3\) | \(B_4\) | \(B_5\) | \(B_6\) | 供应量 | |
---|---|---|---|---|---|---|---|
\(T_1\) | 4 | 4 | 3 | 7 | 3 | 0 | 60 |
\(T_2\) | 8 | 3 | 4 | 9 | 11 | 0 | 90 |
\(T_3\) | 4 | 3 | 4 | 7 | 7 | 0 | 120 |
\(T_4\) | 6 | 2 | 2 | 8 | 8 | 0 | 70 |
销地需求 | 30 | 20 | 80 | 50 | 60 | 100 | 340/340 |
整合的平衡型运输问题:
\(T_1\) | \(T_2\) | \(T_3\) | \(T_4\) | \(B_1\) | \(B_2\) | \(B_3\) | \(B_4\) | \(B_5\) | \(B_6\) | 供应量 | |
---|---|---|---|---|---|---|---|---|---|---|---|
\(A_1\) | 4 | 5 | 7 | 6 | M | M | M | M | M | M | 70 |
\(A_2\) | 7 | 12 | 10 | 11 | M | M | M | M | M | M | 80 |
\(A_3\) | 6 | 11 | 8 | 9 | M | M | M | M | M | M | 90 |
\(A_4\) | 0 | 0 | 0 | 0 | M | M | M | M | M | M | 100 |
\(T_1\) | M | M | M | M | 4 | 3 | 7 | 3 | 7 | 0 | 60 |
\(T_2\) | M | M | M | M | 8 | 3 | 4 | 9 | 11 | 0 | 90 |
\(T_3\) | M | M | M | M | 4 | 3 | 4 | 7 | 7 | 0 | 120 |
\(T_4\) | M | M | M | M | 6 | 2 | 2 | 8 | 8 | 0 | 70 |
需求量 | 60 | 90 | 120 | 70 | 30 | 20 | 80 | 50 | 60 | 100 | 680 |
转化为平衡型运输问题后就可用表上作业法求解。
1.4 中转站无能力约束的纯中转问题
这时可以将各个中转站的能力约束换成所有的运输量$\sum_{i=1}^m a_i $的能力限制,按照上面方法求解即可,不再赘述。
二、转化为网络流问题
运输产生的费用也是供应链和整个物流系统成本结构的重要组成部分。可以说,一个高效率、低成本和高反应能力的运输网络对一个成功的物流配送体系至关重要,这就使得运输网络的优化成为配送体系中一项重要的运营决策,关系到物流设计体系的成功与否。运输网络的优化主要是对运输路线的安排,即选择合理的配送路线,既能保证配送效率的最大化,又能同时使运输成本最低。
2.1 化为最小费用最大流问题
某公司链接产地到销地的物流运输体系为例进行说明。其中,产品运输网络如下图所示,图中各弧表示运输道路。由于道路实际地质情况不同,使得每条道路上的运输费用也不同,因此优化该运输系统除考虑货物的最大流外,还需要考虑道路运输的最小费用,即可基于本文所提的最小费用最大流模型予以求解。
2.2 示例
例1:某供应链物流部门拟从仓库运往某商品到市场销售。已知各仓库的可供量、各市场需求量、以及从\(i\)仓库至\(j\)市场的单位运费为\(c_{ij}\) ,如下表所示,试将其转化为最小费用最大流问题。
1 | 2 | 3 | 可供量 | |
---|---|---|---|---|
\(A_1\) | 20 | 24 | 5 | 8 |
\(A_2\) | 30 | 22 | 20 | 7 |
销售量 | 4 | 5 | 6 |
例2:某供应链企业经销汽车配件A,下设2个分别位于沈阳和郑州的加工厂。该公司每月需要把各产地生产的产品分别运往北京、上海、广州3个销售点。运输过程中,允许经天津、武汉2个中转站进行转运。公司在租用运输车辆时,租赁公司给出如下的单位运价表。问在考虑到产销地之间非直接运输的各种可能方案的情况下,如何将2个加工厂每月生产的产品运往销售地,使总的运费最低?
天津\(T_1\) | 武汉\(T_2\) | 北京\(B_1\) | 上海\(B_2\) | 广州\(B_3\) | 供应量 | |
---|---|---|---|---|---|---|
沈阳\(A_1\) | 210 | 470 | — | — | — | 500 |
郑州\(A_2\) | 230 | 160 | — | — | — | 300 |
天津\(T_1\) | — | — | 100 | 170 | 300 | |
武汉\(T_2\) | — | — | 315 | 130 | 150 | |
需求量 | 300 | 400 | 100 | 800/800 |
上面无穷可以换成相应产地最大的运量,如果有运输能力约束可以调整为运力。
三、纯中转问题的Python求解
例3:某供应链企业经销汽车配件A,下设2个分别位于沈阳和郑州的加工厂。该公司每月需要把各产地生产的产品分别运往北京、上海、广州3个销售点。运输过程中,允许经天津、武汉2个中转站进行转运。公司在租用运输车辆时,租赁公司给出如下的单位运价表。天津和武汉两个中转地的运输能力约束为500,400,问在考虑到产销地之间非直接运输的各种可能方案的情况下,如何将2个加工厂每月生产的产品运往销售地,使总的运费最低?
天津\(T_1\) | 武汉\(T_2\) | 北京\(B_1\) | 上海\(B_2\) | 广州\(B_3\) | 供应量 | |
---|---|---|---|---|---|---|
沈阳\(A_1\) | 210 | 470 | — | — | — | 500 |
郑州\(A_2\) | 230 | 160 | — | — | — | 300 |
天津\(T_1\) | — | — | 100 | 170 | 300 | 500 |
武汉\(T_2\) | — | — | 315 | 130 | 150 | 400 |
需求量 | 500 | 400 | 300 | 400 | 100 |
3.1 按运输问题求解
天津\(T_1\) | 武汉\(T_2\) | 北京\(B_1\) | 上海\(B_2\) | 广州\(B_3\) | 虚拟\(B_4\) | 供应量 | |
---|---|---|---|---|---|---|---|
沈阳\(A_1\) | 210 | 470 | — | — | — | — | 500 |
郑州\(A_2\) | 230 | 160 | — | — | — | — | 300 |
虚拟\(A_3\) | 0 | 0 | — | — | — | — | 100 |
天津\(T_1\) | — | — | 100 | 170 | 300 | 0 | 500 |
武汉\(T_2\) | — | — | 315 | 130 | 150 | 0 | 400 |
需求量 | 500 | 400 | 300 | 400 | 100 | 100 | 1800/1800 |
#f = float('inf') # float('inf')表示无穷大
f=100000
import pulp
import numpy as np
from pprint import pprint
def transport_problem(costs, x_max, y_max):
row = len(costs)
col = len(costs[0])
prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMinimize) # Changed to minimize
var = [[pulp.LpVariable(f'x{i}{j}', lowBound=0, cat=pulp.LpInteger)
for j in range(col)] for i in range(row)]
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
prob += pulp.lpDot(flatten(var), costs.flatten()) # Objective function
for i in range(row):
prob += (pulp.lpSum(var[i])) == x_max[i] # Modified constraint: Equality instead of inequality
for j in range(col):
prob += (pulp.lpSum(var[i][j] for i in range(row))) == y_max[j] # Modified constraint: Equality instead of inequality
prob.solve()
return {'objective': pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for
i in range(row)]}
if __name__ == '__main__':
costs = np.array([[210, 470, f, f, f, f],
[230, 160, f, f, f, f],
[0, 0, f, f, f, f],
[f, f, 100, 170, 300, 0],
[f, f, 315, 130, 150, 0]])
max_plant = [500, 300, 100, 500, 400]
max_cultivation = [500, 400, 300, 400, 100, 100]
res = transport_problem(costs, max_plant, max_cultivation)
print(f'最小值为{res["objective"]}')
print('各变量的取值为: ')
pprint(res['var'])
最小值为254000.0
各变量的取值为:
[[500.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 300.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 100.0, 0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 300.0, 100.0, 0.0, 100.0],
[0.0, 0.0, 0.0, 300.0, 100.0, 0.0]]
中转带虚拟运输量时,运输问题有时会出现虚拟量的流转,会给问题处理带来一些困扰,和下面计算比较会发现这些问题。
3.2 按最小费用最大流求解
import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx # 导入 NetworkX 工具包
# 创建有向图
G3 = nx.DiGraph() # 创建一个有向图 DiGraph
G3.add_edges_from([('s','v1',{'capacity': 500, 'weight': 0}),
('s','v2',{'capacity': 300, 'weight': 0}),
('v1','v3',{'capacity': 500, 'weight': 210}),
('v1','v4',{'capacity': 500, 'weight': 470}),
('v2','v3',{'capacity': 400, 'weight': 230}),
('v2','v4',{'capacity': 400, 'weight': 160}),
('v3', 'v5', {'capacity': 500, 'weight': 100}),
('v3', 'v6', {'capacity': 500, 'weight': 170}),
('v3', 'v7', {'capacity': 500, 'weight':300}),
('v4','v5',{'capacity': 400, 'weight': 315}),
('v4','v6',{'capacity': 400, 'weight': 130}),
('v4','v7',{'capacity': 400, 'weight': 150}),
('v5','t',{'capacity': 300, 'weight': 0}),
('v6','t', {'capacity': 400, 'weight': 0}),
('v7','t',{'capacity': 100, 'weight': 0})]) # 添加边的属性 'capacity', 'weight'
# 求最小费用最大流
minCostFlow = nx.max_flow_min_cost(G3, 's', 't') # 求最小费用最大流
minCost = nx.cost_of_flow(G3, minCostFlow) # 求最小费用的值
maxFlow = sum(minCostFlow['s'][j] for j in minCostFlow['s'].keys()) # 求最大流量的值
# # 数据格式转换
edgeLabel1 = nx.get_edge_attributes(G3,'capacity') # 整理边的标签,用于绘图显示
edgeLabel2 = nx.get_edge_attributes(G3,'weight')
edgeLabel={}
for i in edgeLabel1.keys():
edgeLabel[i]=f'({edgeLabel1[i]:},{edgeLabel2[i]:})' # 边的(容量,成本)
edgeLists = []
for i in minCostFlow.keys():
for j in minCostFlow[i].keys():
edgeLabel[(i, j)] += ',f=' + str(minCostFlow[i][j]) # 将边的实际流量添加到 边的标签
if minCostFlow[i][j]>0:
edgeLists.append((i,j))
print("最小费用最大流的路径及流量: ", minCostFlow) # 输出最大流的途径和各路径上的流量
print("最小费用最大流的路径:", edgeLists) # 输出最小费用最大流的途径
print("最大流量: ", maxFlow) # 输出最大流量的值
print("最小费用: ", minCost) # 输出最小费用的值
# 绘制有向网络图
pos={'s':(0,5), 'v1':(3,9), 'v2':(3,1), 'v3':(6,9), 'v4':(6,1),'v5':(9,12), 'v6':(9,6),'v7':(9,1),'t':(12,5)} # 指定顶点绘图位置
fig, ax = plt.subplots(figsize=(8,6))
ax.text(5,1.5,"youcans-xupt",color='gainsboro')
ax.set_title("Minimum Cost Maximum Flow with NetworkX")
nx.draw(G3,pos,with_labels=True,node_color='c',node_size=300,font_size=10) # 绘制有向图,显示顶点标签
nx.draw_networkx_edge_labels(G3,pos,edgeLabel,font_size=10) # 显示边的标签:'capacity','weight' + minCostFlow
nx.draw_networkx_edges(G3,pos,edgelist=edgeLists,edge_color='m',width=2) # 设置指定边的颜色、宽度
plt.axis('on')
plt.show()
最小费用最大流的路径及流量: {'s': {'v1': 500, 'v2': 300}, 'v1': {'v3': 500, 'v4': 0}, 'v2': {'v3': 0, 'v4': 300}, 'v3': {'v5': 300, 'v6': 200, 'v7': 0}, 'v4': {'v5': 0, 'v6': 200, 'v7': 100}, 'v5': {'t': 300}, 'v6': {'t': 400}, 'v7': {'t': 100}, 't': {}}
最小费用最大流的路径: [('s', 'v1'), ('s', 'v2'), ('v1', 'v3'), ('v2', 'v4'), ('v3', 'v5'), ('v3', 'v6'), ('v4', 'v6'), ('v4', 'v7'), ('v5', 't'), ('v6', 't'), ('v7', 't')]
最大流量: 800
最小费用: 258000
复杂运输问题用网络图处理结果更好!