線性網路模型提供了一種有效的方法來解決複雜的物流和電力分配問題。從網路流的流量守恆定律出發,我們可以建構最大流模型,用於解決如何最大化網路中的流量。最小成本流模型則考慮了運輸成本,目標是在滿足供需的同時最小化總成本。文章以電力分配為例,說明如何使用最小成本流模型來決定發電廠到城市的電力輸送量,並考慮了供應和需求約束。此外,文章還介紹了轉運問題,這是一種更廣泛的網路流問題,允許貨物在中轉節點進行流通,並以 Python 程式碼展示瞭如何求解此類別問題。最後,文章探討了最短路徑問題,並使用線性規劃模型和 Python 程式碼實作了最短路徑演算法,展示瞭如何在網路中找到起點到終點的最短路徑。
4.1.2.2 網路流約束條件
在網路流問題中,唯一的約束型別是流量守恆定律。對於每一個既不是來源節點也不是目標節點的節點,流入的流量必須等於流出的流量,用數學公式表示為:
$$ \sum_{j \in N} x_{ij} = \sum_{j \in N} x_{ji}, \forall i \in N \setminus (S \cup T) $$
由於目標函式會促使流量從來源節點流出,或者等效地流入目標節點,因此流量守恆定律會確保物料從來源節點移動到目標節點。
4.1.2.3 可執行模型
讓我們將上述理論轉化為可執行的模型。為了使模型足夠通用以解決所有此型別的問題,我們假設輸入是一個二維陣列 C,該陣列由節點索引,包含了兩個節點之間弧的容量。此外,我們還假設有來源節點陣列 S 和目標節點陣列 T。
為了在目標函式的選擇上提供一些靈活性,並說明多個最佳解的出現,我們增加了一個最終引數 unique。如果該引數設為 True,模型將執行帶有目標函式 (4.2),該函式在最大化淨流量的同時,最小化流入來源節點的流量。如果設為 False,則僅僅最大化從來源節點流出的流量。程式碼如 清單 4-1 所示。
清單 4-1:最大流模型(maxflow.py)
def solve_model(C, S, T, unique=True):
s, n = newSolver('Maximum flow problem'), len(C)
x = [[s.NumVar(0, C[i][j], "") for j in range(n)] for i in range(n)]
B = sum(C[i][j] for i in range(n) for j in range(n))
Flowout, Flowin = s.NumVar(0, B, ""), s.NumVar(0, B, "")
for i in range(n):
if i not in S and i not in T:
s.Add(sum(x[i][j] for j in range(n)) == sum(x[j][i] for j in range(n)))
s.Add(Flowout == s.Sum(x[i][j] for i in S for j in range(n)))
s.Add(Flowin == s.Sum(x[j][i] for i in S for j in range(n)))
s.Maximize(Flowout - 2 * Flowin if unique else Flowout - Flowin)
rc = s.Solve()
return rc, SolVal(Flowout), SolVal(Flowin), SolVal(x)
程式碼解析:
- 第 3 行定義了一個二維變數
x,其中第一個索引表示來源,第二個索引表示目的地。 - 第 9 行確保了對於既不是來源也不是目標的節點,流量守恆。
- 第 12 行的目標函式計算總流量,並指出我們應該最大化這個量。
我們傳回從來源節點流出的總流量和流入來源節點的總流量。模型的輸出顯示在 表 4-1 和 表 4-2 中,分別對應於引數 unique 為 False 和 True 的情況。
有趣的是,所有解都是整數,但我們並沒有施加整數約束。這是由於兩個因素:首先,問題的結構保證瞭如果存在最優解,則存在整數最優解;其次,所有求解器的求解技術要麼只考慮整數解(單純形法求解器),要麼在傳回給呼叫者之前從分數解移動到整數解(內點法求解器)。
4.1.3 變體
一個有用的應用是建模分配問題。這類別問題有多種形式,其中一種是:假設我們有一定數量的作業員(可以是人、機器或桌面電腦的核心)和一定數量的待完成的工作(待處理的檔案、待製造的零件或待執行的程式)。我們構建一個網路,一個來源節點透過單位容量的弧與每個作業員相連。這些作業員透過弧與工作節點(目標節點)相連,但前提是他們能夠執行給定的工作。最大化流量將以最優的方式將作業員分配給工作,意味著最多的工作將被完成。我們透過檢視作業員和工作之間具有非零流量的弧來讀取分配結果。如 圖 4-4 所示。
4.2 最小成本流問題
還有另一類別問題,其解的整數性質得到保證:最小成本流問題(mincost)。這裡有一個典型的例子。Solar-1138 公司有一組清潔能源發電廠,為多個城市提供電力。每個發電廠都有最大容量,因此它可以供應有限數量的千瓦時(kW-h)。每個城市都有峰值需求,並且所有城市的峰值需求大致同時出現。因此,峰值需求的總和是發電廠需要滿足的數量。從發電廠到城市傳送一千瓦時的成本根據發電廠、城市、傳輸基礎設施和發電廠與城市之間的距離而有所不同。這個成本是透過考慮電力生產和發電廠及輸電線路的維護得出的。
圖 4-5:將作業員分配給工作
此圖展示瞭如何將作業員分配給不同的工作,每個作業員根據其能力被分配到特定的工作。
表 4-3:電力分配成本示例
| 發電廠/城市 | 城市 0 | 城市 1 | 城市 2 | 城市 3 | 城市 4 | 城市 5 | 城市 6 | 供應量 |
|---|---|---|---|---|---|---|---|---|
| 發電廠 0 | 23 | 19 | 25 | 14 | 22 | 551 | ||
| 發電廠 1 | 16 | 20 | 23 | 13 | 23 | 689 | ||
| 發電廠 2 | 22 | 18 | 11 | 20 | 13 | 24 | 634 | |
| 需求量 | 288 | 234 | 236 | 231 | 247 | 262 | 281 |
我們需要決策的問題是,從每個發電廠到每個城市應該傳送多少電力,以滿足峰值需求同時最小化成本?
建模最小成本流問題
我們需要決策的是從每個發電廠到每個客戶的電力供應量。可能一個發電廠會向沒有、部分或全部城市供電,我們需要一種方式來表示這一點。如 圖 4-5 所示,這是一個二部圖,其中頂部節點是發電廠,底部節點是城市,而弧代表輸電線路,並註明瞭沿著特定輸電線路傳輸電力的成本。
電力分配網路
@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333
title 建模最小成本流問題
rectangle "23" as node1
rectangle "19" as node2
rectangle "25" as node3
rectangle "14" as node4
rectangle "22" as node5
rectangle "16" as node6
rectangle "20" as node7
rectangle "13" as node8
rectangle "18" as node9
rectangle "11" as node10
rectangle "24" as node11
node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5
node5 --> node6
node6 --> node7
node7 --> node8
node8 --> node9
node9 --> node10
node10 --> node11
@enduml
此圖表示了發電廠與城市之間的電力分配網路,每條弧上的數字代表傳輸成本。
線性網路模型在電力分配問題的應用
線性網路模型可用於解決電力分配問題,確保電力的有效分配並最小化成本。本章節將探討如何建立一個線性規劃模型來最佳化電力分配,並進一步討論模型的變形及其應用。
決策變數的定義
在電力分配問題中,決策變數代表從發電廠到城市的電力輸送量。為了簡化問題,我們引入一個二維變數 $x_{i,j}$,其中第一維表示發電廠(來源),第二維表示城市(目的地)。例如,$x_{2,3} = 35$ 表示應該從第2個發電廠向第3個城市輸送35 kW-h的電力。
目標函式
目標是最小化電力輸送的總成本。為此,我們需要一個成本引數 $C_{i,j}$,與決策變數具有相同的索引。目標函式可以表示為:
$$ \min \sum_{i,j} C_{i,j} \cdot x_{i,j} $$
約束條件
約束條件分為兩類別:供應約束和需求約束。供應約束確保每個發電廠的輸出不超過其最大供應能力,表示為:
$$ \sum_{j} x_{i,j} \leq S_i, \quad \forall i \in P $$
需求約束則確保每個城市的需求得到滿足,表示為:
$$ \sum_{i} x_{i,j} = D_j, \quad \forall j \in C $$
內容解密:
- 供應約束:$\sum_{j} x_{i,j} \leq S_i$ 確保每個發電廠 $i$ 的總輸出不超過其供應能力 $S_i$。
- 需求約束:$\sum_{i} x_{i,j} = D_j$ 保證每個城市 $j$ 的需求 $D_j$ 得到滿足。
可執行模型
將上述數學模型轉換為可執行的程式碼,需要將成本、需求和供應能力儲存在一個二維陣列中。以下是一個Python範例(摘自 mincost.py):
def solve_model(D):
s = newSolver('Mincost flow problem')
m, n = len(D) - 1, len(D[0]) - 1
B = sum([D[-1][j] for j in range(n)])
G = [[s.NumVar(0, B if D[i][j] else 0, '') for j in range(n)] for i in range(m)]
for i in range(m):
s.Add(D[i][-1] >= sum(G[i][j] for j in range(n)))
for j in range(n):
s.Add(D[-1][j] == sum(G[i][j] for i in range(m)))
Cost = s.Sum(G[i][j] * D[i][j] for i in range(m) for j in range(n))
s.Minimize(Cost)
rc = s.Solve()
return rc, ObjVal(s), SolVal(G)
內容解密:
- 變數定義:
G是一個二維變數,用於表示從發電廠到城市的電力輸送量。 - 供應約束:
s.Add(D[i][-1] >= sum(G[i][j] for j in range(n)))確保每個發電廠的輸出不超過其供應能力。 - 需求約束:
s.Add(D[-1][j] == sum(G[i][j] for i in range(m)))保證每個城市的需求得到滿足。 - 目標函式:
Cost = s.Sum(G[i][j] * D[i][j] for i in range(m) for j in range(n))計算總成本。
模型變形
該模型可以進一步擴充套件,例如加入弧容量限制或限制單一來源的供應比例。這些變形可以透過新增額外的約束條件來實作,例如:
$$ x_{i,j} \leq A_{i,j}, \quad \forall i \in P, j \in C $$
或者限制單一來源的供應比例:
$$ x_{i,j} \leq 0.6 \cdot D_j, \quad \forall i \in P, j \in C $$
這些變形可以增加模型的複雜度和現實適用性,但也可能需要額外的計算資源。
轉運問題(Transshipment Problem)解析
轉運問題是一種更為廣泛的網路流問題(Network Flow Problem),其特點在於節點(Node)之間存在運輸成本,部分節點為供應者,部分為消費者,而其他節點僅作為中轉站,既不生產也不消費貨物。
問題描述
考慮一個具有多個節點的網路,每個節點之間可能存在運輸成本。部分節點具有供應量(Supply),部分節點具有需求量(Demand),而其他節點既無供應也無需求,僅作為貨物流通的中轉節點。我們的目標是最小化總運輸成本。
數學模型
為瞭解決這個問題,我們引入一個二維變數 $x_{i,j}$,其中第一維表示貨物的來源節點 $i$,第二維表示貨物的目的節點 $j$,而變數的值則代表從 $i$ 到 $j$ 的運輸量。
目標函式
我們的目標是最小化總運輸成本,可以表示為:
$$ \min \sum_{i,j} C_{i,j} \cdot x_{i,j} $$
其中 $C_{i,j}$ 表示從節點 $i$ 到節點 $j$ 的單位運輸成本。
約束條件
對於每個節點,我們需要滿足流量守恆約束,即流入量減去流出量等於需求量減去供應量:
$$ \sum_{j} x_{j,i} - \sum_{j} x_{i,j} = D_i - S_i, \quad \forall i $$
其中 $D_i$ 和 $S_i$ 分別表示節點 $i$ 的需求量和供應量。
程式實作
以下是使用 Python 實作的轉運問題模型:
def solve_model(D):
s = newSolver('TransshipmentProblem')
n = len(D[0]) - 1
B = sum([D[-1][j] for j in range(n)])
G = [[s.NumVar(0, B if D[i][j] else 0, '') for j in range(n)] for i in range(n)]
for i in range(n):
s.Add(D[i][-1] - D[-1][i] == sum(G[i][j] for j in range(n)) - sum(G[j][i] for j in range(n)))
Cost = s.Sum(G[i][j] * D[i][j] for i in range(n) for j in range(n))
s.Minimize(Cost)
rc = s.Solve()
return rc, ObjVal(s), SolVal(G)
內容解密:
- 變數定義:
G是一個二維變數矩陣,其中G[i][j]表示從節點i到節點j的運輸量。 - 約束條件實作:對於每個節點
i,我們新增一個約束條件,確保流入量減去流出量等於需求量減去供應量。 - 目標函式實作:計算總運輸成本,並將其最小化。
- 求解模型:呼叫求解器求解該線性規劃問題,並傳回結果。
結果分析
執行該模型後,我們可以獲得最優的運輸方案。結果表明,即使我們沒有強制要求變數為整數,解仍然是整數解。這對於實際應用來說是非常有用的,因為它意味著我們可以直接根據模型的結果進行整數運輸決策。
線性網路模型:最短路徑問題
在前一節中,我們探討了轉運問題(Transshipment Problem),瞭解如何利用網路流模型解決供應鏈中的物流分配問題。本文將進一步探討另一個重要的網路最佳化問題:最短路徑問題(Shortest Path Problem)。這個問題在現實生活中有著廣泛的應用,例如Google Maps在計算兩地之間的最佳路徑時,就需要用到最短路徑演算法。
最短路徑問題的定義
最短路徑問題是指在一個由節點和邊組成的圖中,找出從起點到終點之間距離最短的路徑。這裡的「距離」可以是實際的物理距離,也可以是時間、成本等其他衡量指標。給定一個距離矩陣(Distance Matrix),我們需要決定一條從起點到終點的節點序列,使得這條路徑上的總距離最小。
距離矩陣範例
假設我們有一個如表4-8所示的距離矩陣,用於表示不同節點之間的距離。
建立最短路徑模型
要解決最短路徑問題,我們需要將其建模為一個網路流問題。具體步驟如下:
定義決策變數:對於圖中的每條邊,我們定義一個決策變數$x_{i,j}$,當$x_{i,j} = 1$時,表示這條邊被選入最短路徑中;當$x_{i,j} = 0$時,表示這條邊未被選中。
目標函式:我們的目標是最小化所選路徑的總距離。因此,目標函式可以表示為$\min \sum_{i,j} D_{i,j} \cdot x_{i,j}$,其中$D_{i,j}$是節點$i$到節點$j$的距離。
約束條件:
- 流量守恆約束:除了起點和終點外,其他所有節點的流入量必須等於流出量。這確保了路徑的連續性。
- 起點和終點約束:起點的流出量為1,終點的流入量為1。
程式碼實作
def solve_model(D, Start=None, End=None):
s, n = newSolver('Shortest Path Problem'), len(D)
if Start is None:
Start, End = 0, len(D) - 1
G = [[s.NumVar(0, 1 if D[i][j] else 0, '') for j in range(n)] for i in range(n)]
for i in range(n):
if i == Start:
s.Add(1 == sum(G[Start][j] for j in range(n)))
s.Add(0 == sum(G[j][Start] for j in range(n)))
elif i == End:
s.Add(1 == sum(G[j][End] for j in range(n)))
s.Add(0 == sum(G[End][j] for j in range(n)))
else:
s.Add(sum(G[i][j] for j in range(n)) == sum(G[j][i] for j in range(n)))
s.Minimize(s.Sum(G[i][j] * (0 if D[i][j] is None else D[i][j]) for i in range(n) for j in range(n)))
rc = s.Solve()
Path, Cost, Cumul, node = [Start], [0], [0], Start
while rc == 0 and node != End and len(Path) < n:
next_node = [i for i in range(n) if SolVal(G[node][i]) == 1][0]
Path.append(next_node)
# ... 後續處理
內容解密:
newSolver('Shortest Path Problem'):建立一個新的求解器例項,用於解決最短路徑問題。G = [[s.NumVar(0, 1 if D[i][j] else 0, '') for j in range(n)] for i in range(n)]:為每條可能的邊建立一個決策變數$x_{i,j}$,其取值範圍根據距離矩陣$D$決定。s.Add(1 == sum(G[Start][j] for j in range(n))):約束起點的流出量為1。s.Minimize(s.Sum(G[i][j] * (0 if D[i][j] is None else D[i][j]) for i in range(n) for j in range(n))):定義目標函式,最小化所選路徑的總距離。while迴圈:根據求解結果,逐步構建最短路徑。