Python小白的數學建模課-19.網路流優化問題

youcans發表於2021-08-24

  • 流在生活中十分常見,例如交通系統中的人流、車流、物流,供水管網中的水流,金融系統中的現金流,網路中的資訊流。網路流優化問題是基本的網路優化問題,應用非常廣泛。
  • 網路流優化問題最重要的指標是邊的成本和容量限制,既要考慮成本最低,又要滿足容量限制,由此產生了網路最大流問題、最小費用流問題、最小費用最大流問題。
  • 本文基於 NetworkX 工具包,通過例程詳細介紹網路最大流問題、最小費用流問題、最小費用最大流問題的建模和程式設計。
  • 『Python小白的數學建模課 @ Youcans』帶你從數模小白成為國賽達人。


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),已知每條邊的容量和單位流量的費用,研究網路的流量最大的路徑中,費用最小的路徑。簡單地說,就是滿足最大流的路徑可能有多條,需要從其中找到成本最低的路徑。

Network 工具包求解網路流優化,包括最大流演算法、最小割演算法、最小費用流演算法、最小費用最大流演算法、容量縮放最小成本流演算法。



2. 網路最大流問題(MFP)

2.1 網路最大流演算法

網路最大流問題,是在容量網路 G(V,E) 中求流量 v(f) 達到最大的可行流 f。在最大流問題中,只能有一個源點和一個匯點。

求解網路最大流主要有增廣路法和預流推進法兩類方法。

增廣路方法從一條可行流開始,用 BFS 或 DFS 從源到匯找到一條增廣路,沿著該路徑修改流量,不斷重複這個過程,直到找不到增廣路時停止,此時的流就是最大流。增廣路方法有多種的實現演算法,如 Ford Fulkerson 標號法的演算法複雜度為 \(O(E f)\)(不穩定),Edmonds Karp 演算法的複雜度為 \(O(V E^2)\),Dinic 演算法的複雜度為 \(O(V^2 E)\),ISAP 演算法的複雜度也是 \(O(V^2 E)\),但其速度是最快的。

預流推進方法也稱壓入與重標記方法,演算法從源點開始向下推流,通過不斷地尋找活結點,將流量推向以該點為起點的可推流邊(壓入過程);如果在該點處找不到可推流邊,則將該點的高度加 1,以實現將過大的流向後推進(重標記過程)。最高標號預流推進(HLPP)演算法的複雜度為 \(O(V^2 E)\),改進的 HLPP 演算法的複雜度為 \(O(V^2 \sqrt{(E)})\)


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()maximum_flow_value() 是求解網路最大流問題的通用方法,整合了多種演算法可供選擇。官網介紹詳見:https://networkx.org/documentation/stable/reference/algorithms/flow.html

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 案例:輸油管網的最大流量

問題描述:

在輸油管網中,通過輸油管連線生產石油的油井、儲存石油的油庫和轉運的中間泵站。各站點之間的連線及管路的容量如圖(參見 2.6 程式執行結果圖)所示,求從油井到油庫的最大流量和具體方案。

問題分析:

這是一個網路最大流問題,可以用頂點表示油井、油庫和泵站,其中油井為源點 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。


2.5 Python 例程

# mathmodel19_v1.py
# Demo19 of mathematical modeling algorithm
# Demo of network flow problem optimization with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-15

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')  # Youcans@XUPT
plt.show()

2.6 程式執行結果

最大流值:  14
最大流的途徑及流量:  {'s': {'a': 6, 'c': 8}, 'a': {'b': 3, 'd': 3}, 'c': {'d': 4, 'f': 4}, 'b': {'t': 10}, 'd': {'e': 3, 'g': 4}, 't': {}, 'f': {'h': 4}, 'e': {'b': 7, 'j': 1}, 'g': {'e': 5}, 'j': {'t': 4}, 'h': {'g': 1, 'i': 3}, 'i': {'j': 3}}
最大流的路徑: [('s', 'a'), ('s', 'c'), ('a', 'b'), ('a', 'd'), ('c', 'd'), ('c', 'f'), ('b', 't'), ('d', 'e'), ('d', 'g'), ('f', 'h'), ('e', 'b'), ('e', 'j'), ('g', 'e'), ('j', 't'), ('h', 'g'), ('h', 'i'), ('i', 'j')]



3. 最小費用流問題(MCP)

3.1 最小費用流問題的演算法

在實際問題中,我們總是希望在完成運輸任務的同時,尋求運輸費用最低的方案。最小費用流問題,就是要以最小費用從出發點(供應點)將一定的流量輸送到接收點(需求點)。在最小流問題中,供應點、需求點的數量可以是一個或多個,但每個供應點的供應量和需求點的需求量是固定的。

最小費用流問題可以用如下的線性規劃問題描述

\[\begin{align*} & min\;Cost=\sum_{i=1}^m\sum_{j=1}^m w_{ij} f_{ij}\\ & s.t.:\;\begin{cases} \sum_{j=1}^m f_{ij} - \sum_{j=1}^m f_{ji} = v_i\\ \sum_{i=1}^m v_{i} = 0\\ 0 \leq f_{ij} \leq c_{ij} \; \end{cases} \end{align*} \]

