跳至主要内容

使用 Pyomo 进行优化:完整分步指南

学习如何使用强大的 Python 库 Pyomo 建模并求解优化问题。探索来自线性与非线性优化的实用示例!
· 15分钟 阅读

使用 AI 探索

优化是各行各业和学科中用于在给定约束下做出最佳决策的基础工具。无论是在供应链中最小化成本、提升能源系统效率,还是为机器学习模型寻找最优参数,优化技术都至关重要。

Python 以其简洁与多样性著称,提供了强大的优化库。其中,Pyomo 作为一个全面而灵活的库脱颖而出,它让用户能够无缝地定义并求解复杂的优化模型。

在本教程中,我们将从零开始探索 Pyomo。内容涵盖从安装与配置求解器,到构建与求解不同类型的优化问题。

线性规划优化问题中可行域、约束与顶点的可视化图像

探索线性规划中的可行解空间。图片作者自制。

什么是 Pyomo?

Pyomo 是一个用 Python 构建并求解优化模型的开源库。它让您以既符合数学规范又对 Python 程序员语法直观的方式定义优化模型。它支持广泛的问题类型,包括:

  1. 线性规划(LP):LP涉及在线性等式与不等式约束下优化线性目标函数。广泛用于资源分配、排程与财务规划问题。
  2. 非线性规划(NLP): NLP处理具有非线性目标函数与非线性约束的优化。常用于工程与经济学中更复杂且非线性的系统。
  3. 混合整数规划(MIP):MIP包含部分变量受限为整数、部分可连续的优化问题。适用于如供应链网络设计或项目排程等离散决策(例如开/关)的场景。
  4. 随机规划:Stochastic programming 处理某些要素存在不确定性并以随机变量建模的问题。常用于金融与供应链管理中在不确定性下优化决策。
  5. 动态优化:Dynamic optimization focuses on optimizing decision variables over time, typically involving dynamically evolving systems. It is used in fields like process control, robotics, and economics to manage time-dependent processes.

Pyomo 的特性

既然更了解 Pyomo 了,让我们回顾一些最重要的特性。 

灵活且可扩展

Pyomo 的灵活性来源于它可用标准 Python 结构来建模复杂关系。它可与多种开源与商业求解器集成,便于解决各类优化问题。

Python 式语法

Pyomo 模型基于 Python 构建,并使用标准 Python 语法编写。对熟悉 Python 的用户而言,上手曲线较为平缓,且可在模型中调用丰富的 Python 库。

强大的社区与文档

Pyomo 拥有强大的用户社区和全面的文档,其中包含示例与教程,可帮助不同水平的用户。

Pyomo 的应用场景

Pyomo 在现实世界中有广泛应用。以下是其中一些:

1. 供应链优化

供应链优化涉及改进物流、管理库存水平与制定高效的生产日程。

这可能包括最小化运输成本、优化仓库选址,或平衡供需。

例如,一家公司可能需要在多个区域满足客户需求,同时最小化运费并维持各配送中心的库存水平。

2. 金融建模

在金融建模中,优化有助于配置资源(如资本),以最大化回报、最小化风险。

这可涉及投资组合优化,即在预算上限、监管要求或风险偏好等约束下,通过资产组合平衡风险与收益。

金融建模确保财务策略与长期目标一致,同时降低潜在风险。

3. 能源系统

能源系统中的优化关注发电、输配与用电的效率最大化。

这可能包括在满足排放上限与适应需求波动的同时,确定能源结构(如可再生与非可再生)的最优组合并最小化燃料成本。

此类优化在电网管理、电厂运行与降低环境影响中扮演核心角色。

4. 机器学习与数据科学

优化是许多机器学习与数据科学任务(如超参数调优与特征选择)的核心。

在超参数调优中,优化算法帮助寻找最佳模型配置以提升预测表现。

另一项关键任务是特征选择,即识别对模型精度贡献最大的特征,从而降低复杂度并提升效率。

现在背景已就绪,让我们动手将 Pyomo 应用于一些示例建模问题!

设置 Pyomo

在开始建模之前,需要先通过安装 Pyomo 并选择一个合适的求解器来完成环境配置。

1. 先决条件

