PuLp之一: 线性规划问题求解的一般步骤
使用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