萬字教你如何用 Python 實現線性規劃

華為雲開發者社群發表於2021-12-17
摘要:線性規劃是一組數學和計算工具,可讓您找到該系統的特定解,該解對應於某些其他線性函式的最大值或最小值。

本文分享自華為雲社群《實踐線性規劃:使用 Python 進行優化》,作者: Yuchuan。

線性規劃說明

什麼是線性規劃?

想象一下,您有一個線性方程組和不等式系統。這樣的系統通常有許多可能的解決方案。線性規劃是一組數學和計算工具,可讓您找到該系統的特定解,該解對應於某些其他線性函式的最大值或最小值。

什麼是混合整數線性規劃?

混合整數線性規劃線性規劃的擴充套件。它處理至少一個變數採用離散整數而不是連續值的問題。儘管乍一看混合整數問題與連續變數問題相似,但它們在靈活性和精度方面具有顯著優勢。

整數變數對於正確表示自然用整數表示的數量很重要,例如生產的飛機數量或服務的客戶數量。

一種特別重要的整數變數是二進位制變數。它只能取的值,在做出是或否的決定時很有用,例如是否應該建造工廠或者是否應該開啟或關閉機器。您還可以使用它們來模擬邏輯約束。

為什麼線性規劃很重要?

線性規劃是一種基本的優化技術,已在科學和數學密集型領域使用了數十年。它精確、相對快速,適用於一系列實際應用。

混合整數線性規劃允許您克服線性規劃的許多限制。您可以使用分段線性函式近似非線性函式、使用半連續變數、模型邏輯約束等。它是一種計算密集型工具,但計算機硬體和軟體的進步使其每天都更加適用。

通常,當人們試圖制定和解決優化問題時,第一個問題是他們是否可以應用線性規劃或混合整數線性規劃。

以下文章說明了線性規劃和混合整數線性規劃的一些用例:

  • Gurobi 優化案例研究
  • 線性規劃技術的五個應用領域

隨著計算機能力的增強、演算法的改進以及更多使用者友好的軟體解決方案的出現,線性規劃,尤其是混合整數線性規劃的重要性隨著時間的推移而增加。

使用 Python 進行線性規劃

解決線性規劃問題的基本方法稱為單純形法,它有多種變體。另一種流行的方法是內點法

混合整數線性規劃問題可以通過更復雜且計算量更大的方法來解決,例如分支定界法,它在幕後使用線性規劃。這種方法的一些變體是分支和切割方法,它涉及使用切割平面,以及分支和價格方法

有幾種適用於線性規劃和混合整數線性規劃的合適且眾所周知的 Python 工具。其中一些是開源的,而另一些是專有的。您是否需要免費或付費工具取決於問題的規模和複雜性,以及對速度和靈活性的需求。

值得一提的是,幾乎所有廣泛使用的線性規劃和混合整數線性規劃庫都是以 Fortran 或 C 或 C++ 原生和編寫的。這是因為線性規劃需要對(通常很大)矩陣進行計算密集型工作。此類庫稱為求解器。Python 工具只是求解器的包裝器。

Python 適合圍繞本機庫構建包裝器,因為它可以很好地與 C/C++ 配合使用。對於本教程,您不需要任何 C/C++(或 Fortran),但如果您想了解有關此酷功能的更多資訊,請檢視以下資源:

  • 構建 Python C 擴充套件模組
  • CPython 內部
  • 用 C 或 C++ 擴充套件 Python

基本上,當您定義和求解模型時,您使用 Python 函式或方法呼叫低階庫,該庫執行實際優化工作並將解決方案返回給您的 Python 物件。

幾個免費的 Python 庫專門用於與線性或混合整數線性規劃求解器互動:

  • SciPy Optimization and Root Finding
  • PuLP
  • Pyomo
  • CVXOPT

在本教程中,您將使用SciPy和PuLP來定義和解決線性規劃問題。

線性規劃示例

在本節中,您將看到線性規劃問題的兩個示例:

  1. 一個說明什麼是線性規劃的小問題
  2. 一個與資源分配相關的實際問題,它說明了現實世界場景中的線性規劃概念

您將在下一節中使用 Python 來解決這兩個問題。

小型線性規劃問題

考慮以下線性規劃問題:

萬字教你如何用 Python 實現線性規劃

你需要找到X和Ÿ使得紅色,藍色和黃色的不平等,以及不平等X ≥0和ÿ ≥0,是滿意的。同時,您的解決方案必須對應於z的最大可能值。

