网络流优化

1.网络流优化

1.1 网络流

  网络流优化问题是基本的网络优化问题,应用非常广泛,遍及通讯、运输、电力、工程规划、任务分派、设备更新以及计算机辅助设计等领域。
  流从源点流出、通过路径输送、流入到汇点,从而将目标从源点输送到汇点。流在生活中十分常见,例如交通系统中的人流、车流、物流,供水管网中的水流,金融系统中的现金流,网络中的信息流。
  现实中的任何路径都有最大流量(容量)的限制,在网络中也是如此,并以边的容量(Capacity)表示,一条边的流量不能超过它的容量。
  把这些现实问题抽象为网络流问题,其特征是:(1)有向图上的每条边具有容量限制;(2)从源点流出的流量,等于汇点流入的流量;(3)源点和汇点之外的所有中间节点,流出的流量等于流入的流量。
  注意在网络流问题中有几组概念容易混淆:
  源点/汇点,起点/终点,供应点/需求点:源点是只进不出的点,汇点是只出不进的点。源点/汇点 可以指定为问题的 起点/终点,但本质上源点/汇点是由网络结构特征决定的,而不是被指定的。供应点的供应量和需求点的需求量是固定/确定的,而源点/汇点的目标是发出/接收的流量最大,不是固定值。
  容量 与 流量:容量是路径(边、弧)允许的最大流通能力,用 c(i,j) 表示;流量则是路径(边、弧)上的实际流量,用 f(i,j) 表示。
 

1.2 典型的网络流优化问题

  网络流优化问题最重要的指标是每条边的成本和容量限制,既要考虑成本最低(最短路径问题),又要满足容量限制(最大流问题),由此产生了网络最大流问题、最小费用流问题、最小费用最大流问题。

  最大流问题(Maximun flow problem):已知每条边的容量,研究如何充分利用网络能力,使从源点到汇点的总流量最大,也即在容量网络中求流量最大的可行流。

  最小费用流问题(Minimum cost problem):已知每条边的容量和单位流量的费用,对于给定的源点、汇点流量,研究如何分配流量和路径,使总费用最小,也即在容量费用网络中求成本最低的可行流。

  最小费用最大流问题(Minimum cost maximum flow),已知每条边的容量和单位流量的费用,研究网络的流量最大的路径中,费用最小的路径。简单地说,就是满足最大流的路径可能有多条,需要从其中找到成本最低的路径。

 

