整数线性规划——pulp指南

整数线性规划——pulp指南

PuLP是一个用Python编写的线性规划建模工具。PuLP可以生成MPS或LP文件,并调用GLPK、COIN-OR CLP/CBC、CPLEX、GUROBI、MOSEK、XPRESS、CHOCO、MIPCL、HiGHS、SCIP/FSCIP等求解线性问题。

官方文档地址:https://coin-or.github.io/pulp/index.html

线性规划的标准表示如下:

(LP) min ⁡ c T x , s . t .   A x ≤ b , x ∈ R + n \text{(LP)}\quad\begin{aligned} &\min c^Tx,\\ &s.t.\ Ax\le b,\\ &\qquad x\in \R_+^n \end{aligned} (LP)mincTx,s.t. Axb,xR+n

一、引入

使用pip install pulp安装pulp。

from pulp import *

可以使用LpVariable() 创建新的变量,例如创建变量 0 ≤ x ≤ 3 0\le x\le 3 0x3

x = LpVariable("x", 0, 3)

创建变量 0 ≤ y ≤ 1 0\le y\le 1 0y1

y = LpVariable("y", 0, 1)

使用LpProblem()创建新的问题,比如说创建名为myProblem的线性规划问题:

prob = LpProblem("myProblem", LpMinimize)

将变量组合成表达式和约束条件,然后将它们添加到问题中:

prob += x + y <= 2

如果添加一个表达式(而非约束),它将变成目标函数:

prob += -4*x + y

使用默认求解器进行求解:

status = prob.solve()

展示解的状态:

LpStatus[status]
'Optimal'

查看具体的值:

print(value(x))
print(value(y))
2.0
0.0

二、配置求解器

PuLP通常有多种连接求解器的方式。根据连接方式的不同,配置连接的方法也会有所不同。我们可以将集成总结为两大类:

  1. 使用求解器的命令行界面。

  2. 使用求解器的Python库。

并非所有求解器都有Python库,但大多数都有命令行界面。如果求解器API的名称以CMD结尾(例如PULP_CBC_CMD、CPLEX_CMD、GUROBI_CMD等),那么它就是命令行,否则是python库。

这里我安装cplex,可以前往IBM官网进行申请安装:https://www.ibm.com/analytics/cplex-optimizer

配置路径如下:

# 这里需要换成自己电脑上cplex.exe所在的地址
path_to_cplex = r'D:\software\cplex\cplex\bin\x64_win64\cplex.exe'
import pulp as pl
model = pl.LpProblem("Example", pl.LpMinimize)
solver = pl.CPLEX_CMD(path=path_to_cplex)
_var = pl.LpVariable('a')
_var2 = pl.LpVariable('a2')
model += _var + _var2 == 1
result = model.solve(solver)

配置一次以后便不用再配置。

import pulp as pl
model = pl.LpProblem("Example", pl.LpMinimize)
solver = pl.CPLEX_CMD()
_var = pl.LpVariable('a')
_var2 = pl.LpVariable('a2')
model += _var + _var2 == 1
result = model.solve(solver)
print(result)
1

三、案例学习——猫粮问题

问题描述

Uncle Ben’s希望以尽可能低的成本生产他们的猫粮产品,同时确保它们符合罐头上显示的营养分析要求。因此,他们希望在仍然满足营养标准的情况下改变使用每种成分的数量(主要成分包括鸡肉、牛肉、羊肉、大米、小麦和明胶)。

在这里插入图片描述

鸡肉、牛肉和羊肉的成本分别为0.013美元、0.008美元和0.010美元,而大米、小麦和明胶的成本分别为0.002美元、0.005美元和0.001美元。(所有成本均为每克。)对于这个练习,我们将忽略维生素和矿物质成分。(这些成本可能非常小。)

每种成分对最终产品中蛋白质、脂肪、纤维和盐的总重量都有贡献。每克成分的贡献(以克为单位)如下表所示:

Stuff Protein Fat Fibre Salt
Chicken 0.100 0.080 0.001 0.002
Beef 0.200 0.100 0.005 0.005
Mutton 0.150 0.110 0.003 0.007
Rice 0.000 0.010 0.100 0.002
Wheat bran 0.040 0.010 0.150 0.008
Gel 0.000 0.000 0.000 0.000

