使用PuLp求解
我們解決線性規划問題一般是通過以下三個步驟。
1.列出約束條件及目標函數
2.畫出約束條件所表示的可行域
3.在可行域內求目標函數的最優解及最優值
使用pulp工具包,我們只需要做第一步即可,使用pulp提供的API提供目標函數及約束條件就可以直接求解,非常方便。
Exported Classes:
LpProblem – Container class for a Linear programming problem
LpVariable – Variables that are added to constraints in the LP
LpConstraint – A constraint of the general form
a1x1+a2x2 …anxn (<=, =, >=) b
LpConstraintVar – Used to construct a column of the model in column-wise modelling
Exported Functions:
value() – Finds the value of a variable or expression
lpSum() – given a list of the form [a1*x1, a2x2, …, anxn] will construct a linear expression to be used as a constraint or variable
lpDot() –given two lists of the form [a1, a2, …, an] and [ x1, x2, …, xn] will construct a linear epression to be used as a constraint or variable
1、安裝PuLp並導入, 下載地址:https://github.com/coin-or/pulp
pip install pulp
from pulp import *
2、定義線性規划問題
Prob = LpProblem ( "problem_name" , sense )
定義Prob變量,用來定義一個LP問題實例,其中problem_name指定問題名(輸出信息用),sense值是LpMinimize或LpMaximize中的一個,用來指定目標函數是求最大值還是最小值。
4、定義決策變量
有兩種方式, 一種是定義單個變量(適用於變量個數不多的情況)
DV = LpVariable ( decision variable name , lowbound , upbound ,category )
decision variable name指定變量名,lowBound和upBound是下界和上界, 默認是none, 分別代表負無窮和正無窮, category用來指定變量是離散(LpInteger,LpBinary)還是連續(LpContinuous),
還有一種方式是用dict方式來定義大量的變量,如下:
Ingredients = ['Chicken','Beef','Mutton','Rice','Wheat','Gel']
variables = LpVariable.dicts ("Ingr",Ingredients,0)
上面兩行代碼輸出的變量字典是:
{'Chicken': Ingr_Chicken, 'Beef': Ingr_Beef, 'Mutton': Ingr_Mutton, 'Rice': Ingr_Rice, 'Wheat': Ingr_Wheat, 'Gel': Ingr_Gel}
5、添加目標函數和約束條件
先增加目標函數:
Prob += linear objective in equantion from objective name
= XXX
注意: Prob += A 意思是 Prob = Prob + A, 但要注意這兩者還是有區別的,可以參考:https://www.sohu.com/a/337619727_571478
再設置約束條件:
Prob += linear objective in equantion from constraint name
6、寫入LP文件
Prob.writeLP ( filename )
7、模型求解
Prob.slove ( )
輸出的結果是’Optimal’,說明找到了最優解。my_lp_problem.status是一個常量,pulp.LpStatus是一個dict,把常量變成可讀的字符串:
LpStatus = {
LpStatusNotSolved:"Not Solved",
LpStatusOptimal:"Optimal",
LpStatusInfeasible:"Infeasible",
LpStatusUnbounded:"Unbounded",
LpStatusUndefined:"Undefined",
}
這些常量的含義是:
Not Solved:還沒有調研solve()函數前的狀態。
Optimal:找到了最優解。
Infeasible:問題沒有可行解(比如定義了constraints x <= 1並且x >=2這樣的約束)。
Unbounded:約束條件是無界的(not bounded),最大化會導致無窮大(比如只有一個x >= 3這樣的約束)。
Undefined:最優解可能存在但是沒有求解出來。
8、結果顯示
check status : pulp.LpStatus[Prob.status]
注意:LpStatus是dict
PuLP支持很多開源的線性規划求解器(solver),比如CBC和GLPK;此外它也支持商業(收費)的求解器比如Gurobi和IBM的CPLEX。
默認的是CBC,安裝PuLP是默認就會安裝。對於大部分問題來說,來自 COIN-OR 的CBC開源求解器就夠用了。
下面我們來求解:
my_lp_problem.solve()
print(pulp.LpStatus[my_lp_problem.status])
思考程序本質
problem對象是如何通過不斷加來獲得目標函數和約束的?熟悉python或者c++的朋友可能會想到一個詞:操作符重載。
沒錯,就是這么實現的,上述的對象幾乎都實現了不同的重載。
首先是Problem對象prob,全名pulp.pulp.LpProblem;當打印輸出(print)時,會打印出問題名,當不斷增加目標函數、約束時,也會隨着print輸出;而它的__add__一定是被定義過了,我們先說其他對象。
當我們定義一個變量時,它的類型是pulp.pulp.LpVariable,當我們對這些變量和其他變量做加法、和其他常數做乘法時,它會返回一個新的對象,經檢測,這個新對象的類名叫pulp.pulp.LpAffineExpression,顧名思義,叫做關系表達式;如果print,會打印這個關系表達式。
而如果對關系表達式做出:>=、<=、==時,會返回新的對象,類名叫做pulp.pulp.LpConstraint,即約束對象;如果print,會打印這個約束。
將關系表達式(pulp.pulp.LpAffineExpression)與問題(pulp.pulp.LpProblem)相加時,會返回新的問題對象,並且新的問題對象會將這個關系表達式作為目標函數。
將約束對象(pulp.pulp.LpConstraint)與問題(pulp.pulp.LpProblem)相加時,會返回新的問題對象,並且這個新的問題對象會多出約束對象所代表的約束條件。
調用問題對象的solve方法,解出線性規划的解。
訪問問題對象的objective成員變量,會得到目標函數(關系表達式對象)。
調用pulp的value方法,將獲得對變量代入值的結果,如果是關系表達式對象,將獲得優化結果;如果是變量對象,將獲得優化結果達到時的變量取值;如果是None,說明你忘調用solve了。
其它參考資料:
https://github.com/benalexkeen/Introduction-to-linear-programming
https://www.codercto.com/a/109524.html
https://www.jianshu.com/p/9be417cbfebb