您需要找到的自變數(在本例中為xy)稱為決策變數。要最大化或最小化的決策變數的函式(在本例中為z)稱為目標函式成本函式或僅稱為目標。您需要滿足的不等式稱為不等式約束。您還可以在稱為等式約束的約束中使用方程。

這是您如何視覺化問題的方法:

萬字教你如何用 Python 實現線性規劃

紅線代表的功能2 X + Ý = 20,和它上面的紅色區域示出了紅色不等式不滿足。同樣,藍線是函式−4 x + 5 y = 10,藍色區域被禁止,因為它違反了藍色不等式。黃線是 − x + 2 y = −2,其下方的黃色區域是黃色不等式無效的地方。

如果您忽略紅色、藍色和黃色區域,則僅保留灰色區域。灰色區域的每個點都滿足所有約束,是問題的潛在解決方案。該區域稱為可行域,其點為可行解。在這種情況下,有無數可行的解決方案。

您想最大化z。對應於最大z的可行解是最優解。如果您嘗試最小化目標函式,那麼最佳解決方案將對應於其可行的最小值。

請注意,z是線性的。你可以把它想象成一個三維空間中的平面。這就是為什麼最優解必須在可行區域的頂點或角上的原因。在這種情況下,最佳解決方案是紅線和藍線相交的點,稍後您將看到。

有時,可行區域的整個邊緣,甚至整個區域,都可以對應相同的z值。在這種情況下,您有許多最佳解決方案。

您現在已準備好使用綠色顯示的附加等式約束來擴充套件問題:

萬字教你如何用 Python 實現線性規劃

方程式 − x + 5 y = 15,以綠色書寫,是新的。這是一個等式約束。您可以通過向上一張影像新增相應的綠線來將其視覺化:

萬字教你如何用 Python 實現線性規劃

現在的解決方案必須滿足綠色等式,因此可行區域不再是整個灰色區域。它是綠線從與藍線的交點到與紅線的交點穿過灰色區域的部分。後一點是解決方案。

如果插入x的所有值都必須是整數的要求,那麼就會得到一個混合整數線性規劃問題,可行解的集合又會發生變化:

萬字教你如何用 Python 實現線性規劃

您不再有綠線,只有沿線的x值為整數的點。可行解是灰色背景上的綠點,此時最優解離紅線最近。

這三個例子說明了可行的線性規劃問題,因為它們具有有界可行區域和有限解。

不可行的線性規劃問題

如果沒有解,線性規劃問題是不可行的。當沒有解決方案可以同時滿足所有約束時,通常會發生這種情況。

例如,考慮如果新增約束x + y ≤ −1會發生什麼。那麼至少有一個決策變數(x或y)必須是負數。這與給定的約束x ≥ 0 和y ≥ 0相沖突。這樣的系統沒有可行的解決方案,因此稱為不可行的。

另一個示例是新增與綠線平行的第二個等式約束。這兩行沒有共同點,因此不會有滿足這兩個約束的解決方案。

無界線性規劃問題

一個線性規劃問題是無界的,如果它的可行區域是無界,將溶液不是有限。這意味著您的變數中至少有一個不受約束,可以達到正無窮大或負無窮大,從而使目標也無限大。

例如,假設您採用上面的初始問題並刪除紅色和黃色約束。從問題中刪除約束稱為放鬆問題。在這種情況下,x和y不會在正側有界。您可以將它們增加到正無窮大,從而產生無限大的z值。

資源分配問題

在前面的部分中,您研究了一個與任何實際應用程式無關的抽象線性規劃問題。在本小節中,您將找到與製造業資源分配相關的更具體和實用的優化問題。

假設一家工廠生產四種不同的產品,第一種產品的日產量為x ₁,第二種產品的產量為x 2,依此類推。目標是確定每種產品的利潤最大化日產量,同時牢記以下條件:

  1. 第一種、第二種、第三種和第四種產品的每單位產品利潤分別為 20 美元、12 美元、40 美元和 25 美元。
  2. 由於人力限制,每天生產的總數量不能超過五十臺。
  3. 對於每單位第一個產品,消耗三個單位的原材料 A。每單位第二產品需要兩單位原料 A 和一單位原料 B。每單位第三產品需要一單位 A 和兩單位 B。最後,每單位第四產品需要三B 的單位
  4. 由於運輸和儲存的限制,工廠每天最多可以消耗一百單位的原材料 A 和九十單位的 B。

數學模型可以這樣定義:

萬字教你如何用 Python 實現線性規劃

目標函式(利潤)在條件 1 中定義。人力約束遵循條件 2。對原材料 A 和 B 的約束可以從條件 3 和條件 4 中通過對每種產品的原材料需求求和得出。