建模表示

确定决策变量

假设Whiskas想要用两种成分制作他们的猫食:鸡肉和牛肉。首先定义我们的决策变量:

x 1 = 一罐猫粮中所用鸡肉的百分比 x 2 = 一罐猫粮中所用牛肉的百分比 x_1=一罐猫粮中所用鸡肉的百分比\\ x_2 =一罐猫粮中所用牛肉的百分比 x1=一罐猫粮中所用鸡肉的百分比x2=一罐猫粮中所用牛肉的百分比

这些变量必须大于零。

制定目标函数

目标函数变为:

min ⁡ 0.013 x 1 + 0.008 x 2 \min 0.013x_1+0.008x_2 min0.013x1+0.008x2

限制

变量必须加起来等于100,并且满足营养需求:

1.000 x 1 + 1.000 x 2 = 100.0 0.100 x 1 + 0.200 x 2 ≥ 8.0 0.080 x 1 + 0.100 x 2 ≥ 6.0 0.001 x 1 + 0.005 x 2 ≤ 2.0 0.002 x 1 + 0.005 x 2 ≤ 0.4 \begin{aligned} 1.000x_1&+1.000x_2=100.0\\ 0.100x_1&+0.200x_2\ge 8.0\\ 0.080x_1&+0.100x_2\ge 6.0\\ 0.001x_1&+0.005x_2\le 2.0\\ 0.002x_1&+0.005x_2\le 0.4 \end{aligned} 1.000x10.100x10.080x10.001x10.002x1+1.000x2=100.0+0.200x28.0+0.100x26.0+0.005x22.0+0.005x20.4

python实现

引入库:

from pulp import *

使用LpProblem函数定义名为prob的变量。它有两个参数,第一个是此问题的任意名称(作为字符串),第二个参数取决于要解决的LP类型,可以是LpMinimize或LpMaximize:

prob = LpProblem("WhiskasModel", LpMinimize)

使用 LpVariable 类创建问题变量 x1x2 。它有四个参数,第一个是表示此变量的任意名称,第二个是此变量的下限,第三个是上限,第四个是数据类型(离散或连续)。第四个参数的选项为LpContinuousLpInteger,其默认值为LpContinuous。如果我们正在建模要生产的罐数,我们需要输入LpInteger,因为它是离散数据。边界可以直接输入为数字,或者为 None 表示没有边界(即正无穷或负无穷),默认值为 None。创建适用于本问题的两个变量:

# 本问题中的鸡肉和牛肉变量
x1 = LpVariable("ChickenPercent",0 , None, LpInteger)
x2 = LpVariable("BeefPercent",0 , None, LpInteger)

添加目标函数:

# 首先添加目标函数
prob += 0.013 * x1 + 0.008 * x2, "每罐猫粮的成本价格"

添加限制条件:

# The five constraints are entered
prob += x1 + x2 == 100, "百分比之和"
prob += 0.100 * x1 + 0.200 * x2 >= 8.0, "蛋白质需求"
prob += 0.080 * x1 + 0.100 * x2 >= 6.0, "脂肪需求"
prob += 0.001 * x1 + 0.005 * x2 <= 2.0, "纤维需求"
prob += 0.002 * x1 + 0.005 * x2 <= 0.4, "盐分需求"

使用 writeLP() 函数将这些信息复制到一个 .lp 文件中,该文件位于你的代码块运行的目录中。一旦代码成功运行,可以使用文本编辑器打开这个 .lp 文件,看看上面的步骤都在做什么:

# 将问题数据写入.lp文件中
prob.writeLP('WhiskasModel.lp')
[BeefPercent, ChickenPercent]

LP问题是使用PuLP选择的求解器来解决的,在这种情况下,solve() 后面的输入括号为空,但是它们可以用来指定要使用的求解器(例如,prob.solve(CPLEX())):

prob.solve(CPLEX())
1

输出求解结果,结果可能是 “Not Solved”, “Infeasible”, “Unbounded”, “Undefined” 或者 “Optimal”:

print("Status:", LpStatus[prob.status])
Status: Optimal

