最优解问题——PuLP解决线性规划问题
Posted 悟乙己
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了最优解问题——PuLP解决线性规划问题相关的知识,希望对你有一定的参考价值。
文章目录
1 PuLP介绍
1.1 理论、流程介绍
线性规划是研究线性约束条件下线性目标函数的极值问题的数学理论和方法。Python中有许多第三方的工具可以解决这类问题,这里介绍常用的pulp工具包。pulp能够解包括整数规划在内的绝大多数线性规划问题,并且提供了多种solver,每种solver针对不同类型的线性规划问题有更好的效果。
关于pulp工具包的详细介绍,请参见pulp官网。
pip install pulp
我们解决线性规划问题一般是通过以下三个步骤。
- 1.列出约束条件及目标函数
- 2.画出约束条件所表示的可行域
- 3.在可行域内求目标函数的最优解及最优值
1.2 主函数介绍
1.2.1 LpProblem类
LpProblem(name='NoName', sense=LpMinimize)
构造函数,用来构造一个LP问题实例,其中name指定问题名(输出信息用),
sense值是LpMinimize
或LpMaximize
中的一个,用来指定目标函数是求极大值还是极小值。
solve(solver=None, **kwargs)
在对LpProblem添加完约束条件后,调用该函数进行求解,如果不是求解特定的整数规划问题,solver一般使用默认即可。
1.2.2 LpVariable类
LpVariable(name, lowBound=None, upBound=None, cat='Continuous', e=None)
构造函数,用来构造LP问题中的变量,name指定变量名,lowBound和upBound是下界和上界,
默认分别是负无穷到正无穷,cat用来指定变量是离散(Integer,Binary)还是连续(Continuous)。
取值包括:
- LpInteger - 整数型
- LpContinuous - 连续性
dicts(name, indexs, lowBound=None, upBound=None, cat='Continuous', indexStart=[])
用来构造变量字典,可以让我们不用一个个地创建Lp变量实例。name指定所有变量的前缀,
index是列表,其中的元素会被用来构成变量名,后面三个参数和LbVariable
中的一样。
1.2.3 lpSum(vector)
计算一个序列的值,使用lpSum求解比普通的sum函数要快得多。
1.3 一些函数写法优化
1.3.1 赋值
变量定义,注意最后的LpInteger,当设置该参数时,则该决策变量只能取整数
如果决策变量可以取小数,那就设置为LpContinuous
x1 = LpVariable('日间电视',0,14,LpInteger)
x2 = LpVariable('夜间电视',0,8,LpInteger)
x3 = LpVariable('网络媒体',0,40,LpInteger)
x4 = LpVariable('平面媒体',0,5,LpInteger)
x5 = LpVariable('户外广告',0,50,LpInteger)
变成了:
Ingredients = ['日间电视','夜间电视','网络媒体','平面媒体','户外广告']
ingredient_vars = LpVariable.dicts("Ingr",Ingredients,0)
1.3.2 PuLP里面不可使用的
不可以使用:
x1/x2
1/x1
x2/3
案例一:优化投放广告渠道的资源
来看一个案例:如何用Python解决最优化问题?
现有5个广告投放渠道,分别是日间电视、夜间电视、网络媒体、平面媒体、户外广告,每个渠道的效果、费用及限制如下表所示:
注:案例来自《活用数据:驱动业务的数据分析实战》,作者陈哲
比如日间电视这个渠道,每做一次投放需要费用1000元,可以触达2000个用户(曝光量),带来咨询量600个(也可以看做app下载量等目标产出),该广告渠道最多投放14次。
除了表格中的限制条件外,还要求:
电视广告至少投放20次(包括日间和夜间);
触达用户数(曝光量)不少于10万;
电视广告投入费用不超过3万元;
现在公司总共给到4万的营销费用,要求咨询量能最大化。
这是一个线性规划问题,即在有限的资源(约束条件)下如何使效用(线性目标函数)最大化。
把5个广告渠道各自能使用的次数作为决策变量,分别用
来表示
那么,现在要优化的目标函数是
约束条件:
电视广告投放至少20次,
用户曝光量至少10万,
电视广告费用不超过3万,
总广告费用不超过4万,
投放次数为正整数,且
使用PuLP的代码为:
from pulp import *
prob = LpProblem('营销优化问题',LpMaximize)
# 变量定义,注意最后的LpInteger,当设置该参数时,则该决策变量只能取整数
# 如果决策变量可以取小数,那就设置为LpContinuous
x1 = LpVariable('日间电视',0,14,LpInteger)
x2 = LpVariable('夜间电视',0,8,LpInteger)
x3 = LpVariable('网络媒体',0,40,LpInteger)
x4 = LpVariable('平面媒体',0,5,LpInteger)
x5 = LpVariable('户外广告',0,50,LpInteger)
# 需要优化的表达式
prob += 600*x1+800*x2+500*x3+400*x4+300*x5
# 约束条件
prob += 1000*x1 + 2000*x2 <= 30000, '电视广告费用不超过3万'
prob += x1+x2 >= 20, '电视广告至少20次'
prob += 1000*x1+2000*x2+400*x3+1000*x4+100*x5 <= 40000, '广告总费用不超过4万'
prob += 2000*x1+4000*x2+3000*x3+5000*x4+600*x5 >= 100000,'曝光人数不少于10万'
#lp文件保存该优化问题的信息,可以用文本编辑器打开
prob.writeLP("营销优化问题.lp")
# 执行计算
prob.solve()
# 如果成功得到了最优值,则会输出 Optimal
print(LpStatus[prob.status])
# 得到最优值时,各决策变量的取值,如果没有找到最优值,则输出None
for v in prob.variables():
print(v.name, "=", v.varValue)
# 输出最优值,如果没有找到最优值,则输出None
print("最大咨询量为", value(prob.objective))
输出结果:
可以看到最优值为39200,对应的决策变量的取值均为整数。
PuLP的代码量看着虽然多,但是相对于scipy.optimize.linprog函数,PuLP的代码非常灵活,而且很直观,对参数取值是整数或者小数还有细分。
案例二:如何分配水库供水量,公司才能获利最多
供水公司有三个水库分别为A,B,C向四个小区甲乙丙丁供水,A和B向所有小区供水,C仅向甲乙丙供水,水库最大供水量(千吨)
小区用水情况为
水库供水收入900元/千吨,支出费用为:其他费用450/千吨,引水管理费:
问 如何分配水库供水量,公司才能获利最多。
目标函数为:
z = 160.0*x11 + 130.0*x12 + 220.0*x13 + 170.0*x14 + 140.0*x21 + 130.0*x22 + 190.0*x23 + 150.0*x24 + 190.0*x31 + 200.0*x32 + 230.0*x33
求的值,使Z最小。
# coding=utf-8
from pulp import *
def get_re():
pass
def getresult(c, con):
# 设置对象
prob = LpProblem('myPro', LpMinimize)
# 设置三个变量,并设置变量最小取值
x11 = LpVariable("x11", lowBound=0)
x12 = LpVariable("x12", lowBound=0)
x13 = LpVariable("x13", lowBound=0)
x14 = LpVariable("x14", lowBound=0)
x21 = LpVariable("x21", lowBound=0)
x22 = LpVariable("x22", lowBound=0)
x23 = LpVariable("x23", lowBound=0)
x24 = LpVariable("x24", lowBound=0)
x31 = LpVariable("x31", lowBound=0)
x32 = LpVariable("x32", lowBound=0)
x33 = LpVariable("x33", lowBound=0)
X = [x11, x12, x13, x14, x21, x22, x23, x24, x31, x32, x33]
#c = [160, 130, 220, 170, 140, 130, 190, 150, 190, 200, 230]
# 目标函数
z = 0
for i in range(len(X)):
z += X[i]*c[i]
#print(z)
prob += z
# 载入约束变量
prob += x11+x12+x13+x14 == con[0]# 约束条件1
prob += x21+x22+x23+x24 == con[1]
prob += x31+x32+x33 == con[2]
prob += x11+x21+x31 <= con[3]
prob += x11+x21+x31 >= con[4]
prob += x12 + x22 + x32 <= con[5]
prob += x12 + x22 + x32 >= con[6]
prob += x13 + x23 + x33 <= con[7]
prob += x13 + x23 + x33 >= con[8]
prob += x14 + x24 <= con[9]
prob += x14 + x24 >= con[10]
# 求解
status = prob.solve()
print(status)
print(LpStatus[status])
print(value(prob.objective)) # 计算结果
# 显示结果
# for i in prob.variables():
# print(i.name + "=" + str(i.varValue))
for i in prob.variables():
print(i.varValue)
if __name__ == '__main__':
c = [160, 130, 220, 170, 140, 130, 190, 150, 190, 200, 230]
con = [50, 60, 50, 80, 30, 140, 70, 30,10, 50, 10]
getresult(mubiao, yueshu)
输出结果:
Optimal24400.00.050.00.00.00.050.00.010.040.00.010.0
案例三: 求解最普通的线性规划问题
【数学建模】线性规划各种问题的Python调包方法
求解最普通的线性规划问题:
import pulp
#目标函数的系数
z = [2, 3, 1]
#约束
a = [[1, 4, 2], [3, 2, 0]]
b = [8, 6]
#确定最大化最小化问题,最大化只要把Min改成Max即可
m = pulp.LpProblem(sense=pulp.LpMinimize)
#定义三个变量放到列表中
x = [pulp.LpVariable(f'xi', lowBound=0) for i in [1,2,3]]
#定义目标函数,lpDot可以将两个列表的对应位相乘再加和
#相当于z[0]*x[0]+z[1]*x[0]+z[2]*x[2]
m += pulp.lpDot(z, x)
#设置约束条件
for i in range(len(a)):
m += (pulp.lpDot(a[i], x) >= b[i])
#求解
m.solve()
#输出结果
print(f'优化结果:pulp.value(m.objective)')
print(f'参数取值:[pulp.value(var) for var in x]')
#output:
#优化结果:7.0
#参数取值:[2.0, 0.0, 3.0]
每一步的说明已经注释在代码中,可以看到输出结果,两者的变量取值并不一致,但代入目标函数的结果都是一样的。
同样的,如果存在类似x_1+2x_2+4x_3=101
这种情况,可以:
A_eq = [1,2,4]
b_eq = 101
m += (pulp.lpDot(A_eq, x) == b_eq)
案例四:运输问题
import pulp
import numpy as np
from pprint import pprint
def transportation_problem(costs, x_max, y_max):
row = len(costs)
col = len(costs[0])
prob = pulp.LpProblem('Transportation Problem', sense=pulp.LpMaximize)
var = [[pulp.LpVariable(f'xij', lowBound=0, cat=pulp.LpInteger) for j in range(col)] for i in range(row)]
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
prob += pulp.lpDot(flatten(var), costs.flatten())
for i in range(row):
prob += (pulp.lpSum(var[i]) <= x_max[i])
for j in range(col):
prob += (pulp.lpSum([var[i][j] for i in range(row)]) <= y_max[j])
prob.solve()
return 'objective':pulp.value(prob.objective), 'var': [[pulp.value(var[i][j]) for j in range(col)] for i in range(row)]
然后构造参数传递进去:
if __name__ == '__main__':
costs = np.array([[500, 550, 630, 1000, 800, 700],
[800, 700, 600, 950, 900, 930],
[1000, 960, 840, 650, 600, 700],
[1200, 1040, 980, 860, 880, 780]])
max_plant = [76, 88, 96, 40]
max_cultivation = [42, 56, 44, 39, 60, 59]
res = transportation_problem(costs, max_plant, max_cultivation)
print(f'最大值为res["objective"]')
print('各变量的取值为:')
pprint(res['var'])
#output:
#最大值为284230.0
#各变量的取值为:
#[[0.0, 0.0, 6.0, 39.0, 31.0, 0.0],
# [0.0, 0.0, 0.0, 0.0, 29.0, 59.0],
# [2.0, 56.0, 38.0, 0.0, 0.0, 0.0],
# [40.0, 0.0, 0.0, 0.0, 0.0, 0.0]]
案例五:指派问题
【数学建模】线性规划各种问题的Python调包方法
先定义通用解决方法,其中的flatten是递归展开列表用的。
def assignment_problem(efficiency_matrix):
row = len(efficiency_matrix)
col = len(efficiency_matrix[0])
flatten = lambda x: [y for l in x for y in flatten(l)] if type(x) is list else [x]
m = pulp.LpProblem('assignment', sense=pulp.LpMinimize)
var_x = [[pulp.LpVariable(f'xij', cat=pulp.LpBinary) for j in range(col)] for i in range(row)]
m += pulp.lpDot(efficiency_matrix.flatten(), flatten(var_x))
for i in range(row):
m += (pulp.lpDot(var_x[i], [1]*col) == 1)
for j in range(col):
m += (pulp.lpDot([var_x[i][j] for i in range(row)], [1]*row) == 1)
m.solve()
print(m)
return 'objective':pulp.value(m.objective), 'var': [[pulp.value(var_x[i][j]) for j in range(col)] for i in range(row)]
输出结果:
efficiency_matrix = np.array([
[12, 7, 9, 7, 9],
[8, 9, 6, 6, 6],
[7, 17, 12, 14, 9],
[15, 14, 6, 6, 10],
[4, 10, 7, 10, 9]
])
res = assignment_problem(efficiency_matrix)
print(f'最小值res["objective"]')
print(res['var'])
#output
#最小值32.0
#[[0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0], [0.0, 0.0, 1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0, 0.0]]
以上是关于最优解问题——PuLP解决线性规划问题的主要内容,如果未能解决你的问题,请参考以下文章