最後,產品數量不能為負,因此所有決策變數必須大於或等於零。

與前面的示例不同,您無法方便地將其視覺化,因為它有四個決策變數。但是,無論問題的維度如何,原理都是相同的。

線性規劃 Python 實現

在本教程中,您將使用兩個Python 包來解決上述線性規劃問題:

  1. SciPy是一個用於使用 Python 進行科學計算的通用包。
  2. PuLP是一個 Python 線性程式設計 API,用於定義問題和呼叫外部求解器。

SciPy 設定起來很簡單。安裝後,您將擁有開始所需的一切。它的子包scipy.optimize可用於線性和非線性優化。

PuLP 允許您選擇求解器並以更自然的方式表述問題。PuLP 使用的預設求解器是COIN-OR Branch and Cut Solver (CBC)。它連線到用於線性鬆弛的COIN-OR 線性規劃求解器 (CLP)和用於切割生成的COIN-OR 切割生成器庫 (CGL)。

另一個偉大的開源求解器是GNU 線性規劃工具包 (GLPK)。一些著名且非常強大的商業和專有解決方案是Gurobi、CPLEX和XPRESS。

除了在定義問題時提供靈活性和執行各種求解器的能力外,PuLP 使用起來不如 Pyomo 或 CVXOPT 等替代方案複雜,後者需要更多的時間和精力來掌握。

安裝 SciPy 和 PuLP

要學習本教程,您需要安裝 SciPy 和 PuLP。下面的示例使用 SciPy 1.4.1 版和 PuLP 2.1 版。

您可以使用pip以下方法安裝兩者:

$ python -m pip install -U "scipy==1.4.*" "pulp==2.1"

您可能需要執行pulptest或sudo pulptest啟用 PuLP 的預設求解器,尤其是在您使用 Linux 或 Mac 時:

$ pulptest

或者,您可以下載、安裝和使用 GLPK。它是免費和開源的,適用於 Windows、MacOS 和 Linux。在本教程的後面部分,您將看到如何將 GLPK(除了 CBC)與 PuLP 一起使用。

在 Windows 上,您可以下載檔案並執行安裝檔案。

在 MacOS 上,您可以使用 Homebrew:

$ brew install glpk

在 Debian 和 Ubuntu 上,使用apt來安裝glpk和glpk-utils:

$ sudo apt install glpk glpk-utils

在Fedora,使用dnf具有glpk-utils:

$ sudo dnf install glpk-utils

您可能還會發現conda對安裝 GLPK 很有用:

$ conda install -c conda-forge glpk

安裝完成後,可以檢視GLPK的版本:

$ glpsol --version

有關詳細資訊,請參閱 GLPK 關於使用Windows 可執行檔案和Linux 軟體包進行安裝的教程。

使用 SciPy

在本節中,您將學習如何使用 SciPy優化和求根庫進行線性規劃。

要使用 SciPy 定義和解決優化問題,您需要匯入scipy.optimize.linprog():

>>>
>>> from scipy.optimize import linprog

現在您已經linprog()匯入,您可以開始優化。

示例 1

讓我們首先解決上面的線性規劃問題:

萬字教你如何用 Python 實現線性規劃

linprog()僅解決最小化(而非最大化)問題,並且不允許具有大於或等於符號 (≥) 的不等式約束。要解決這些問題,您需要在開始優化之前修改您的問題:

  • 不是最大化z = x + 2 y,你可以最小化它的負值(− z = − x − 2 y)。
  • 代替大於或等於符號,您可以將黃色不等式乘以 -1 並得到小於或等於符號 (≤) 的相反數。

引入這些更改後,您將獲得一個新系統:

萬字教你如何用 Python 實現線性規劃

該系統與原始系統等效,並且將具有相同的解決方案。應用這些更改的唯一原因是克服 SciPy 與問題表述相關的侷限性。

下一步是定義輸入值:

>>>
>>> obj = [-1, -2]
>>> #      ─┬  ─┬
>>> #       │   └┤ Coefficient for y
>>> #       └────┤ Coefficient for x

>>> lhs_ineq = [[ 2,  1],  # Red constraint left side
...             [-4,  5],  # Blue constraint left side
...             [ 1, -2]]  # Yellow constraint left side

>>> rhs_ineq = [20,  # Red constraint right side
...             10,  # Blue constraint right side
...              2]  # Yellow constraint right side

>>> lhs_eq = [[-1, 5]]  # Green constraint left side
>>> rhs_eq = [15]       # Green constraint right side