要使用 pyomo,您需要 Python 3.6 或更高版本。Pyomo 可通过 pip 安装。

pip install pyomo

本教程使用的 pyomo 版本为 6.8.0。

import pyomo
print(pyomo.__version__)

输出:

>>> 6.8.0

2. 选择并安装合适的求解器

在优化中,求解器至关重要,它们是为您定义的问题寻找最优解的算法。不同求解器适用于不同问题类型(例如线性、非线性、整数)。Pyomo 是建模工具,实际计算依赖外部求解器。

我们来回顾一些最常见的求解器。

开源求解器

1. GLPK(GNU Linear Programming Kit)

GLPK 是一个用于求解线性规划(LP)和混合整数规划(MIP)问题的常用工具。

它是基础线性优化任务的绝佳选择,广泛用于学术和工业应用。

安装

brew install glpk
  • Linux: 
sudo apt-get install glpk-utils
2. CBC(Coin-or Branch and Cut)

CBC 是一个用于线性规划(LP)和混合整数规划(MIP)问题的开源求解器。

在某些情况下,它较 GLPK 提供更先进的功能与更好的性能,是更复杂优化任务的有力选择。

CBC 可通过 conda 包管理器安装。

conda install -c conda-forge coincbc
3. IPOPT(Interior Point OPTimizer)

IPOPT 是为大规模非线性规划(NLP)问题设计的强大求解器。

它特别擅长处理复杂的非线性模型,是超越线性优化问题的极佳选择。

IPOPT 可通过 conda 包管理器安装。

!conda install -c conda-forge ipopt

商业求解器

1. CPLEX

CPLEX 是最先进的优化求解器之一,能够高效处理线性规划(LP)、混合整数规划(MIP)与二次规划(QP)问题。

需从 IBM 获取许可,但对学术用户免费,是科研与教学的理想选择。

2. Gurobi

Gurobi 是领先的商业求解器,以解决 LP、MIP、QP 与非线性规划(NLP)问题的速度与效率著称。

与 CPLEX 类似,需要许可,但为学术用户提供免费使用。因此,它是高级优化的行业标准工具。

开源 vs. 商业求解器

GLPK 与 CBC 等开源求解器免费,足以满足多数基础优化需求。它们非常适合小规模项目与教育用途。

然而,CPLEX 与 Gurobi 等商业求解器通常在更大、更复杂的问题上表现更佳。这些求解器具备高级功能,包括增强的二次与非线性规划支持,并针对大规模工业应用进行了优化。

虽然开源求解器可处理许多常规优化任务,但在面对复杂且对性能要求很高的场景时,商业求解器往往是更好的选择。

请注意,商业求解器需要许可证,但对学术用户免费。

现在,让我们看看如何在 Pyomo 中配置求解器。本例中我将使用 GLPK。

3. 在 Pyomo 中配置求解器

首先,确保安装后求解器的可执行文件已添加到系统 PATH。

然后,创建一个 Python 脚本并添加以下内容:

from pyomo.environ import SolverFactory
solver = SolverFactory('glpk')

为确认 Pyomo 与求解器均正确安装,我们来求解一个简单测试问题。

测试问题:简单线性规划

目标:最小化 Z=x+y

约束条件:

  • x + 2y ≥ 4
  • x - y ≤ 1
  • x ≥ 0
  • y ≥ 0

该问题是寻找Z(两个变量x与y之和)的尽可能小的值。但x与y必须满足特定条件。

首先,x与两倍的y之和必须至少为 4;其次,x减去y必须小于或等于 1;最后,x与y均需为零或正数(不能为负)。

目标是在满足这些条件的同时,找到使Z尽可能小的x与y的取值。

使用 Pyomo 实现:

import pyomo.environ as pyo
# Create a model
model = pyo.ConcreteModel()
# Define variables
model.x = pyo.Var(within=pyo.NonNegativeReals)
model.y = pyo.Var(within=pyo.NonNegativeReals)
# Define objective
model.obj = pyo.Objective(expr=model.x + model.y, sense=pyo.minimize)
# Define constraints
model.con1 = pyo.Constraint(expr=model.x + 2 * model.y >= 4)
model.con2 = pyo.Constraint(expr=model.x - model.y <= 1)
# Select solver
solver = pyo.SolverFactory('glpk')
# Solve the problem
result = solver.solve(model)
# Display results
print('Status:', result.solver.status)
print('Termination Condition:', result.solver.termination_condition)
print('Optimal x:', pyo.value(model.x))
print('Optimal y:', pyo.value(model.y))
print('Optimal Objective:', pyo.value(model.obj))