查看各个变量的值:

for v in prob.variables():
    print(v.name, "=", v.varValue)
BeefPercent = 66.0
ChickenPercent = 34.0

输出目标函数的值:

print("每罐猫粮的原材料成本为:", value(prob.objective))
每罐猫粮的原材料成本为: 0.97

完整公式

现在我们将使用所有变量完全公式化问题。虽然可以在上面的方法中加入一些内容将其实现为Python,但我们将看到一种更好的方法,它不会混合问题数据和公式化。这将使更改其他测试的任何问题数据变得更容易。我们将以代数方式定义问题的方式开始:

  1. 确定决策变量,决策变量是我们在罐中包含的不同成分的百分比。由于罐子重100g,这些百分比也代表每种成分包含的克数。请注意,这些百分比必须介于0和100之间。

    x 1 = 一罐猫粮中所用鸡肉的百分比 x 2 = 一罐猫粮中所用牛肉的百分比 x 3 = 一罐猫粮中所用羊肉的百分比 x 4 = 一罐猫粮中所用米饭的百分比 x 5 = 一罐猫粮中所用麦麸的百分比 x 6 = 一罐猫粮中所用凝胶的百分比 x_1=一罐猫粮中所用鸡肉的百分比\\ x_2=一罐猫粮中所用牛肉的百分比\\ x_3=一罐猫粮中所用羊肉的百分比\\ x_4=一罐猫粮中所用米饭的百分比\\ x_5=一罐猫粮中所用麦麸的百分比\\ x_6=一罐猫粮中所用凝胶的百分比 x1=一罐猫粮中所用鸡肉的百分比x2=一罐猫粮中所用牛肉的百分比x3=一罐猫粮中所用羊肉的百分比x4=一罐猫粮中所用米饭的百分比x5=一罐猫粮中所用麦麸的百分比x6=一罐猫粮中所用凝胶的百分比

  2. 制定目标函数。对于Whiskas猫食品问题,目标是最小化每罐猫食品成分的总成本。

    min ⁡ 0.013 x 1 + 0.008 x 2 + 0.010 x 3 + 0.002 x 4 + 0.005 x 5 + 0.001 x 6 \min 0.013x_1+0.008x_2+0.010x_3+0.002x_4+0.005x_5+0.001x_6 min0.013x1+0.008x2+0.010x3+0.002x4+0.005x5+0.001x6

  3. 制定约束条件。Whiskas猫食品问题的约束条件是:

    • 百分比总和必须占整个罐子(= 100%)。

    • 满足规定的营养分析要求。

    “整罐”的约束是:

    x 1 + x 2 + x 3 + x 4 + x 5 + x 6 = 100 x_1+x_2+x_3+x_4+x_5+x_6=100 x1+x2+x3+x4+x5+x6=100

    为了满足营养分析要求,我们需要每100g至少8g蛋白质,6g脂肪,但不超过2g纤维和0.4g盐。为了制定这些约束条件,我们利用以前的每种成分贡献表。这使我们能够制定有关来自成分的蛋白质,脂肪,纤维和盐总贡献的以下约束条件:
    0.100 x 1 + 0.200 x 2 + 0.150 x 3 + 0.000 x 4 + 0.040 x 5 + 0.0 x 6 ≥ 8.0 0.080 x 1 + 0.100 x 2 + 0.110 x 3 + 0.010 x 4 + 0.010 x 5 + 0.0 x 6 ≥ 6.0 0.001 x 1 + 0.005 x 2 + 0.003 x 3 + 0.100 x 4 + 0.150 x 5 + 0.0 x 6 ≤ 2.0 0.002 x 1 + 0.005 x 2 + 0.007 x 3 + 0.002 x 4 + 0.008 x 5 + 0.0 x 6 ≤ 0.4 \begin{aligned} 0.100x_1&+0.200x_2+0.150x_3+0.000x_4+0.040x_5+0.0x_6≥8.0\\ 0.080x_1&+0.100x_2+0.110x_3+0.010x_4+0.010x_5+0.0x_6≥6.0\\ 0.001x_1&+0.005x_2+0.003x_3+0.100x_4+0.150x_5+0.0x_6≤2.0\\ 0.002x_1&+0.005x_2+0.007x_3+0.002x_4+0.008x_5+0.0x_6≤0.4 \end{aligned} 0.100x10.080x10.001x10.002x1+0.200x2+0.150x3+0.000x4+0.040x5+0.0x68.0+0.100x2+0.110x3+0.010x4+0.010x5+0.0x66.0+0.005x2+0.003x3+0.100x4+0.150x5+0.0x62.0+0.005x2+0.007x3+0.002x4+0.008x5+0.0x60.4