您將上述系統中的值放入適當的列表、元組或NumPy 陣列中:

  • obj 儲存目標函式的係數。
  • lhs_ineq 儲存不等式(紅色、藍色和黃色)約束的左側係數。
  • rhs_ineq 儲存不等式(紅色、藍色和黃色)約束的右側係數。
  • lhs_eq 儲存來自等式(綠色)約束的左側係數。
  • rhs_eq 儲存來自等式(綠色)約束的右側係數。

注意:請注意行和列的順序!

約束左側和右側的行順序必須相同。每一行代表一個約束。

來自目標函式和約束左側的係數的順序必須匹配。每列對應一個決策變數。

下一步是以與係數相同的順序定義每個變數的界限。在這種情況下,它們都在零和正無窮大之間:

>>>
>>> bnd = [(0, float("inf")),  # Bounds of x
...        (0, float("inf"))]  # Bounds of y

此語句是多餘的,因為linprog()預設情況下采用這些邊界(零到正無窮大)。

注:相反的float("inf"),你可以使用math.inf,numpy.inf或scipy.inf。

最後,是時候優化和解決您感興趣的問題了。你可以這樣做linprog():

>>>
>>> opt = linprog(c=obj, A_ub=lhs_ineq, b_ub=rhs_ineq,
...               A_eq=lhs_eq, b_eq=rhs_eq, bounds=bnd,
...               method="revised simplex")
>>> opt
     con: array([0.])
     fun: -16.818181818181817
 message: 'Optimization terminated successfully.'
     nit: 3
   slack: array([ 0.        , 18.18181818,  3.36363636])
  status: 0
 success: True
       x: array([7.72727273, 4.54545455])

引數c是指來自目標函式的係數。A_ub和b_ub分別與不等式約束左邊和右邊的係數有關。同樣,A_eq並b_eq參考等式約束。您可以使用bounds提供決策變數的下限和上限。

您可以使用該引數method來定義要使用的線性規劃方法。有以下三種選擇:

  1. method="interior-point"選擇內點法。預設情況下設定此選項。
  2. method="revised simplex" 選擇修正的兩相單純形法。
  3. method="simplex" 選擇傳統的兩相單純形方法。

linprog() 返回具有以下屬性的資料結構:

  • .con 是等式約束殘差。
  • .fun 是最優的目標函式值(如果找到)。
  • .message 是解決方案的狀態。
  • .nit 是完成計算所需的迭代次數。
  • .slack 是鬆弛變數的值,或約束左右兩側的值之間的差異。
  • .status是一個介於0和之間的整數4,表示解決方案的狀態,例如0找到最佳解決方案的時間。
  • .success是一個布林值,顯示是否已找到最佳解決方案。
  • .x 是一個儲存決策變數最優值的 NumPy 陣列。

您可以分別訪問這些值:

>>>
>>> opt.fun
-16.818181818181817

>>> opt.success
True

>>> opt.x
array([7.72727273, 4.54545455])

這就是您獲得優化結果的方式。您還可以以圖形方式顯示它們:

萬字教你如何用 Python 實現線性規劃

如前所述,線性規劃問題的最優解位於可行區域的頂點。在這種情況下,可行區域只是藍線和紅線之間的綠線部分。最優解是代表綠線和紅線交點的綠色方塊。

如果要排除相等(綠色)約束,只需刪除引數A_eq並b_eq從linprog()呼叫中刪除:

>>>
>>> opt = linprog(c=obj, A_ub=lhs_ineq, b_ub=rhs_ineq, bounds=bnd,
...               method="revised simplex")
>>> opt
     con: array([], dtype=float64)
     fun: -20.714285714285715
 message: 'Optimization terminated successfully.'
     nit: 2
   slack: array([0.        , 0.        , 9.85714286])
  status: 0
 success: True
       x: array([6.42857143, 7.14285714]))

解決方案與前一種情況不同。你可以在圖表上看到:

萬字教你如何用 Python 實現線性規劃

在這個例子中,最優解是紅色和藍色約束相交的可行(灰色)區域的紫色頂點。其他頂點,如黃色頂點,具有更高的目標函式值。

示例 2

您可以使用 SciPy 來解決前面部分所述的資源分配問題:

萬字教你如何用 Python 實現線性規劃

和前面的例子一樣,你需要從上面的問題中提取必要的向量和矩陣,將它們作為引數傳遞給.linprog(),然後得到結果:

>>>
>>> obj = [-20, -12, -40, -25]

>>> lhs_ineq = [[1, 1, 1, 1],  # Manpower
...             [3, 2, 1, 0],  # Material A
...             [0, 1, 2, 3]]  # Material B