求解最小費用流問題的方法很多,常見的如:連續最短路演算法(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)的邊以黑色繪製。

3.5 Python 例程:

# mathmodel19_v1.py
# Demo19 of mathematical modeling algorithm
# Demo of network flow problem optimization with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-15

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()

3.6 程式執行結果:

最短路徑:  ['s', 'v1', 'v3', 'v4', 't']
最短路徑長度:  9

最大流量: 14
最大流量的最小費用: 159

流量:  1	最小費用:9
流量:  2	最小費用:18
流量:  3	最小費用:27
流量:  4	最小費用:36
流量:  5	最小費用:45
流量:  6	最小費用:56
流量:  7	最小費用:67
流量:  8	最小費用:79
流量:  9	最小費用:91
流量: 10	最小費用:103
流量: 11	最小費用:115
流量: 12	最小費用:127
流量: 13	最小費用:143
流量: 14	最小費用:159
流量: 15	超出網路最大容量,沒有可行流。

流量 v=15:計算最小費用流失敗(no flow satisfies all node demands)。

最大流量: 14,	最小費用:159
最小費用流的路徑及流量:  {'s': {'v1': 7, 'v2': 7}, 'v1': {'v3': 9}, 'v2': {'v1': 2, 'v4': 5}, 'v3': {'v4': 0, 't': 9}, 'v4': {'v1': 0, 't': 5}, 't': {}}
最小費用流的路徑: [('s', 'v1'), ('s', 'v2'), ('v1', 'v3'), ('v2', 'v1'), ('v2', 'v4'), ('v3', 't'), ('v4', 't')]



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 中的每段管路允許的容量和單位流量的運輸費用如圖所示(參見4.5 程式執行結果圖),圖中邊上的引數 (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)的邊以黑色繪製。

4.4 Python 例程

# mathmodel19_v1.py
# Demo19 of mathematical modeling algorithm
# Demo of network flow problem optimization with NetworkX
# Copyright 2021 YouCans, XUPT
# Crated:2021-07-15

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(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') # Youcans@XUPT
plt.show()

4.5 執行結果

最小費用最大流的路徑及流量:  {'s': {'v1': 11, 'v2': 9}, 'v1': {'v3': 6, 'v4': 5}, 'v2': {'v1': 0, 'v3': 4, 'v5': 5}, 'v3': {'v4': 2, 'v5': 4, 't': 4}, 'v4': {'t': 7}, 'v5': {'t': 9}, 't': {}}
最小費用最大流的路徑: [('s', 'v1'), ('s', 'v2'), ('v1', 'v3'), ('v1', 'v4'), ('v2', 'v3'), ('v2', 'v5'), ('v3', 'v4'), ('v3', 'v5'), ('v3', 't'), ('v4', 't'), ('v5', 't')]
最大流量:  20
最小費用:  370



5. 總結

  1. 本文基於 NetworkX 工具包,通過例程詳細介紹了網路最大流問題、最小費用流問題、最小費用最大流問題的建模和程式設計。
  2. 運輸問題、指派問題、轉運問題、最大流問題、最短路徑問題,都是特殊情況下的最小費用流問題。通過 3.6 中最短路徑、最小費用最大流的結果與 v=1、v=14 的最小費用流結果的比較,可以理解這種關係。
  3. 例程給出了對部分指定的邊設定顏色,為邊設定指定格式的顯示內容,NetworkX 函式輸出值的資料格式轉換的程式設計方法,建議讀者多加留意。
  4. 網路流優化問題還有很多變形和衍生問題,將在今後的文中進行介紹。

【本節完】



版權宣告:

歡迎關注『Python小白的數學建模課 @ Youcans』 原創作品

原創作品,轉載必須標註原文連結:(https://www.cnblogs.com/youcans/category/1981091.html)。

Copyright 2021 Youcans, XUPT

Crated:2021-07-16


歡迎關注 『Python小白的數學建模課 @ Youcans』,每週更新數模筆記
Python小白的數學建模課-01.新手必讀
Python小白的數學建模課-02.資料匯入
Python小白的數學建模課-03.線性規劃
Python小白的數學建模課-04.整數規劃
Python小白的數學建模課-05.0-1規劃
Python小白的數學建模課-06.固定費用問題
Python小白的數學建模課-07.選址問題
Python小白的數學建模課-09.微分方程模型
Python小白的數學建模課-10.微分方程邊值問題
Python小白的數學建模課-12.非線性規劃
Python小白的數學建模課-15.圖論的基本概念
Python小白的數學建模課-16.最短路徑演算法
Python小白的數學建模課-17.條件最短路徑
Python小白的數學建模課-18.最小生成樹問題
Python小白的數學建模課-19.網路流優化問題
Python小白的數學建模課-B2.新冠疫情 SI模型
Python小白的數學建模課-B3.新冠疫情 SIS模型
Python小白的數學建模課-B4.新冠疫情 SIR模型
Python小白的數學建模課-B5.新冠疫情 SEIR模型
Python小白的數學建模課-B6.改進 SEIR疫情模型

相關文章