python实现
from pulp import *
# 原材料
Ingredients = ["CHICKEN", "BEEF", "MUTTON", "RICE", "WHEAT", "GEL"]

# 成本字典
costs = {
    
    
    "CHICKEN": 0.013,
    "BEEF": 0.008,
    "MUTTON": 0.010,
    "RICE": 0.002,
    "WHEAT": 0.005,
    "GEL": 0.001,
}

# 蛋白质字典
proteinPercent = {
    
    
    "CHICKEN": 0.100,
    "BEEF": 0.200,
    "MUTTON": 0.150,
    "RICE": 0.000,
    "WHEAT": 0.040,
    "GEL": 0.000,
}

# 脂肪字典
fatPercent = {
    
    
    "CHICKEN": 0.080,
    "BEEF": 0.100,
    "MUTTON": 0.110,
    "RICE": 0.010,
    "WHEAT": 0.010,
    "GEL": 0.000,
}

# 纤维字典
fibrePercent = {
    
    
    "CHICKEN": 0.001,
    "BEEF": 0.005,
    "MUTTON": 0.003,
    "RICE": 0.100,
    "WHEAT": 0.150,
    "GEL": 0.000,
}

# 盐分字典
saltPercent = {
    
    
    "CHICKEN": 0.002,
    "BEEF": 0.005,
    "MUTTON": 0.007,
    "RICE": 0.002,
    "WHEAT": 0.008,
    "GEL": 0.000,
}
# 创建问题
prob2 = LpProblem("The Whiskas Problem", LpMinimize)

创建一个名为ingredient_vars的字典,其中包含LP变量,其下限定义为零。字典的引用键是原料名称,数据为Ingr_IngredientName。 (例如:MUTTON:Ingr_MUTTON)

ingredient_vars = LpVariable.dicts("Ingr", Ingredients, 0)

由于costsingredient_vars现在是以成分名称为参考键的字典,因此可以使用列表推导式轻松提取数据

# 目标函数
prob2 += (
    lpSum([costs[i] * ingredient_vars[i] for i in Ingredients]),
    "Total Cost of Ingredients per can",
)
# 五个限制
prob2 += lpSum([ingredient_vars[i] for i in Ingredients]) == 100, "百分比之和"
prob2 += (
    lpSum([proteinPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 8.0,
    "蛋白质需求",
)
prob2 += (
    lpSum([fatPercent[i] * ingredient_vars[i] for i in Ingredients]) >= 6.0,
    "脂肪需求",
)
prob2 += (
    lpSum([fibrePercent[i] * ingredient_vars[i] for i in Ingredients]) <= 2.0,
    "纤维需求",
)
prob2 += (
    lpSum([saltPercent[i] * ingredient_vars[i] for i in Ingredients]) <= 0.4,
    "盐分需求",
)
# 将问题数据写入.lp文件中
prob2.writeLP('WhiskasModel2.lp')
[Ingr_BEEF, Ingr_CHICKEN, Ingr_GEL, Ingr_MUTTON, Ingr_RICE, Ingr_WHEAT]
prob2.solve(CPLEX())
1
print("求解状态:", LpStatus[prob2.status])
求解状态: Optimal
for v in prob2.variables():
    print(v.name, "=", v.varValue)
Ingr_BEEF = 60.0
Ingr_CHICKEN = 0.0
Ingr_GEL = 40.0
Ingr_MUTTON = 0.0
Ingr_RICE = 0.0
Ingr_WHEAT = 0.0
print("每罐猫粮的原材料成本为:", value(prob2.objective))
每罐猫粮的原材料成本为: 0.52

猜你喜欢

转载自blog.csdn.net/weixin_47692652/article/details/132193424