>>> rhs_ineq = [ 50,  # Manpower
...             100,  # Material A
...              90]  # Material B

>>> opt = linprog(c=obj, A_ub=lhs_ineq, b_ub=rhs_ineq,
...               method="revised simplex")
>>> opt
     con: array([], dtype=float64)
     fun: -1900.0
 message: 'Optimization terminated successfully.'
     nit: 2
   slack: array([ 0., 40.,  0.])
  status: 0
 success: True
       x: array([ 5.,  0., 45.,  0.])

結果告訴您最大利潤是1900並且對應於x ₁ = 5 和x ₃ = 45。在給定條件下生產第二和第四個產品是沒有利潤的。您可以在這裡得出幾個有趣的結論:

  1. 第三個產品帶來的單位利潤最大,因此工廠將生產最多。
  2. 第一個slack是0,表示人力(第一)約束左右兩邊的值是一樣的。工廠每天50生產單位,這是它的全部產能。
  3. 第二個鬆弛是40因為工廠消耗了 60 單位的原材料 A(第一種產品為 15 單位,第三種產品為 45100單位)。
  4. 第三個裕量是0,這意味著工廠消耗了所有90單位的原材料 B。這全部量都用於第三個產品。這就是為什麼工廠根本不能生產第二或第四種產品,也不能生產超過45單位的第三種產品。缺乏原材料 B.

opt.statusis0和opt.successis True,說明優化問題成功求解,最優可行解。

SciPy 的線性規劃功能主要用於較小的問題。對於更大和更復雜的問題,您可能會發現其他庫更適合,原因如下:

  • SciPy 無法執行各種外部求解器。
  • SciPy 不能使用整數決策變數。
  • SciPy 不提供促進模型構建的類或函式。您必須定義陣列和矩陣,這對於大型問題來說可能是一項乏味且容易出錯的任務。
  • SciPy 不允許您直接定義最大化問題。您必須將它們轉換為最小化問題。
  • SciPy 不允許您直接使用大於或等於符號來定義約束。您必須改用小於或等於。

幸運的是,Python 生態系統為線性程式設計提供了幾種替代解決方案,這些解決方案對於更大的問題非常有用。其中之一是 PuLP,您將在下一節中看到它的實際應用。

Using PuLP

PuLP 具有比 SciPy 更方便的線性程式設計 API。您不必在數學上修改您的問題或使用向量和矩陣。一切都更乾淨,更不容易出錯。

像往常一樣,您首先匯入您需要的內容:

from pulp import LpMaximize, LpProblem, LpStatus, lpSum, LpVariable

現在您已經匯入了 PuLP,您可以解決您的問題。

示例 1

您現在將使用 PuLP 解決此係統:

萬字教你如何用 Python 實現線性規劃

第一步是初始化一個例項LpProblem來表示你的模型:

# Create the model
model = LpProblem(name="small-problem", sense=LpMaximize)

您可以使用該sense引數來選擇是執行最小化(LpMinimize或1,這是預設值)還是最大化(LpMaximize或-1)。這個選擇會影響你的問題的結果。

一旦有了模型,就可以將決策變數定義為LpVariable類的例項:

# Initialize the decision variables
x = LpVariable(name="x", lowBound=0)
y = LpVariable(name="y", lowBound=0)

您需要提供下限,lowBound=0因為預設值為負無窮大。該引數upBound定義了上限,但您可以在此處省略它,因為它預設為正無窮大。

可選引數cat定義決策變數的類別。如果您使用的是連續變數,則可以使用預設值"Continuous"。

您可以使用變數x和y建立表示線性表示式和約束的其他 PuLP 物件:

>>>
>>> expression = 2 * x + 4 * y
>>> type(expression)
<class 'pulp.pulp.LpAffineExpression'>

>>> constraint = 2 * x + 4 * y >= 8
>>> type(constraint)
<class 'pulp.pulp.LpConstraint'>

當您將決策變數與標量相乘或構建多個決策變數的線性組合時,您會得到一個pulp.LpAffineExpression代表線性表示式的例項。

注意:您可以增加或減少變數或表示式,你可以乘他們常數,因為紙漿類實現一些Python的特殊方法,即模擬數字型別一樣__add__(),__sub__()和__mul__()。這些方法用於像定製運營商的行為+,-和*。

類似地,您可以將線性表示式、變數和標量與運算子 ==、<=、 或>=以獲取表示模型線性約束的紙漿.LpConstraint例項。