如果一切正常,预期输出为:

Status: ok
Termination Condition: optimal
Optimal x: 0.0
Optimal y: 2.0
Optimal Objective: 2.0

让我们逐步解析上述代码:首先,它定义了两个只能取非负值的变量x与y。模型的目标是最小化x与y之和(x + y)。代码将求解器设为 glpk,用于在满足这些约束的同时,寻找使目标最小化的x与y的最优值。

运行后,我们得到变量的最优值为x = 0.0与y = 2.0,从而最小化目标函数Z = x + y。因此目标函数的最小值为2.0,且满足给定约束。

Pyomo 建模基础

理解如何在 Pyomo 中定义优化模型的基本组成部分,是高效设置并求解优化问题的前提。

1. 定义变量

变量代表优化问题中需要做出的决策。在 Pyomo 中,变量是求解器将调整以在满足所有约束的前提下优化目标函数的量。

标量变量

标量变量是不在任何集合上索引的单个变量。要在 Pyomo 中定义标量变量,使用 pyomo.environ 模块中的 Var 类。

from pyomo.environ import Var
model.x = Var()

我们先导入 Var,并通过 Var() 创建变量 x。该变量未指定边界,意味着除非在模型中另行约束,否则它可取任意实数。

添加边界

您可以通过指定边界限制变量取值。边界以元组(lower_bound, upper_bound)形式定义:

from pyomo.environ import Var
model.x = Var(bounds=(0, None))

指定取值域

Pyomo 提供了预定义的取值域,您可用来约束变量类型,例如 NonNegativeReals、Integers 或 Binary:

from pyomo.environ import Var, NonNegativeReals
model.x = Var(domain=NonNegativeReals)

索引变量

当处理多个性质相似的变量(如不同时间段或不同项目的变量)时,使用索引变量更高效。索引变量是在某个集合上定义的变量。

import pyomo.environ as pyo
model.I = pyo.Set(initialize=[1, 2, 3])
model.y = pyo.Var(model.I, domain=pyo.NonNegativeReals)

假设您在建模三种产品的产量。您可以这样定义:

model.Products = pyo.Set(initialize=['A', 'B', 'C'])
model.production = pyo.Var(model.Products, domain=pyo.NonNegativeReals)

此时,model.production['A']、model.production['B'] 与 model.production['C'] 分别表示 A、B、C 三种产品的产量。

2. 定义目标

目标函数是我们试图优化(最小化或最大化)的对象。它定义了模型的目标,如最小化成本或最大化利润,通常以涉及决策变量的数学习式表示。

使用 Objective 类定义:

from pyomo.environ import ConcreteModel, Var, Objective, minimize, maximize, NonNegativeReals
# Create a model
model = ConcreteModel()
# Define variables
model.x = Var(within=NonNegativeReals)
model.y = Var(within=NonNegativeReals)
# Minimization (cost)
model.cost = Objective(expr=2 * model.x + 3 * model.y, sense=minimize)
# When Maximization profit - (can have one objective at a time)
# model.profit = Objective(expr=5 * model.x + 4 * model.y, sense=maximize)

3. 添加约束

约束定义了问题的限制或要求:

from pyomo.environ import Constraint
model.con1 = Constraint(expr=model.x + model.y >= 10)

上述示例使用 Constraint 类在 Pyomo 模型中定义了一个约束。约束 model.con1 指定变量x与y之和必须大于或等于10。

4. 参数化模型

参数是在优化过程中不变化的固定值,用于表示已知量或常数。

它们帮助定义变量与约束之间的关系,通过引入真实数据或假设为模型提供结构:

from pyomo.environ import Param
model.p = Param(initialize=5)

上述代码使用 Param 类在 Pyomo 模型中定义了参数 p,并将其初始化为固定值5。参数 p 随后可在模型中用作在优化过程中不变的常量。