2. 网络最大流问题(MFP

2.1 网络最大流算法

  网络最大流问题,是在容量网络 G(V,E) 中求流量 v(f) 达到最大的可行流 f。在最大流问题中,只能有一个源点和一个汇点。

  求解网络最大流主要有增广路法和预流推进法两类方法。

 

2.2 NetworkX 求解网络最大流问题

  Network 工具包提供了多种求解网络最大流问题的算法和函数。其中 maximum_flow()、maximum_flow_value()、minimum_cut()、minimum_cut_value() 是集成了多种算法的通用函数,可以设置算法选项调用对应的算法;其它函数则是具体的算法实现函数。

函数

功能

maximum_flow(flowG,s,t[, capacity,…])

计算最大流

maximum_flow_value(flowG,s,t[,…])

计算最大的单一目标流的值

minimum_cut(flowG,s,t[, capacity,flow_func])

计算最小割的值和节点分区

minimum_cut_value(flowG,s,t[,capacity,…])

计算最小割的值

edmonds_karp(G,s,t[,capacity,…])

Edmonds-Karp 算法求最大流

shortest_augmenting_path(G,s,t[,…])

SAP算法求最大流

dinitz(G,s,t[,capacity,…])

Dinitz 算法求最大流

preflow_push(G,s,t[,capacity,…])

HLPP 算法求最大流

boykov_kolmogorov(G,s,t[,capacity,…])

Boykov-Kolmogorov 算法求最大流

 

2.3 maximum_flow() 函数说明

maximum_flow (flowG, _s, _t, capacity=‘capacity’, flow_func=None, *kwargs)

maximum_flow_value (flowG, _s, _t, capacity=‘capacity’, flow_func=None, *kwargs)

 

主要参数:

flowG(NetworkX graph):有向图,边必须带有容量属性 capacity(不能用 ‘weight’ )。

_s (node):源点。

_t (node):汇点。

capacity (string):边的容量属性 capacity,缺省视为无限容量。

flow_func(function):调用算法的函数名,如:‘edmonds_karp’, ‘shortest_augmenting_path’, ‘dinitz’, ‘preflow_push’, ‘boykov_kolmogorov’。缺省值 ‘None’ ,选择 ‘preflow_push’(HLPP 算法)。

返回值:

flow_value(integer, float):网络最大流的流量值

flow_dict (dict):字典类型,网络最大流的流经路径及各路径的分配流量

注意:如果要选择指定算法,需要写成以下形式 flow_func=nx.algorithms.flow.edmonds_karp,而不是 flow_func=edmonds_karp。也可以写成:

from networkx.algorithms.flow import edmonds_karp

flowValue, flowDict = nx.maximum_flow(G1, 's', 't', flow_func=edmonds_karp)

 

2.4 案例:输油管网的最大流量

问题描述:

  在输油管网中,通过输油管连接生产石油的油井、储存石油的油库和转运的中间泵站。各站点之间的连接及管路的容量如图所示,求从油井到油库的最大流量和具体方案。

问题分析:

  这是一个网络最大流问题,可以用顶点表示油井、油库和泵站,其中油井为源点 s、油库为汇点 t,用有向边表示输油管,有向边的权 capacity 表示输油管的最大流量(容量)。

  用 NetworkX 的 maximum_flow() 函数即可求出从从源点 s 到汇点 t 的最大流量。

 

程序说明:

  1.图的输入。本例为稀疏有向图,使用 nx.DiGraph() 定义一个有向图。通过 add_edge(‘s’, ‘a’, capacity=6) 定义有向边和属性 capacity。注意必须以关键字 ‘capacity’ 表示容量,不能用权值 ‘weight’ 或其它关键字代替。

  2.nx.maximum_flow_value() 返回网络最大流的值,nx.maximum_flow() 可以同时返回网络最大流的值和网络最大流的路径及分配的流量。

  3.maxFlowDict 为字典类型,具体格式参加 2.6 程序运行结果。为了得到最大流所流经的边的列表edgeLists,要对 maxFlowDict 进行整理和格式转换。

  4.在网络最大流图中,以边的标签显示了边的容量 c 和流量 f。

Python例程

import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包

# 1. 最大流问题 (Maximum Flow Problem,MFP)
# 创建有向图
G1 = nx.DiGraph()  # 创建一个有向图 DiGraph
G1.add_edge('s', 'a', capacity=6)  # 添加边的属性 "capacity"
G1.add_edge('s', 'c', capacity=8)
G1.add_edge('a', 'b', capacity=3)
G1.add_edge('a', 'd', capacity=3)
G1.add_edge('b', 't', capacity=10)
G1.add_edge('c', 'd', capacity=4)
G1.add_edge('c', 'f', capacity=4)
G1.add_edge('d', 'e', capacity=3)
G1.add_edge('d', 'g', capacity=6)
G1.add_edge('e', 'b', capacity=7)
G1.add_edge('e', 'j', capacity=4)
G1.add_edge('f', 'h', capacity=4)
G1.add_edge('g', 'e', capacity=7)
G1.add_edge('h', 'g', capacity=1)
G1.add_edge('h', 'i', capacity=3)
G1.add_edge('i', 'j', capacity=3)
G1.add_edge('j', 't', capacity=5)

# 求网络最大流
# maxFlowValue = nx.maximum_flow_value(G1, 's', 't')  # 求网络最大流的值
# maxFlowValue, maxFlowDict = nx.maximum_flow(G1, 's', 't')  # 求网络最大流
from networkx.algorithms.flow import edmonds_karp  # 导入 edmonds_karp 算法函数
maxFlowValue, maxFlowDict = nx.maximum_flow(G1, 's', 't', flow_func=edmonds_karp)  # 求网络最大流

# 数据格式转换
edgeCapacity = nx.get_edge_attributes(G1, 'capacity')
edgeLabel = {}  # 边的标签
for i in edgeCapacity.keys():  # 整理边的标签,用于绘图显示
    edgeLabel[i] = f'c={edgeCapacity[i]:}'  # 边的容量
edgeLists = []  # 最大流的边的 list
for i in maxFlowDict.keys():
    for j in maxFlowDict[i].keys():
        edgeLabel[(i, j)] += ',f=' + str(maxFlowDict[i][j])  # 取出每条边流量信息存入边显示值
        if maxFlowDict[i][j] > 0:  # 网络最大流的边(流量>0)
            edgeLists.append((i,j))

# 输出显示
print("最大流值: ", maxFlowValue)
print("最大流的途径及流量: ", maxFlowDict)  # 输出最大流的途径和各路径上的流量
print("最大流的路径:", edgeLists)  # 输出最大流的途径

# 绘制有向网络图
fig, ax = plt.subplots(figsize=(8, 6))   #绘制画布
pos = {'s': (1, 8), 'a': (6, 7.5), 'b': (9, 8), 'c': (1.5, 6), 'd': (4, 6), 'e': (8, 5.5),  # 指定顶点绘图位置
       'f': (2, 4), 'g': (5, 4), 'h': (1, 2), 'i': (5.5, 2.5), 'j': (9.5, 2), 't': (11, 6)}
edge_labels = nx.get_edge_attributes(G1, 'capacity')
ax.set_title("Maximum flow of petroleum network with NetworkX")  # 设置标题
nx.draw(G1, pos, with_labels=True, node_color='c', node_size=300, font_size=10)  # 绘制有向图,显示顶点标签
nx.draw_networkx_edge_labels(G1, pos, edgeLabel, font_color='navy')  # 显示边的标签:'capacity' + maxFlow
nx.draw_networkx_edges(G1, pos, edgelist=edgeLists, edge_color='m')  # 设置指定边的颜色、宽度
plt.axis('on')
plt.show()
最大流问题

 

 

3. 最小费用流问题(MCP

3.1 最小费用流问题的算法

  在实际问题中,我们总是希望在完成运输任务的同时,寻求运输费用最低的方案。最小费用流问题,就是要以最小费用从出发点(供应点)将一定的流量输送到接收点(需求点)。在最小流问题中,供应点、需求点的数量可以是一个或多个,但每个供应点的供应量和需求点的需求量是固定的。

  求解最小费用流问题的方法很多,常见的如:连续最短路算法(Successive shortest path)、消圈算法(Cycle canceling)、原始对偶算法(Primal dual)、网络单纯性算法(Network simplex)和非均衡网络流算法(Out of Kilter法)等。

  网络单纯形是单纯形算法的一个特殊应用,它使用生成树基来更有效地解决具有纯网络形式的线性规划问题。网络单纯性为最小费用流问题提供了标准的解决方法,可以解决数万个节点的大型问题。

  最小费用流问题最重要的应用是配送网络的优化,如确定如何从出发地运送到中转站再转运到客户的配送方案。运输问题、指派问题、转运问题、最大流问题、最短路径问题,都是特殊情况下的最小费用流问题。例如,最短路径问题是流量 v=1 的最小费用流问题,最大流问题是最大流量下的最小费用流问题。只要选定合适的权重、容量、流量,解决最小费用流的方法就能用来解决上述问题。

3.2 NetworkX 求解最小费用流问题

Network 工具包提供了多个求解最小费用流问题的函数,所用的基本算法都是网络单纯性算法。

函数

功能

network_simplex(G,[,demand,capacity,weight])

单纯性法计算最小成本流

min_cost_flow_cost(G,[,demand,capacity,weight])

计算最小成本流的成本

min_cost_flow(G,[,demand,capacity,weight])

计算最小成本流

max_flow_min_cost(G,s,t[,capacity,weight])

计算最小成本的最大流

capacity_scaling(G[,demand,capacity,…])

计算容量缩放最小成本流

 

3.3 min_cost_flow() 函数说明

min_cost_flow()、min_cost_flow_cost() 是求解费用最小流问题的函数,通过调用网络单纯性算法函数 network_simplex() 求解。

min_cost_flow(G, demand=‘demand’, capacity=‘capacity’, weight=‘weight’)

min_cost_flow_cost(G, demand=‘demand’, capacity=‘capacity’, weight=‘weight’)

 

主要参数:

G(NetworkX graph):有向图,边必须带有容量属性 capacity、单位成本属性 ‘weight’ 。

demand (string):顶点的需求量属性 demand,表示节点的净流量:负数表示供应点的净流出量,正数表示需求点的净流入量,0 表示中转节点。缺省值为 0。

capacity (string):边的容量,缺省视为无限容量。

weight (string):边的单位流量的费用,缺省值为 0。

返回值:

flowDict (dict):字典类型,最小费用流的流经路径及各路径的分配流量

flowCost(integer, float):满足需求的最小费用流的总费用

NetworkXUnfeasible:输入的净流量(demand)不平衡,或没有满足需求流量的可行流时,抛出异常信息。

注意:费用最小流函数 min_cost_flow() 中并没有设定供应点、需求点,而是通过设置顶点属性 ‘demand’ 确定供应点、需求点及各顶点的净流量,因而允许网络中存在多个供应点、

3.4 案例:运输费用

问题描述:

  从 s 将货物运送到 t。已知与 s、t 相连各道路的最大运输能力、单位运量的费用如图所示(参见 3.6 程序运行结果图),图中边上的参数 (9,4) 表示道路的容量为 9,单位流量的费用为 4。求流量 v 的最小费用流。

问题分析:

  这是一个最小费用流问题。用 NetworkX 的 nx.min_cost_flow() 函数或 nx.network_simplex() 函数即可求出从供应点到需求点的给定流量 v 的最小费用流。

程序说明:

  1.图的输入。本例为稀疏的有向图,使用 nx.DiGraph() 定义一个有向图,用G.add_weighted_edges_from() 函数以列表向图中添加多条赋权边,每个赋权边以元组 (node1,node2,{‘capacity’:c1, ‘weight’:w1}) 定义属性 ‘capacity’ 和 ‘weight’。注意必须以关键字 ‘capacity’ 表示容量,以 ‘weight’ 表示单位流量的费用。

  2.nx.shortest_path() 用于计算最短路径,该段不是必须的。将最短路径的计算结果与最小费用流的结果进行比较,可以看到流量 v=1 时最小费用流的结果与最短路径结果是相同的。

  3.nx.cost_of_flow() 用于计算最小费用最大流,该段不是必须的。将最小费用最大流的计算结果与最小费用流的结果进行比较,可以看到在最大流量 v=14 时最小费用流的结果与最小费用最大流结果是相同的。

  4.最小费用流是基于确定的流量 v 而言的。流量 v 可以在程序中赋值;例程中 v 从 1 逐渐递增,计算所有流量下的最小费用流,直到达到网络容量的极限(如果再增大流量将会超出网络最大容量,没有可行流,计算最小费用流失败)。

  5.NetworkX 计算最小费用流时不是在函数中指定源点、汇点和流量,而是通过向源点、汇点添加属性 demand 实现的。demand 为正值时表示净输入流量,demand 为负值时表示净输出流量,这使我们可以指定多源多汇。

  6.nx.min_cost_flow() 返回最小费用流的路径和流量分配,字典格式;nx.min_cost_flow_cost() 返回最小费用流的费用值。nx.network_simplex() 也可以求最小费用流,返回最小费用流的费用值,路径和流量分配。

  7.在最小费用流图中(最大流量 v=14),以边的标签显示了边的容量 c、单位流量的费用 w 和流量 f,如 (8,4),f=7 表示边的容量为 8,单位流量的费用为 4,分配流量为 7。

  8.在最小费用流图中(最大流量 v=14),以不同颜色(edge_color=‘m’)和宽度(width=2)表示最小费用流的边,未使用的流量为 0 (f=0)的边以黑色绘制。

Python例程

import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包

# 2. 最小费用流问题(Minimum Cost Flow,MCF)
# 创建有向图
G2 = nx.DiGraph()  # 创建一个有向图 DiGraph
G2.add_edges_from([('s','v1',{'capacity': 7, 'weight': 4}),
                  ('s','v2',{'capacity': 8, 'weight': 4}),
                  ('v1','v3',{'capacity': 9, 'weight': 1}),
                  ('v2','v1',{'capacity': 5, 'weight': 5}),
                  ('v2','v4',{'capacity': 9, 'weight': 4}),
                  ('v3','v4',{'capacity': 6, 'weight': 2}),
                  ('v3','t',{'capacity': 10, 'weight': 6}),
                  ('v4','v1',{'capacity': 2, 'weight': 1}),
                  ('v4','t',{'capacity': 5, 'weight': 2})]) # 添加边的属性 'capacity', 'weight'
# 整理边的标签,用于绘图显示
edgeLabel1 = nx.get_edge_attributes(G2, 'capacity')
edgeLabel2 = nx.get_edge_attributes(G2, 'weight')
edgeLabel = {}
for i in edgeLabel1.keys():
    edgeLabel[i] = f'({edgeLabel1[i]:},{edgeLabel2[i]:})'  # 边的(容量,成本)

# 计算最短路径---非必要,用于与最小费用流的结果进行比较
lenShortestPath = nx.shortest_path_length(G2, 's', 't', weight="weight")
shortestPath = nx.shortest_path(G2, 's', 't', weight="weight")
print("\n最短路径: ", shortestPath)  # 输出最短路径
print("最短路径长度: ", lenShortestPath)  # 输出最短路径长度

# 计算最小费用最大流---非必要,用于与最小费用流的结果进行比较
minCostFlow = nx.max_flow_min_cost(G2, 's', 't')  # 求最小费用最大流
minCost = nx.cost_of_flow(G2, minCostFlow)  # 求最小费用的值
maxFlow = sum(minCostFlow['s'][j] for j in minCostFlow['s'].keys())  # 求最大流量的值
print("\n最大流量: {}".format(maxFlow))  # 输出最小费用的值
print("最大流量的最小费用: {}\n".format(minCost))  # 输出最小费用的值

# v = input("Input flow (v>=0):")
v = 0
while True:
    v += 1  # 最小费用流的流量
    G2.add_node("s", demand=-v)  # nx.min_cost_flow() 的设置要求
    G2.add_node("t", demand=v)  # 设置源点/汇点的流量

    try: # Youcans@XUPT
        # 求最小费用流(demand=v)
        minFlowCost = nx.min_cost_flow_cost(G2)  # 求最小费用流的费用
        minFlowDict = nx.min_cost_flow(G2)  # 求最小费用流
        # minFlowCost, minFlowDict = nx.network_simplex(G2)  # 求最小费用流--与上行等效
        print("流量: {:2d}\t最小费用:{}".format(v, minFlowCost))  # 输出最小费用的值(demand=v)
        # print("最小费用流的路径及流量: ", minFlowDict)  # 输出最大流的途径和各路径上的流量
    except Exception as e:
        print("流量: {:2d}\t超出网络最大容量,没有可行流。".format(v))
        print("\n流量 v={:2d}:计算最小费用流失败({})。".format(v, str(e)))
        break  # 结束 while True 循环

edgeLists = []
for i in minFlowDict.keys():
    for j in minFlowDict[i].keys():
        edgeLabel[(i, j)] += ',f=' + str(minFlowDict[i][j])  # 取出每条边流量信息存入边显示值
        if minFlowDict[i][j] > 0:
            edgeLists.append((i, j))

maxFlow = sum(minFlowDict['s'][j] for j in minFlowDict['s'].keys())  # 求最大流量的值
print("\n最大流量: {:2d},\t最小费用:{}".format(maxFlow, minFlowCost))  # 输出最小费用的值
print("最小费用流的路径及流量: ", minFlowDict)  # 输出最小费用流的途径和各路径上的流量
print("最小费用流的路径:", edgeLists)  # 输出最小费用流的途径

# 绘制有向网络图
pos={'s':(0,5),'v1':(4,2),'v2':(4,8),'v3':(10,2),'v4':(10,8),'t':(14,5)}  # 指定顶点绘图位置
fig, ax = plt.subplots(figsize=(8,6))
ax.text(6,2.5,"youcans-xupt",color='gainsboro')
ax.set_title("Minimum Cost Maximum Flow with NetworkX")
nx.draw(G2,pos,with_labels=True,node_color='c',node_size=300,font_size=10)   # 绘制有向图,显示顶点标签
nx.draw_networkx_edge_labels(G2,pos,edgeLabel,font_size=10)  # 显示边的标签:'capacity','weight' + minCostFlow
nx.draw_networkx_edges(G2,pos,edgelist=edgeLists,edge_color='m',width=2)  # 设置指定边的颜色、宽度
plt.axis('on')
plt.show()
最小费用流问题

 

 

4. 最小费用最大流问题(MCMF

4.1 最小费用最大流问题的算法

  最小成本最大流问题可以看做是最短路径问题和最大流问题的结合,既要像最短路径问题那样考虑成本最小,又要考虑到每条边上的流量限制。最短路径问题和最大流问题在本质上也是特殊的最小成本最大流问题,是网络优化中的基本问题。

  求解最小费用最大流问题的常用方法有 Bellman-Ford算法、SPFA算法、Dijkstra 改进算法。

  在 NetworkX 工具包中,求解最小费用最大流问题的方法与众不同:先调用 nx.maximum_flow_value() 函数求网络最大流,再以最大流调用 min_cost_flow() 函数求网络最大流时的最小费用流。

 

4.2 max_flow_min_cost() 函数说明

max_flow_min_cost()是求解最小费用最大流问题的函数。
max_flow_min_cost(G, s, t, capacity=‘capacity’, weight=‘weight’)
cost_of_flow(G, flowDict, weight=‘weight’)
主要参数:
G(NetworkX graph):有向图,边必须带有容量属性 capacity、单位成本属性 ‘weight’ 。
s (node):流的源点。
t (node):流的汇点。
capacity (string):边的容量,缺省视为无限容量。
weight (string):边的单位流量的费用,缺省值为 0。
返回值:
flowDict (dict):字典类型,最小费用最大流的流经路径及各路径的分配流量。
使用 cost_of_flow() 函数,可以由流经路径及各路径的分配流量 flowDict 计算可行流的成本。

 

4.3 案例:输油管网的最大流量和最小费用

问题描述:

  某输油网络 G 中的每段管路允许的容量和单位流量的运输费用如图所示,图中边上的参数 (9,5) 表示边的容量为 9,单位流量的费用为 5。求从网络源点 s 到汇点 t 的最大流量,及输送最大流量的最小费用。

问题分析:

  这是一个的最小费用最大流问题。用Networkx的nx.max_flow_min_cost() 函数可以求出从网络源点到汇点的最小费用最大流。

程序说明:

  1.图的输入。用 nx.DiGraph() 定义一个有向图。用 G.add_weighted_edges_from() 函数以列表向图中添加多条赋权边,每个赋权边以元组 (node1,node2,{‘capacity’:c1, ‘weight’:w1}) 定义属性 ‘capacity’ 和 ‘weight’。注意必须以关键字 ‘capacity’ 表示容量,以 ‘weight’ 表示单位流量的费用。

  2.nx.max_flow_min_cost(G3, ‘s’, ‘t’) 用来计算从源点 ‘s’ 到汇点 ‘t’ 的最小费用最大流,返回最大流的途径和各路径上的流量分配,字典格式。

  3.nx.cost_of_flow() 计算一个可行流的费用,例程中用来计算最小费用最大流的费用。

  4.maxFlow 计算从源点 ‘s’ 发出的所有路径上的流量总和,得到最大流量的值。

  5.在最小费用最大流图中,以边的标签显示了边的容量 c、单位流量的费用 w 和流量 f,如 (13,7),f=11表示边的容量为 13,单位流量的费用为 7,分配流量为 11。

  6.最小费用最大流图中,以不同颜色(edge_color=‘m’)和宽度(width=2)表示最小费用流的边,未使用的流量为 0 (f=0)的边以黑色绘制。

Python例程

import numpy as np
import matplotlib.pyplot as plt # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包

# 3. 最小费用最大流问题(Minimum Cost Maximum Flow,MCMF)
# 创建有向图
G3 = nx.DiGraph()  # 创建一个有向图 DiGraph
G3.add_edges_from([('s','v1',{'capacity': 13, 'weight': 7}),
                  ('s','v2',{'capacity': 9, 'weight': 9}),
                  ('v1','v3',{'capacity': 6, 'weight': 6}),
                  ('v1','v4',{'capacity': 5, 'weight': 5}),
                  ('v2','v1',{'capacity': 4, 'weight': 4}),
                  ('v2','v3',{'capacity': 5, 'weight': 2}),
                  ('v2','v5',{'capacity': 5, 'weight': 5}),
                  ('v3','v4',{'capacity': 5, 'weight': 2}),
                  ('v3','v5',{'capacity': 4, 'weight': 1}),
                  ('v3','t',{'capacity': 4, 'weight': 4}),
                  ('v4','t', {'capacity': 9, 'weight': 7}),
                  ('v5','t',{'capacity': 9, 'weight': 5})]) # 添加边的属性 '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,5), 'v4':(9,9),'v5':(9,1), 't':(12,5)}  # 指定顶点绘图位置
fig, ax = plt.subplots(figsize=(8,6))
# 增加水印ax.text(x, y, s,color=''):x,y:注释的坐标位置(标量),s:注释的内容(字符串)
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()
最大流且最小费用问题

 

5. 网络最大流问题的应用与推广

5.1 指定流量的可行流

  如果不是计算最大流,而是要计算指定流量 v 的可行流,可以用如下图所示的增广路(相当于辅助线)方法处理。

  另一种思路是,设置源点 s /汇点 t 的流量为 v,再按最小费用流即可。这种方法不仅得到了流量 v 的可行流,而且是最小费用流,但该方法的计算量较大。主要程序如下:

G2.add_node("s", demand=-v)  # nx.min_cost_flow() 的设置要求

G2.add_node("t", demand=v)  # 设置源点/汇点的流量

# 求最小费用流(demand=v)

minFlowCost = nx.min_cost_flow_cost(G2)  # 求最小费用流的费用

minFlowDict = nx.min_cost_flow(G2)  # 求最小费用流

 

5.2 多源点多汇点的最大流

  以上都讨论的网络流优化问题,都是单源点、单汇点的网络。对于多源点单汇点、单源点多汇点、多源点多汇点的容量网络的最大流问题,可以通过以下所示的增加一个超级源点和/或超级汇点的方法处理。

  (1)在容量网络 G 上增加一个超级源点 s 和一个超级源点 t;从超级源点 s 向网络 G 中的每个源点连一条容量为 +∞ 的边,从网络 G 中的每个汇点向超级汇点 t 连一条容量为 + ∞的边。于是得到了一个新的容量网络 G’。

  (2)对于容量网络 G’,计算从超级源点 s 到超级汇点 t 的最大流 f m a x f_{max}f max,即为多源多汇容量网络 G 的最大流。

  使用 NetworkX 工具包,也有另一种特殊方法可以解决多源多汇最大流问题。

  NetworkX 中求解费用最小流问题的函数 min_cost_flow()、min_cost_flow_cost() 是求解费用最小流问题的函数,并不是设定供应点、需求点,而是通过设置顶点属性 ‘demand’ 确定供应点、需求点及各顶点的净流量,因而允许网络中存在多个供应点、需求点。

min_cost_flow(G, demand=‘demand’, capacity=‘capacity’, weight=‘weight’)

min_cost_flow_cost(G, demand=‘demand’, capacity=‘capacity’, weight=‘weight’)

  因此,对于指定流量的多源多汇网络可行流问题,只要在每个源点、汇点的顶点属性 ‘demand’ 设置对应的供应量、需求量,再按最小费用流即可。这种方法不仅得到了流量 v 的可行流,而且是最小费用流。类似地,对于多源多汇网络的最大流问题,可以参考3.4 案例:运输费用”的方法,v 从 1 逐渐递增,计算所有流量下的最小费用流,直到达到网络容量的极限,即得到容量网络的最大费用流。该方法不需要构造新的容量网络,编程实现简单,而且可以获得最大流量的最小费用流,但计算量很大。

 

5.3 带顶点容量约束的最大流

  标准形式的网络流优化问题,只有边的容量约束,没有顶点的容量约束。在实际问题中,顶点所表示的工厂、仓库、销售点,都存在最大储存量,因而带有顶点的容量约束。

  对于顶点容量约束的网络流问题,可以通过如下图所示的将每个带约束的顶点 N 分拆为两个顶点(入顶点 Nin 和出顶点 Nout)的方法处理:

  (1)将指向顶点 N 的边,改为指向入顶点 Nin;

  (2)将顶点 N 所指向的边,改为出顶点 Nout 所指向的边;

  (3)增加一条从入顶点 Nin 指向出顶点 Nout 的边,边的容量为顶点 N 的容量,单位费用为 0。

  于是得到了一个新的容量网络 G’。由此,原有网络 G 的顶点 N 的容量限制,转化为网络 G’ 的边 (Nin,Nout) 的容量限制。带顶点约束的容量网络 G 的最大流问题,转化为新的标准形式的容量网络 G’ 的最大流问题。

 

6. 最小费用流问题的应用与推广

6.1 运输问题

  有出发地(供应点、供应量)和目的地(需求点、需求量),没有转运点,没有路段(边)的容量限制,目标是总运输成本最小或总利润最大。

6.2 指派问题

  出发地(供应点、供应量=1)是人,目的地(需求点、需求量=1)是任务,没有转运点,没有路段(边)的容量限制,目标是总指派成本最小或总利润最大。

6.3 转运问题

有出发地(供应点、供应量)和目的地(需求点、需求量),有转运点,没有路段(边)的容量限制(或带有容量限制),目标是总流量费用最小或总利润最大。

6.4 最大流问题

  最大流问题是特殊的最小费用流问题:有供应点、需求点、转运点,有路段(边)的容量限制,没有供应量和需求量的限制,目标是通过网络到目的地的总流量最大。

6.5 最短路问题

  有供应点(供应量=1)、需求点(需求量=1)、转运点,没有路段(边)的容量限制,目标是通过网络到目的地的总距离最短。

 

7. 案例:多源多汇的物流转运问题

问题描述

  如图所示,某公司的工厂(供应点)位于F1、F2,仓库(中转点)位于W1、W2,零售商(需求点)位于R1、R2、R3、R4。从工厂生产的产品先送到仓库,再由仓库发到零售商。图中给出了各供应点和需求点的流量、每条发货线路的最大流量、单位流量的运输成本,要求在产销平衡的条件下,找出总运输费用最小的运输方案。

问题分析

  这是一个运输路径规划问题,也是一个多源点、多汇点的最小费用流问题。

  对于多源多汇的最小费用流问题,构造一个费用网络 G,网络 G 的各条边没有容量限制,单位流量的运输成本为边的权值 w。F1、F2 是网络 G 的源点(供应点),R1~R4 是汇点(需求点),W1、W2 是中间节点。F1、F2 的供应量与 R1~R4 的需求量是平衡的。因此可以用 NetworkX 的 maximum_flow() 函数求出最小费用流。


  该问题也是一个典型的线性规划问题,可以用线性规划方法求解。

  式中:f(i,j) 表示路段 (i,j) 的流量,w(i,j) 表示路段 (i,j) 的单位流量运输成本,S 表示源点(供应点),T 表示汇点(需求点)。

  对于上式描述的线性规划问题,可以用 PuLP工具包求解(参见《Python小白的数学建模课-03.线性规划》),编程步骤如下:

  (1) 导入 PuLP库函数;

  (2) 定义一个线性规划问题;

  (3)定义决策变量,决策变量的上下限:

      f11:F1-W1 流量, 0<=f11<=600

      f12:F1-W2 流量, 0<=f12<=600

      f21:F2-W1 流量, 0<=f21<=400

      f22:F2-W2 流量, 0<=f22<=400

      w11:W1-R1 流量, 0<=w11<=200

      w12:W1-R2 流量, 0<=w12<=150

      w13:W1-R3 流量, 0<=w13<=350

      w14:W1-R4 流量, 0<=w14<=300

      w21:W2-R1 流量, 0<=w21<=200

      w22:W2-R2 流量, 0<=w22<=150

      w23:W2-R3 流量, 0<=w23<=350

      w24:W2-R4 流量, 0<=w24<=300

  (4)定义目标函数:

min cost = 2*f11 + 3*f12 + 3*f21 +   f22 + 2*w11 + 6*w12

       + 3*w13 + 6*w14 + 4*w21 + 4*w22 + 6*w23 + 5*w24

  (5)定义约束条件:

      f11 + f12 <= 600

      f21 + f22 <= 400

      f11 + f21 = w11 + w12 + w13 + w14

      f12 + f22 = w21 + w22 + w23 + w24

      w11 + w21 = 200

      w12 + w22 = 150

      w13 + w23 = 350

      w14 + w24 = 300

注意,f11+f12<=600, f21+f22<=400,意味着允许存在产销不平衡的情况。

  (6)求解规划问题,输出优化结果

  本文给出了基于 NetworkX 工具包和基于 PuLP 工具包的两种方法的程序,以便理解图论中的最小费用流问题与线性规划问题的联系。

Python例程

import numpy as np
import matplotlib.pyplot as plt  # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包
import pulp      # 导入 pulp库

# # 4. 多源多汇最小费用流问题 (Capacity network with multi source and multi sink)
# # 4.1 费用网络
# 创建有向图
G1 = nx.DiGraph()  # 创建一个有向图 DiGraph
G1.add_edges_from([('F1','W1',{'capacity': 9e5, 'weight': 2}), # F1~F2:工厂
                  ('F1','W2',{'capacity': 9e5, 'weight': 3}),  # W1~W2:仓库
                  ('F2','W1',{'capacity': 9e5, 'weight': 3}),
                  ('F2','W2',{'capacity': 9e5, 'weight': 1}),
                  ('W1','R1',{'capacity': 9e5, 'weight': 2}),  # R1~R4:零售店
                  ('W1','R2',{'capacity': 9e5, 'weight': 6}),
                  ('W1','R3',{'capacity': 9e5, 'weight': 3}),
                  ('W1','R4',{'capacity': 9e5, 'weight': 6}),
                  ('W2','R1',{'capacity': 9e5, 'weight': 4}),
                  ('W2','R2',{'capacity': 9e5, 'weight': 4}),
                  ('W2','R3',{'capacity': 9e5, 'weight': 6}),
                  ('W2','R4',{'capacity': 9e5, 'weight': 5})]) # 添加边的属性 'capacity', 'weight'
G1.add_node("F1", demand=-600)  # nx.min_cost_flow() 的设置要求
G1.add_node("F2", demand=-400)  # 设置源点的流量,负值表示净流出
G1.add_node("R1", demand=200)  # 设置汇点的流量,正值表示净流入
G1.add_node("R2", demand=150)
G1.add_node("R3", demand=350)
G1.add_node("R4", demand=300)
pos={'F1':(2,6.5),'F2':(2,3.5),'W1':(5,6.5),'W2':(5,3.5),'R1':(9,8),'R2':(9,6),'R3':(9,4),'R4':(9,2)}  # 指定顶点绘图位置

# # 4.2 用 NetworkX 求最小费用流
# 求最小费用流(demand=v)
minFlowCost = nx.min_cost_flow_cost(G1)  # 求最小费用流的费用
minFlowDict = nx.min_cost_flow(G1)  # 求最小费用流
# minFlowCost, minFlowDict = nx.network_simplex(G2)  # 求最小费用流--与上行等效

# 整理边的标签,用于绘图显示
# 数据格式转换
edgeCapacity = nx.get_edge_attributes(G1, 'weight')
edgeLabel = {}  # 边的标签
for i in edgeCapacity.keys():  # 整理边的标签,用于绘图显示
    edgeLabel[i] = f'w={edgeCapacity[i]:}'  # 边的容量
edgeLists = []
for i in minFlowDict.keys():
    for j in minFlowDict[i].keys():
        edgeLabel[(i,j)] += ',f=' + str(minFlowDict[i][j])  # 取出每条边流量信息存入边显示值
        if minFlowDict[i][j] > 0:
            edgeLists.append((i, j))

print("1. NetworkX 网络与图(最小费用流优化结果):")  # NetworkX 工具包
print("最小费用:{}".format(minFlowCost))  # 输出最小费用的值
print("最小费用流的路径及流量: ", minFlowDict)  # 输出最小费用流的途径和各路径上的流量
print("最小费用流的路径:", edgeLists)  # 输出最小费用流的途径

# 绘制有向网络图
fig, ax = plt.subplots(figsize=(8,6))
ax.text(1.2,6.4,"600",color='navy')
ax.text(1.2,3.4,"400",color='navy')
ax.text(9.3,7.9,"200",color='navy')
ax.text(9.3,5.9,"150",color='navy')
ax.text(9.3,3.9,"350",color='navy')
ax.text(9.3,1.9,"300",color='navy')
ax.set_title("Capacity network with multi source and multi sink")
nx.draw(G1,pos,with_labels=True,node_color='skyblue',node_size=300,font_size=10)   # 绘制有向图,显示顶点标签
edgeLabel1 = nx.get_edge_attributes(G1, 'weight')
nx.draw_networkx_nodes(G1, pos, nodelist=['F1','F2'], node_color='orange')  # 设置指定顶点的颜色、宽度
nx.draw_networkx_nodes(G1, pos, nodelist=['W1','W2'], node_color='c')  # 设置指定顶点的颜色、宽度
nx.draw_networkx_edge_labels(G1,pos,edgeLabel,font_size=10,label_pos=0.25)  # 显示边的标签:'capacity','weight' + minCostFlow
nx.draw_networkx_edges(G1,pos,edgelist=edgeLists,edge_color='m',width=2)  # 设置指定边的颜色、宽度
plt.xlim(0.5, 10.5)
plt.ylim(1, 9)
plt.axis('on')
# plt.show()

# # 4.3 用线性规划求最小费用流
# (1) 建立优化问题 TransportCost: 求最小值(LpMinimize)
transportLP = pulp.LpProblem("TransportCostProblem", sense=pulp.LpMinimize)  # 定义问题,求最小值
# (2) 定义决策变量 f11~f22, w11~w24
f11 = pulp.LpVariable('F1-W1', lowBound=0, upBound=600, cat='Continuous')  # 定义 f11
f12 = pulp.LpVariable('F1-W2', lowBound=0, upBound=600, cat='Continuous')  # 定义 f12
f21 = pulp.LpVariable('F2-W1', lowBound=0, upBound=400, cat='Continuous')  # 定义 f21
f22 = pulp.LpVariable('F2-W2', lowBound=0, upBound=400, cat='Continuous')  # 定义 f22
w11 = pulp.LpVariable('W1-R1', lowBound=0, upBound=200, cat='Continuous')  # 定义 w11
w21 = pulp.LpVariable('W2-R1', lowBound=0, upBound=200, cat='Continuous')  # 定义 w21
w12 = pulp.LpVariable('W1-R2', lowBound=0, upBound=150, cat='Continuous')  # 定义 w12
w22 = pulp.LpVariable('W2-R2', lowBound=0, upBound=150, cat='Continuous')  # 定义 w22
w13 = pulp.LpVariable('W1-R3', lowBound=0, upBound=350, cat='Continuous')  # 定义 w13
w23 = pulp.LpVariable('W2-R3', lowBound=0, upBound=350, cat='Continuous')  # 定义 w23
w14 = pulp.LpVariable('W1-R4', lowBound=0, upBound=300, cat='Continuous')  # 定义 w14
w24 = pulp.LpVariable('W2-R4', lowBound=0, upBound=300, cat='Continuous')  # 定义 w24
# (3) 定义目标函数 cost
transportLP += (2*f11 + 3*f12 + 3*f21 +   f22 + 2*w11 + 6*w12
             +  3*w13 + 6*w14 + 4*w21 + 4*w22 + 6*w23 + 5*w24)  # 运输费用
# (4) 设置约束条件
transportLP += (f11 + f12 <= 600)  # 等式约束
transportLP += (f21 + f22 <= 400)  # 等式约束
transportLP += (w11 + w21 == 200)  # 等式约束
transportLP += (w12 + w22 == 150)  # 等式约束
transportLP += (w13 + w23 == 350)  # 等式约束
transportLP += (w14 + w24 == 300)  # 等式约束
transportLP += (f11 +f21 == w11 + w12 + w13 + w14)  # 等式约束
transportLP += (f12 +f22 == w21 + w22 + w23 + w24)  # 等式约束
# (5) 求解线性规划问题
transportLP.solve()
# (6) 输出优化结果
print("2. PuLP 线性规划(最小费用的优化结果):")  # PuLP 工具包
print(transportLP)  # 输出问题设定参数和条件
print("Optimization result")  # PuLP 工具包
for v in transportLP.variables():
    print(v.name, " = ", v.varValue)  # 输出每个变量的最优值
print("\n最小运输费用 = ", pulp.value(transportLP.objective))  # 输出最优解的目标函数值

plt.show()
多源多汇的物流转运问题
 

便于理解的运行结果图:

8. 案例:多商品流问题

  多商品流问题(Multi-Commodity Flow Prolem)是多种商品(物流、人流、数据流等)在具有容量限制的网络中从供应点流到需求点的网络流问题,通常是 NP 问题。多商品流问题的优化目标是以最小的成本实现商品在网络中的流通,约束条件主要是每条边的容量。

  多商品流路径优化问题具有广泛的实际应用,如交通运输、物流网络、电信网络以及产销系统。一些实际问题带有特殊要求,如在电信网络中要考虑时间延迟和可靠性问题,在零担货运中需要考虑配货拼车,有时需要综合考虑运输的时间和成本。

问题描述

  将城市 v0 生产的商品 A、B 通过铁路网分别运送到 城市 v6、v5,商品 A、B 的需求量分别为 6.0、5.0 单位。铁路网的结构如图所示,每条线路(边)上的数值表示该线路的容量和单位运费。求完成商品运输任务的最小费用的运输方案。

问题分析

  这是一个多商品流问题,具有 1 个供应点 V0 和 2个需求点 V5、V6。

  对于多源多汇的多商品流最小费用流问题,构造一个费用网络 G,网络 G 的各条边具有容量限制,单位流量的运输成本为边的权值 w。源点(供应点)为流量净输出,汇点(需求点)为流量净输入;中间节点不带存储,每种商品的净流量都为 0。所有线路的商品总流量不超过边的容量限制。


  该问题也是一个典型的线性规划问题,可以用如下模型描述:

  式中:M 表示商品品种,S 表示源点(供应点),T 表示汇点(需求点),w(i,j) 表示路段 (i,j) 的单位流量运输成本,f(i,j,m) 表示商品 m 在路段 (i,j) 的流量,

  NetworkX 工具包中没有多商品流问题的解决方法,在 Python 其它图与网络工具包也没有找到多商品流问题的函数。网络流问题本质上是线性规划问题,对于上式描述的线性规划问题,可以用 PuLP工具包求解(参见线性规划)。

  多商品流的目标是各种商品从供应点运送到需求点的总费用最小。定义各商品 m 在各路段 (i,j) 的流量 f(i,j,m) 为决策变量,则目标函数为商品 m 在各路段 (i,j) 的运输费用之和。多商品流的模型主要包含流守恒约束和容量限制。

  本案例的问题中,不同商品 A、B 是从同一供应点发出,运送到不同需求点。但本案例的数学模型和例程,并不限定从不同商品从同一供应点发出,也就是说可以适用于多源多汇的多商品流问题。另外,如果如果不同商品在各条边的运费单价不同,只要相应修改例程的目标函数即可处理。

程序说明

  本程序并不复杂,但有些地方的处理可能会令小白感到困惑,详细说明如下:

  图的输入:稀疏有向图,使用 nx.DiGraph() 定义有向图,使用G.add_weighted_edges_from() 函数以列表向图中添加多条赋权边,每个赋权边以元组 (node1,node2,{‘capacity’:c1, ‘weight’:w1}) 定义属性 ‘capacity’ 和 ‘weight’。注意必须以关键字 ‘capacity’ 表示容量,以 ‘weight’ 表示单位流量的成本。

  supplyA、supplyB 表示商品 A、B 的供应量,来自题目要求。注意网络的最大流量必须大于等于各种商品的总供应量或总需求量,否则将没有可行解。

  绘制网络图,以边的标签显示边的容量 ‘capacity’ 和单位流量的成本 ‘weight’ 。

  用 PuLP 求解多商品流线性规划问题:

  (1) 定义一个线性规划问题 MCFproblem。

  (2)定义决策变量 fA、fB ,并设置决策变量类型和上下限:

  ·fA、fB 都是一维数组,表示商品 A、B 在网络 G2 各条边的实际流量。根据题意将决策变量设为连续变量,如果问题要求整车配货等条件,可以相应地设为整数变量。

  ·fA、fB 的下限为 0,上限应为各条边的容量。但由于 pulp.LpVariable.dicts 函数中定义上限 upBound 为实数类型,不能是数组,无法对每个决策变量设定不同的上限值。

  ·因此,在定义决策变量 fA、fB 时,将上限设为所有边的容量的最大值 maxCapacity。每条边的具体容量约束,则作为约束条件处理。

  ·G2.edges() 表示遍历网络 G2 的边,因此 fA、fB 是 A、B 在所有边 edges 的流量:

{('v0', 'v1'): FlowA_('v0',_'v1'), ('v0', 'v2'): FlowA_('v0',_'v2'), ('v1', 'v3'): FlowA_('v1',_'v3'), ('v2', 'v1'): FlowA_('v2',_'v1'), ('v2', 'v4'): FlowA_('v2',_'v4'), ('v3', 'v4'): FlowA_('v3',_'v4'), ('v3', 'v5'): FlowA_('v3',_'v5'), ('v3', 'v6'): FlowA_('v3',_'v6'), ('v4', 'v1'): FlowA_('v4',_'v1'), ('v4', 'v5'): FlowA_('v4',_'v5'), ('v4', 'v6'): FlowA_('v4',_'v6')}

{('v0', 'v1'): FlowB_('v0',_'v1'), ('v0', 'v2'): FlowB_('v0',_'v2'), ('v1', 'v3'): FlowB_('v1',_'v3'), ('v2', 'v1'): FlowB_('v2',_'v1'), ('v2', 'v4'): FlowB_('v2',_'v4'), ('v3', 'v4'): FlowB_('v3',_'v4'), ('v3', 'v5'): FlowB_('v3',_'v5'), ('v3', 'v6'): FlowB_('v3',_'v6'), ('v4', 'v1'): FlowB_('v4',_'v1'), ('v4', 'v5'): FlowB_('v4',_'v5'), ('v4', 'v6'): FlowB_('v4',_'v6')}

(3)定义目标函数:

目标函数是 A、B 在所有边 edges 的总运费,即流量 fA、fB 与单位流量成本 ‘weight’的乘积的总和。

需要说明的是:(1)通过 edgeWeight = nx.get_edge_attributes(G2, ‘weight’) 将所有边的属性 ‘weight’ 转换为字典类型,就可以用 for edge in G2.edges 遍历操作。(2)如果不同商品在各条边的运费单价不同,只要将目标函数中的 KaTeX parse error: Undefined control sequence: \* at position 8: w(edge)\̲*̲ [fA(edge)+fB(e… 修改为 KaTeX parse error: Undefined control sequence: \* at position 9: wA(edge)\̲*̲fA(edge)+wB(edg… 即可。

# (3). 设置目标函数

MCFproblem += pulp.lpSum([edgeWeight[edge] * (fA[edge]+fB[edge]) for edge in G2.edges])  # 总运输费用

  (4)定义约束条件:

  ·多商品流的模型主要包含边的容量约束和顶点的流守恒约束和两个方面。

  ·边的容量约束,即每条边上各种商品流量之和不大于边的容量。

    ·顶点的流守恒约束,分为源点(供应点)、汇点(需求点)和中间节点三种情况:

通过 for node in G2.nodes 可以遍历网络的所有顶点。

    · in_degree(node) 计算顶点 node 的入度,入度为0的顶点是源点;out_degree(node) 计算顶点 node 的出度,出度为0的顶点是汇点。由此可以判断网络的源点、汇点和中间节点;也可以根据题目直接判断各中商品的供应点、需求点进行定义。

    ·in_edges(nbunch=node) 返回顶点 node 的所有入边,out_edges(nbunch=node) 返回顶点 node 的所有出边。

    ·源点:对于每种商品,所有输出边的流量之和不大于该顶点该商品的供应量。对于供应量、需求量相等的问题,也可以将该约束条件设为输出流量等于供应量。

    ·汇点:对于每种商品,所有输入边的流量之和等于该顶点该商品的供应量。案例问题有多个需求点,并对应了不同品种的商品,因此需要分别进行设置。

    ·中间节点:对于每种商品,中间节点的总流入量与总流出量相等。

    ·注意,如果供应点、需求点也是流通线路而不是源点、汇点,则不必分作三种情况,都按照中间节点处理,但对每个节点需要设置每种商品的净流量。

源点:v0, 出边:[('v0', 'v1'), ('v0', 'v2')]

中间点:v1, 入边:[('v0', 'v1'), ('v2', 'v1'), ('v4', 'v1')], 出边:[('v1', 'v3')]

中间点:v2, 入边:[('v0', 'v2')], 出边:[('v2', 'v1'), ('v2', 'v4')]

中间点:v3, 入边:[('v1', 'v3')], 出边:[('v3', 'v4'), ('v3', 'v5'), ('v3', 'v6')]

中间点:v4, 入边:[('v2', 'v4'), ('v3', 'v4')], 出边:[('v4', 'v1'), ('v4', 'v5'), ('v4', 'v6')]

汇点:v5, 入边:[('v3', 'v5'), ('v4', 'v5')]

汇点:v6, 入边:[('v3', 'v6'), ('v4', 'v6')]

  (6)求解规划问题,输出优化结果

Python例程

import numpy as np
import matplotlib.pyplot as plt  # 导入 Matplotlib 工具包
import networkx as nx  # 导入 NetworkX 工具包
import pulp      # 导入 pulp库

# 5. 多商品流问题 (Multi-commodity Flow Problem)
# 5.1 创建有向图
G2 = nx.DiGraph()  # 创建一个有向图 DiGraph
G2.add_edges_from([('v0','v1',{'capacity': 7, 'weight': 4}),
                  ('v0','v2',{'capacity': 8, 'weight': 4}),
                  ('v1','v3',{'capacity': 9, 'weight': 1}),
                  ('v2','v1',{'capacity': 5, 'weight': 5}),
                  ('v2','v4',{'capacity': 9, 'weight': 4}),
                  ('v3','v4',{'capacity': 6, 'weight': 2}),
                  ('v3','v5',{'capacity': 10, 'weight': 6}),
                  ('v3','v6',{'capacity': 10, 'weight': 6}),
                  ('v4','v1',{'capacity': 2, 'weight': 1}),
                  ('v4','v5',{'capacity': 5, 'weight': 2}),
                  ('v4','v6',{'capacity': 5, 'weight': 2})]) # 添加边的属性 'capacity', 'weight'
pos={'v0':(0,5),'v1':(4,2),'v2':(4,8),'v3':(10,2),'v4':(10,8),'v5':(15,3),'v6':(15,7)}  # 指定顶点绘图位置

supplyA = 6.0  # 商品 A 供应量
supplyB = 5.0  # 商品 B 供应量
print("Supply A:{}\tSupply B:{}".format(supplyA,supplyB))

# 整理边的标签
edgeLabel1 = nx.get_edge_attributes(G2, 'capacity')
edgeLabel2 = nx.get_edge_attributes(G2, 'weight')
edgeLabel = {}
for i in edgeLabel1.keys():
    edgeLabel[i] = f'({edgeLabel1[i]:},{edgeLabel2[i]:})'  # 边的(容量,成本)

# 5.2 绘制有向网络图
fig, ax = plt.subplots(figsize=(8,6))
nx.draw(G2,pos,with_labels=True,node_color='skyblue',node_size=400,font_size=10)   # 绘制有向图,显示顶点标签
nx.draw_networkx_nodes(G2, pos, nodelist=['v0'], node_color='orange',node_size=400)  # 设置指定顶点的颜色、宽度
nx.draw_networkx_nodes(G2, pos, nodelist=['v5','v6'], node_color='c',node_size=400)  # 设置指定顶点的颜色、宽度
nx.draw_networkx_edge_labels(G2,pos,edgeLabel,font_size=10)  # 显示边的标签:'capacity','weight'
ax.set_title("Multi-commodity Flow Problem by youcans@xupt")
ax.text(-1.8,5.2,"A:{}".format(supplyA),color='m')
ax.text(-1.8,4.6,"B:{}".format(supplyB),color='navy')
ax.text(15.8,7.0,"A:{}".format(supplyA),color='m')
ax.text(15.8,2.8,"B:{}".format(supplyB),color='navy')
plt.xlim(-3, 18)
plt.ylim(1, 9)
plt.axis('on')
# plt.show(YouCans-XUPT)

# 5.3  用 PuLP 求解多商品流最小费用问题 (Multi-commodity Flow Problem YouCans-XUPT)
edgeWeight = nx.get_edge_attributes(G2, 'weight')  # 'weight', 单位流量的成本
edgeCapacity = nx.get_edge_attributes(G2, 'capacity')  # 'capacity', 边的容量
maxCapacity = max(edgeCapacity.values())  # 边的容量的最大值
print(edgeWeight)
print("max(Weight)",max(edgeWeight.values()))
print("max(Capacity)",max(edgeCapacity.values()))

# (1) 建立优化问题 MCFproblem: 求最小值(LpMinimize)
MCFproblem = pulp.LpProblem("MultiCommodityFlowProb", sense=pulp.LpMinimize)  # 定义问题,求最小值

# (2) 定义决策变量 fA(edges), fB(edges)
# itemsG2 = ["A","B"]  # 商品种类
fA = pulp.LpVariable.dicts("FlowA", G2.edges(), lowBound=0.0, upBound=maxCapacity, cat='Continuous')
fB = pulp.LpVariable.dicts("FlowB", G2.edges(), lowBound=0.0, upBound=maxCapacity, cat='Continuous')
print(fA)
print(fB)

# (3). 设置目标函数
MCFproblem += pulp.lpSum([edgeWeight[edge] * (fA[edge]+fB[edge]) for edge in G2.edges])  # 总运输费用

# (4) 设置约束条件
for edge in G2.edges:  # 边的最大流量约束
    MCFproblem += (fA[edge] + fB[edge] - edgeCapacity[edge] <= 0)  # edgeCapacity[edge], 边的容量

for node in G2.nodes:  # 顶点的净流量约束
    if G2.in_degree(node) == 0:  # 入度为 0,判断是否为源点
        print("源点:{}, 出边:{}".format(node,G2.out_edges(nbunch=node)))
        MCFproblem += (sum(fA[edge] for edge in G2.out_edges(nbunch=node)) <= supplyA)  # A 供应量约束
        MCFproblem += (sum(fB[edge] for edge in G2.out_edges(nbunch=node)) <= supplyB)  # B 供应量约束
    elif G2.out_degree(node) == 0:  # 出度为 0,判断是否为汇点
        print("汇点:{}, 入边:{}".format(node,G2.in_edges(nbunch=node)))
        if node=='v6':  # 题目条件, v6 需求为 B
            MCFproblem += (sum(fA[edge] for edge in G2.in_edges(nbunch=node)) == supplyA)  # A 需求量约束
        if node=='v5':  # 题目条件, v5 需求为 A
            MCFproblem += (sum(fB[edge] for edge in G2.in_edges(nbunch=node)) == supplyB)  # B 需求量约束
    else:  # 中间节点,每种商品都是流量平衡
        print("中间点:{}, 入边:{}, 出边:{}".format(node,G2.in_edges(nbunch=node),G2.out_edges(nbunch=node)))
        MCFproblem += (sum(fA[edge1] for edge1 in G2.out_edges(nbunch=node))
                       - sum(fA[edge2] for edge2 in G2.in_edges(nbunch=node)) == 0)  # 总流出 = 总流入
        MCFproblem += (sum(fB[edge1] for edge1 in G2.out_edges(nbunch=node))
                       - sum(fB[edge2] for edge2 in G2.in_edges(nbunch=node)) == 0)  # 总流出 = 总流入
# (5) 求解线性规划问题
MCFproblem.solve()

# (6) 输出优化结果
print("2. PuLP 线性规划(最小费用的优化结果):")  # PuLP 工具包
print(MCFproblem)  # 输出问题设定参数和条件
print("Optimization result")  # PuLP 工具包
for v in MCFproblem.variables():
    print(v.name, " = ", v.varValue)  # 输出每个变量的最优值
print("\n最小运输费用 = ", pulp.value(MCFproblem.objective))  # 输出最优解的目标函数值
plt.show()
多商品流问题

 

posted @ 2022-09-11 18:12  一眉师傅  阅读(1235)  评论(0编辑  收藏  举报