注:也有可能與豐富的比較方法來構建的約束.__eq__(),.__le__()以及.__ge__()定義了運營商的行為==,<=和>=。

考慮到這一點,下一步是建立約束和目標函式並將它們分配給您的模型。您不需要建立列表或矩陣。只需編寫 Python 表示式並使用+=運算子將它們附加到模型中:

# Add the constraints to the model
model += (2 * x + y <= 20, "red_constraint")
model += (4 * x - 5 * y >= -10, "blue_constraint")
model += (-x + 2 * y >= -2, "yellow_constraint")
model += (-x + 5 * y == 15, "green_constraint")

在上面的程式碼中,您定義了包含約束及其名稱的元組。LpProblem允許您通過將約束指定為元組來向模型新增約束。第一個元素是一個LpConstraint例項。第二個元素是該約束的可讀名稱。

設定目標函式非常相似:

# Add the objective function to the model
obj_func = x + 2 * y
model += obj_func

或者,您可以使用更短的符號:

# Add the objective function to the model
model += x + 2 * y

現在您已經新增了目標函式並定義了模型。

注意:您可以使用運算子將​​約束或目標附加到模型中,+=因為它的類LpProblem實現了特殊方法.__iadd__(),該方法用於指定 的行為+=。

對於較大的問題,lpSum()與列表或其他序列一起使用通常比重複+運算子更方便。例如,您可以使用以下語句將目標函式新增到模型中:

# Add the objective function to the model
model += lpSum([x, 2 * y])

它產生與前一條語句相同的結果。

您現在可以看到此模型的完整定義:

>>>
>>> model
small-problem:
MAXIMIZE
1*x + 2*y + 0
SUBJECT TO
red_constraint: 2 x + y <= 20

blue_constraint: 4 x - 5 y >= -10

yellow_constraint: - x + 2 y >= -2

green_constraint: - x + 5 y = 15

VARIABLES
x Continuous
y Continuous

模型的字串表示包含所有相關資料:變數、約束、目標及其名稱。

注意:字串表示是通過定義特殊方法構建的.__repr__()。有關 的更多詳細資訊.__repr__(),請檢視Pythonic OOP 字串轉換:__repr__vs__str__ .

最後,您已準備好解決問題。你可以通過呼叫.solve()你的模型物件來做到這一點。如果要使用預設求解器 (CBC),則不需要傳遞任何引數:

# Solve the problem
status = model.solve()

.solve()呼叫底層求解器,修改model物件,並返回解決方案的整數狀態,1如果找到了最優解。有關其餘狀態程式碼,請參閱LpStatus[]。

你可以得到優化結果作為 的屬性model。該函式value()和相應的方法.value()返回屬性的實際值:

>>>
>>> print(f"status: {model.status}, {LpStatus[model.status]}")
status: 1, Optimal

>>> print(f"objective: {model.objective.value()}")
objective: 16.8181817

>>> for var in model.variables():
...     print(f"{var.name}: {var.value()}")
...
x: 7.7272727
y: 4.5454545

>>> for name, constraint in model.constraints.items():
...     print(f"{name}: {constraint.value()}")
...
red_constraint: -9.99999993922529e-08
blue_constraint: 18.181818300000003
yellow_constraint: 3.3636362999999996
green_constraint: -2.0000000233721948e-07)

model.objective持有目標函式model.constraints的值,包含鬆弛變數的值,以及物件x和y具有決策變數的最優值。model.variables()返回一個包含決策變數的列表:

>>>
>>> model.variables()
[x, y]
>>> model.variables()[0] is x
True
>>> model.variables()[1] is y
True

如您所見,此列表包含使用 的建構函式建立的確切物件LpVariable。

結果與您使用 SciPy 獲得的結果大致相同。

注意:注意這個方法.solve()——它會改變物件的狀態,x並且y!

您可以通過呼叫檢視使用了哪個求解器.solver:

>>>
>>> model.solver
<pulp.apis.coin_api.PULP_CBC_CMD object at 0x7f60aea19e50>

輸出通知您求解器是 CBC。您沒有指定求解器,因此 PuLP 呼叫了預設求解器。

如果要執行不同的求解器,則可以將其指定為 的引數.solve()。例如,如果您想使用 GLPK 並且已經安裝了它,那麼您可以solver=GLPK(msg=False)在最後一行使用。請記住,您還需要匯入它:

from pulp import GLPK

現在你已經匯入了 GLPK,你可以在裡面使用它.solve():

# Create the model
model = LpProblem(name="small-problem", sense=LpMaximize)

# Initialize the decision variables
x = LpVariable(name="x", lowBound=0)
y = LpVariable(name="y", lowBound=0)