现在,让我们动手完成一个端到端的优化问题!

Pyomo 端到端示例

下面通过一个端到端示例展示如何使用 Pyomo 求解优化问题。我们将建模一个现实场景:某工厂生产两种产品,目标是在机器时间约束下最大化利润。

1. 问题描述

工厂生产两种产品,P1 与 P2。每单位利润为:

  • P1:$40
  • P2:$50

可用机器时间:

  • 机器 A:100 小时
  • 机器 B:80 小时
  • 机器 C:90 小时

每单位所需时间:

产品

机器 A(小时)

机器 B(小时)

机器 C(小时)

P1

1

2

0

P2

2

1

3

目标:最大化利润。

决策变量:

  • x₁:P1 的生产数量。
  • x₂:P2 的生产数量。

2. 数学表述

目标函数:

最大化 Z = 40x₁ + 50x₂

约束条件:

  1. 机器 A 容量:1x₁ + 2x₂ ≤ 100
  2. 机器 B 容量:2x₁ + 1x₂ ≤ 80
  3. 机器 C 容量:3x₂ ≤ 90
  4. 非负性:x₁, x₂ ≥ 0

3. 实现

基于问题的目标与约束,以下是使用 GLPK 的 Python 建模代码。

# Step 1: Import Libraries
import pyomo.environ as pyo
# Step 2: Create a Concrete Model
model = pyo.ConcreteModel()
# Step 3: Define Decision Variables (Units of P1 and P2 to produce)
model.x1 = pyo.Var(within=pyo.NonNegativeReals)
model.x2 = pyo.Var(within=pyo.NonNegativeReals)
# Step 4: Define the Objective Function (Maximize profit)
model.profit = pyo.Objective(expr=40 * model.x1 + 50 * model.x2, sense=pyo.maximize)
# Step 5: Define Constraints
# Machine A capacity constraint: 1x1 + 2x2 <= 100
model.machine_a = pyo.Constraint(expr=1 * model.x1 + 2 * model.x2 <= 100)
# Machine B capacity constraint: 2x1 + 1x2 <= 80
model.machine_b = pyo.Constraint(expr=2 * model.x1 + 1 * model.x2 <= 80)
# Machine C capacity constraint: 3x2 <= 90
model.machine_c = pyo.Constraint(expr=3 * model.x2 <= 90)
# Step 6: Solve the Model using GLPK solver
solver = pyo.SolverFactory('glpk')
result = solver.solve(model)
# Step 7: Analyze Results
# Display Solver Status and Termination Condition
print('Solver Status:', result.solver.status)
print('Termination Condition:', result.solver.termination_condition)
# Get and display the optimal values for x1, x2, and the maximum profit
x1_opt = pyo.value(model.x1)
x2_opt = pyo.value(model.x2)
profit_opt = pyo.value(model.profit)
print(f'Optimal production of P1 (x1): {x1_opt}')
print(f'Optimal production of P2 (x2): {x2_opt}')
print(f'Maximum Profit: ${profit_opt}')

输出:

>>> Solver Status: ok
>>> Termination Condition: optimal
>>> Optimal production of P1 (x1): 25.0
>>> Optimal production of P2 (x2): 30.0
>>> Maximum Profit: $2500.0

在上述代码中,我们定义了一个线性优化模型来最大化生产两种产品(P1 与 P2)的利润。目标函数设为最大化利润,其中每单位 P1 贡献 $40,每单位 P2 贡献 $50。

我们设置了三条约束,表示机器 A、B 与 C 的时间限制。

最后,我们使用 GLPK 求解该问题。

最终答案为生产 25 单位的 P1 与 30 单位的 P2,此时最大利润为 $2,500。

Pyomo 的高级功能

在上一节中,我们看到用 Pyomo 实现端到端优化问题是多么容易。然而,大多数现实问题并不那么直观。

本节将介绍一些可用于解决更复杂场景的高级功能。

1. 非线性优化

非线性优化是在非线性约束下最小化或最大化非线性目标函数。下面看一个在圆形约束下最小化平方差之和的示例。

问题描述

最小化目标: Z = (x - 1)² + (y - 2)²

约束:

  • x² + y² ≤ 4
  • x, y ≥ 0

在 Pyomo 中,我们可以将决策变量x与y设置下界为0以确保非负。目标函数写作相对于特定点的平方差之和,而约束确保解位于半径为2的圆内。

此处可使用 IPOPT 求解器处理非线性优化问题:

import pyomo.environ as pyo
model = pyo.ConcreteModel()
# Define variables with lower bounds
model.x = pyo.Var(bounds=(0, None))
model.y = pyo.Var(bounds=(0, None))
# Objective function: minimize (x - 1)² + (y - 2)²
model.obj = pyo.Objective(expr=(model.x - 1)**2 + (model.y - 2)**2, sense=pyo.minimize)
# Constraint: x² + y² ≤ 4 (circle of radius 2)
model.circle = pyo.Constraint(expr=model.x**2 + model.y**2 <= 4)
solver = pyo.SolverFactory('ipopt')
result = solver.solve(model)
print('Optimal x:', pyo.value(model.x))
print('Optimal y:', pyo.value(model.y))
print('Minimum Z:', pyo.value(model.obj))

2. 混合整数规划(MIP)

当部分决策变量为整数(常为二元变量)、部分为连续变量时使用混合整数规划。它在设施选址、生产计划等决策问题中非常有用。

问题描述

某公司需决定是否在地点 A、B 与 C 开设仓库。目标是最小化总成本,包括开仓固定成本与运输成本。

我们先初始化数据,包括开仓固定成本、运输成本、容量上限与总需求:

locations = ['A', 'B', 'C']
FixedCost = {'A': 1000, 'B': 1200, 'C': 1500}
TransportCost = {'A': 5, 'B': 4, 'C': 6}
Capacity = {'A': 100, 'B': 80, 'C': 90}
Demand = 150
model = pyo.ConcreteModel()
# Binary variable: 1 if warehouse is open, 0 otherwise
model.y = pyo.Var(locations, domain=pyo.Binary)
# Continuous variable: amount of goods transported
model.x = pyo.Var(locations, domain=pyo.NonNegativeReals)
model.cost = pyo.Objective(
    expr=sum(FixedCost[i] * model.y[i] + TransportCost[i] * model.x[i] for i in locations),
    sense=pyo.minimize
)
# Demand constraint
model.demand = pyo.Constraint(expr=sum(model.x[i] for i in locations) >= Demand)
# Capacity constraints
def capacity_rule(model, i):
    return model.x[i] <= Capacity[i] * model.y[i]
model.capacity = pyo.Constraint(locations, rule=capacity_rule)
solver = pyo.SolverFactory('cbc')
result = solver.solve(model)
for i in locations:
    print(f"Warehouse {i}: Open={pyo.value(model.y[i])}, Transported={pyo.value(model.x[i])}")
print('Minimum Total Cost:', pyo.value(model.cost))

模型包含两类决策变量:二元变量 y 表示某仓库是否开设(开为 1,否则为 0),以及连续变量 x 表示从各仓库运输的货物量。

目标函数对各仓库的固定成本与运输成本求和,并最小化总成本。约束确保满足总需求,且若仓库开启,其运输量不超过容量。

3. 处理多目标

有时,优化问题涉及可能互相冲突的多个目标,例如在最大化利润的同时最小化环境影响。常用方法是加权和法,即为每个目标分配权重以平衡其重要性。

问题描述

我们希望在最大化利润的同时最小化环境影响:

  • 利润:Z₁ = 3x + 5y
  • 环境影响:Z₂ = 2x + y

我们可以使用权重 w1=0.6、w2=0.4 将上述目标合并,得到加权和目标:

w1 = 0.6
w2 = 0.4
model.obj = pyo.Objective(
    expr=w1 * (3 * model.x + 5 * model.y) - w2 * (2 * model.x + model.y),
    sense=pyo.maximize
)

在该合并目标中,我们通过调整权重,在最大化利润的同时最小化环境影响。

4. 使用外部数据源

在处理大规模数据集时,常需要从 CSV 等外部源导入数据。Pyomo 与 Pandas 的配合良好,可用于读取与使用外部数据。

我们可以用 Pandas 读取 CSV 文件,并用数据来初始化模型中的集合与参数:

import pandas as pd
data = pd.read_csv('parameters.csv')
# Define set from CSV data
model.I = pyo.Set(initialize=data['index'].unique())
# Define parameter initialized from CSV data
param_dict = data.set_index('index')['value'].to_dict()
model.param = pyo.Param(model.I, initialize=param_dict)

Pyomo 使用技巧与最佳实践

使用 Pyomo 时,保持模型高效、文档清晰、易于排错非常重要。

1. 调试与排错

在 Pyomo 中构建优化模型时,常会遇到如不可行解、求解器失败或结果不正确等问题。以下是一些调试最佳实践:

  • 检查约束: 若模型无法得到可行解,请回顾您的约束。过紧的约束会导致问题不可行。使用 Pyomo 的 .display() 方法打印变量与约束的取值,验证其行为是否符合预期。
  • 查看求解器输出: 在调用 solve() 方法时传入 tee=True 以启用详细日志。这能帮助定位求解困难的原因,如变量无界或不可行等。
  • 先测试简化模型: 面对复杂模型时,先测试一个简化版本,有助于在不引入完整复杂度的前提下定位问题。

系统性地进行排错——逐一分析约束、目标函数与求解器反馈——会更高效。

2. 建模效率

随着模型规模增大,优化问题可能会变得计算开销很大。为保证建模高效,请考虑以下建议:

  • 利用稀疏性: 在定义约束或目标时避免对不必要的索引进行循环。利用问题的稀疏结构可降低计算时间。
  • 二元 vs. 连续变量: 如有可能,减少二元或整数变量的数量。连续变量更易于求解器处理,通常能更快求解。
  • 约束表述: 在数学形式与实现上尽量保持简洁。避免不必要的非线性,将复杂约束拆分为更小、易管理的部分。

高效的模型求解更快,也更易于调试与维护。

3. 文档与维护

为长期使用与协作,保持良好文档化的 Pyomo 模型是好习惯。良好的文档也使后续回顾与更新模型更容易:

  • 使用行内注释: 始终为变量、约束与目标函数添加注释说明其目的。优化模型的逻辑不一定直观,注释尤为重要。
  • 模块化代码: 将模型拆分为逻辑模块,甚至独立函数。这种模块化方法可提升可读性,并便于调试与修改特定部分。
  • 跟踪模型变更: 若模型在演进,请保留版本历史。使用 Git 等版本控制工具跟踪变更,确保任何更新或改进可追溯。

良好的文档与结构化代码能让您的 Pyomo 模型更易于被后续协作者理解,并能随着需求演进更易扩展或修改。

结论

Pyomo 是一个强大而灵活的 Python 优化建模与求解工具。在本教程中,我们探索了 Pyomo 如何让用户建模多种优化问题,从线性规划到非线性与混合整数规划。

借助其友好的语法与对求解器的集成,Pyomo 让初学者与高级用户都能便捷地构建并求解真实世界的优化问题。

如果您想进一步学习如何用优化解决真实问题,欢迎查看 DataCamp 的免费课程 Introduction to Optimization in Python!

FAQs

Pyomo 有什么用?

Pyomo 是一个开源的 Python 库,用于定义并求解复杂的优化问题,包括线性、非线性和混合整数规划。

如何安装 Pyomo 及其求解器?

您可以使用 pip install pyomo 安装 Pyomo。GLPK 和 CBC 等求解器可通过 Homebrew、apt-get 或 Conda 等包管理器安装。

Pyomo 能解决哪些类型的优化问题?

Pyomo 支持广泛的优化问题类型,包括线性规划(LP)、非线性规划(NLP)、混合整数规划(MIP)与随机规划。

我能将 Pyomo 用于供应链优化等真实业务吗?

可以。Pyomo 被广泛用于现实场景,例如供应链优化、金融建模、能源系统优化与机器学习任务。

Pyomo 中开源求解器与商业求解器有何区别?

开源求解器(如 GLPK 与 CBC)免费且能解决许多问题;而商业求解器(如 Gurobi 与 CPLEX)在更复杂、大规模的优化任务上提供更强性能与高级功能。

Moez Ali

作者

Moez Ali

数据科学家,PyCaret 的创始人兼作者

主题