# Add the constraints to the model
model += (2 * x + y <= 20, "red_constraint")
model += (4 * x - 5 * y >= -10, "blue_constraint")
model += (-x + 2 * y >= -2, "yellow_constraint")
model += (-x + 5 * y == 15, "green_constraint")

# Add the objective function to the model
model += lpSum([x, 2 * y])

# Solve the problem
status = model.solve(solver=GLPK(msg=False))

該msg引數用於顯示來自求解器的資訊。msg=False禁用顯示此資訊。如果要包含資訊,則只需省略msg或設定msg=True。

您的模型已定義並求解,因此您可以按照與前一種情況相同的方式檢查結果:

>>>
>>> print(f"status: {model.status}, {LpStatus[model.status]}")
status: 1, Optimal

>>> print(f"objective: {model.objective.value()}")
objective: 16.81817

>>> for var in model.variables():
...     print(f"{var.name}: {var.value()}")
...
x: 7.72727
y: 4.54545

>>> for name, constraint in model.constraints.items():
...     print(f"{name}: {constraint.value()}")
...
red_constraint: -1.0000000000509601e-05
blue_constraint: 18.181830000000005
yellow_constraint: 3.3636299999999997
green_constraint: -2.000000000279556e-05

使用 GLPK 得到的結果與使用 SciPy 和 CBC 得到的結果幾乎相同。

一起來看看這次用的是哪個求解器:

>>>
>>> model.solver
<pulp.apis.glpk_api.GLPK_CMD object at 0x7f60aeb04d50>

正如您在上面用突出顯示的語句定義的那樣model.solve(solver=GLPK(msg=False)),求解器是 GLPK。

您還可以使用 PuLP 來解決混合整數線性規劃問題。要定義整數或二進位制變數,只需傳遞cat="Integer"或cat="Binary"到LpVariable。其他一切都保持不變:

# Create the model
model = LpProblem(name="small-problem", sense=LpMaximize)

# Initialize the decision variables: x is integer, y is continuous
x = LpVariable(name="x", lowBound=0, cat="Integer")
y = LpVariable(name="y", lowBound=0)

# Add the constraints to the model
model += (2 * x + y <= 20, "red_constraint")
model += (4 * x - 5 * y >= -10, "blue_constraint")
model += (-x + 2 * y >= -2, "yellow_constraint")
model += (-x + 5 * y == 15, "green_constraint")

# Add the objective function to the model
model += lpSum([x, 2 * y])

# Solve the problem
status = model.solve()

在本例中,您有一個整數變數並獲得與之前不同的結果:

>>>
>>> print(f"status: {model.status}, {LpStatus[model.status]}")
status: 1, Optimal

>>> print(f"objective: {model.objective.value()}")
objective: 15.8

>>> for var in model.variables():
...     print(f"{var.name}: {var.value()}")
...
x: 7.0
y: 4.4

>>> for name, constraint in model.constraints.items():
...     print(f"{name}: {constraint.value()}")
...
red_constraint: -1.5999999999999996
blue_constraint: 16.0
yellow_constraint: 3.8000000000000007
green_constraint: 0.0)

>>> model.solver
<pulp.apis.coin_api.PULP_CBC_CMD at 0x7f0f005c6210>

Nowx是一個整數,如模型中所指定。(從技術上講,它儲存一個小數點後為零的浮點值。)這一事實改變了整個解決方案。讓我們在圖表上展示這一點:

萬字教你如何用 Python 實現線性規劃

如您所見,最佳解決方案是灰色背景上最右邊的綠點。這是兩者的最大價值的可行的解決方案x和y,給它的最大目標函式值。

GLPK 也能夠解決此類問題。

示例 2

現在你可以使用 PuLP 來解決上面的資源分配問題:

萬字教你如何用 Python 實現線性規劃

定義和解決問題的方法與前面的示例相同:

# Define the model
model = LpProblem(name="resource-allocation", sense=LpMaximize)

# Define the decision variables
x = {i: LpVariable(name=f"x{i}", lowBound=0) for i in range(1, 5)}

# Add constraints
model += (lpSum(x.values()) <= 50, "manpower")
model += (3 * x[1] + 2 * x[2] + x[3] <= 100, "material_a")
model += (x[2] + 2 * x[3] + 3 * x[4] <= 90, "material_b")

# Set the objective
model += 20 * x[1] + 12 * x[2] + 40 * x[3] + 25 * x[4]

# Solve the optimization problem
status = model.solve()

# Get the results
print(f"status: {model.status}, {LpStatus[model.status]}")
print(f"objective: {model.objective.value()}")

for var in x.values():
    print(f"{var.name}: {var.value()}")

for name, constraint in model.constraints.items():
    print(f"{name}: {constraint.value()}")

在這種情況下,您使用字典 x來儲存所有決策變數。這種方法很方便,因為字典可以將決策變數的名稱或索引儲存為鍵,將相應的LpVariable物件儲存為值。列表或元組的LpVariable例項可以是有用的。

上面的程式碼產生以下結果:

status: 1, Optimal
objective: 1900.0
x1: 5.0
x2: 0.0
x3: 45.0
x4: 0.0
manpower: 0.0
material_a: -40.0
material_b: 0.0

如您所見,該解決方案與使用 SciPy 獲得的解決方案一致。最有利可圖的解決方案是每天生產5.0第一件產品和45.0第三件產品。

讓我們把這個問題變得更復雜和有趣。假設由於機器問題,工廠無法同時生產第一種和第三種產品。在這種情況下,最有利可圖的解決方案是什麼?

現在您有另一個邏輯約束:如果x ₁ 為正數,則x ₃ 必須為零,反之亦然。這是二元決策變數非常有用的地方。您將使用兩個二元決策變數y ₁ 和y ₃,它們將表示是否生成了第一個或第三個產品:

 1model = LpProblem(name="resource-allocation", sense=LpMaximize)
 2
 3# Define the decision variables
 4x = {i: LpVariable(name=f"x{i}", lowBound=0) for i in range(1, 5)}
 5y = {i: LpVariable(name=f"y{i}", cat="Binary") for i in (1, 3)}
 6
 7# Add constraints
 8model += (lpSum(x.values()) <= 50, "manpower")
 9model += (3 * x[1] + 2 * x[2] + x[3] <= 100, "material_a")
10model += (x[2] + 2 * x[3] + 3 * x[4] <= 90, "material_b")
11
12M = 100
13model += (x[1] <= y[1] * M, "x1_constraint")
14model += (x[3] <= y[3] * M, "x3_constraint")
15model += (y[1] + y[3] <= 1, "y_constraint")
16
17# Set objective
18model += 20 * x[1] + 12 * x[2] + 40 * x[3] + 25 * x[4]
19
20# Solve the optimization problem
21status = model.solve()
22
23print(f"status: {model.status}, {LpStatus[model.status]}")
24print(f"objective: {model.objective.value()}")
25
26for var in model.variables():
27    print(f"{var.name}: {var.value()}")
28
29for name, constraint in model.constraints.items():
30    print(f"{name}: {constraint.value()}")

除了突出顯示的行之外,程式碼與前面的示例非常相似。以下是差異:

  • 第 5 行定義了二元決策變數y[1]並y[3]儲存在字典中y。
  • 第 12 行定義了一個任意大的數M。100在這種情況下,該值足夠大,因為您100每天的數量不能超過單位。
  • 第 13 行說如果y[1]為零,則x[1]必須為零,否則它可以是任何非負數。
  • 第 14 行說如果y[3]為零,則x[3]必須為零,否則它可以是任何非負數。
  • 第 15 行說要麼y[1]ory[3]為零(或兩者都是),所以要麼x[1]or 也x[3]必須為零。

這是解決方案:

status: 1, Optimal
objective: 1800.0
x1: 0.0
x2: 0.0
x3: 45.0
x4: 0.0
y1: 0.0
y3: 1.0
manpower: -5.0
material_a: -55.0
material_b: 0.0
x1_constraint: 0.0
x3_constraint: -55.0
y_constraint: 0.0

事實證明,最佳方法是排除第一種產品而只生產第三種產品。

線性規劃求解器

就像有許多資源可以幫助您學習線性規劃和混合整數線性規劃一樣,還有許多具有 Python 包裝器的求解器可用。這是部分列表:

  • GLPK
  • LP Solve
  • CLP
  • CBC
  • CVXOPT
  • SciPy
  • SCIP with PySCIPOpt
  • Gurobi Optimizer
  • CPLEX
  • XPRESS
  • MOSEK

其中一些庫,如 Gurobi,包括他們自己的 Python 包裝器。其他人使用外部包裝器。例如,您看到可以使用 PuLP 訪問 CBC 和 GLPK。

結論

您現在知道什麼是線性規劃以及如何使用 Python 解決線性規劃問題。您還了解到 Python 線性程式設計庫只是本機求解器的包裝器。當求解器完成其工作時,包裝器返回解決方案狀態、決策變數值、鬆弛變數、目標函式等。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章