在数据科学与工程领域,数学优化是解决资源分配、路径规划、成本最小化等问题的核心工具。而用代码高效表达优化模型,往往是落地的关键。如果你熟悉 Python,那么 Pyomo—— 这款由美国国家可再生能源实验室(NREL)主导开发的优化建模库,或许能成为你的得力助手。本文将基于《Pyomo–Optimization Modeling in Python》(Bynum et al., 2021),带你从基础到实战,掌握 Pyomo 的核心用法。
一、Pyomo 是什么?核心价值与生态
Pyomo 全称为 “Python Optimization Modeling Objects”,是一款开源的 Python 优化建模库,其核心是通过面向对象设计实现数学优化模型的灵活构建(Bynum et al., 2021)。与传统的商业代数建模语言(AML)如 AMPL、GAMS 相比,Pyomo 的优势在于:
- Python 原生兼容:无需学习新语言,直接用 Python 语法(列表、字典、函数)定义模型,降低学习成本;
- 多求解器支持:可调用 GLPK(线性规划)、IPOPT(非线性规划)、Gurobi(商业求解器)等,甚至通过 NEOS 平台使用远程求解服务(Bynum et al., 2021);
- AML 能力:支持用数学表达式直观描述目标函数与约束,媲美专业 AML 工具;
- 灵活性:适用于线性、非线性、整数规划等多种问题,支持动态参数调整与复杂数据导入(如 Excel、CSV)。
值得一提的是,Pyomo 并非孤例 —— 同类工具如 C++ 的 FlopC++、Java 的 OptimJ、Julia 的 JuMP,均采用 “编程语言 + 优化建模” 的模式,但 Pyomo 凭借 Python 的生态优势(如 Pandas 数据处理、Matplotlib 可视化),在易用性上更胜一筹(Bynum et al., 2021)。
二、Pyomo 核心:模型类型与关键组件
Pyomo 的核心是 “模型” 与 “组件”—— 模型是容器,组件则是构成优化问题的基本单元。我们先从两种核心模型类型说起,再拆解五大关键组件。
1. 两种模型类型:Concrete vs Abstract
Pyomo 支持两种模型定义方式,核心区别在于数据与模型结构的绑定时机(Bynum et al., 2021):
(1)ConcreteModel:数据先行,即时构建
适用于数据已知的场景,模型结构与数据同步定义,用 Python 原生数据结构(列表、字典)传入参数。
示例:简单线性规划
目标函数:\(\min\ x_1 + 2x_2\)
约束条件:
\(3x_1 + 4x_2 \geq 1\)
\(2x_1 + 5x_2 \geq 2\)
\(x_1, x_2 \geq 0\)
代码实现(修正文档中的语法错误后):
import pyomo.environ as pyo
\# 1. 初始化ConcreteModel
model = pyo.ConcreteModel(name="SimpleLP")
\# 2. 定义变量(Var):非负实数
model.x1 = pyo.Var(within=pyo.NonNegativeReals)
model.x2 = pyo.Var(within=pyo.NonNegativeReals)
\# 3. 定义目标函数(Objective):最小化
model.obj = pyo.Objective(expr=model.x1 + 2 \* model.x2, sense=pyo.minimize)
\# 4. 定义约束(Constraint)
model.con1 = pyo.Constraint(expr=3 \* model.x1 + 4 \* model.x2 >= 1)
model.con2 = pyo.Constraint(expr=2 \* model.x1 + 5 \* model.x2 >= 2)
\# 5. 求解(调用GLPK求解器)
solver = pyo.SolverFactory("glpk")
result = solver.solve(model)
\# 6. 验证结果与输出
pyo.assert\_optimal\_termination(result) # 确保求解成功
print("最优解:")
print(f"x1 = {pyo.value(model.x1)}")
print(f"x2 = {pyo.value(model.x2)}")
print(f"目标函数值 = {pyo.value(model.obj)}")
(2)AbstractModel:先定结构,后传数据
适用于数据未知或需多次替换数据的场景(如批量求解不同参数的问题)。模型结构先通过 Set(集合)和 Param(参数)抽象定义,数据后续传入(Bynum et al., 2021)。
示例:参数化线性规划
目标函数:\(\min\ \sum_{i \in N} c_i x_i\)
约束条件:\(\sum_{i \in N} a_{ji} x_i \geq b_j\ (\forall j \in M)\),\(x_i \geq 0\)
代码实现:
import pyomo.environ as pyo
\# 1. 初始化AbstractModel(仅定义结构,无数据)
model = pyo.AbstractModel(name="ParametricLP")
\# 2. 定义集合(Set):索引容器
model.N = pyo.Set() # 变量索引集合(如i=1,2)
model.M = pyo.Set() # 约束索引集合(如j=1,2)
\# 3. 定义参数(Param):模型的输入数据
model.c = pyo.Param(model.N) # 目标函数系数(对应c\_i)
model.a = pyo.Param(model.M, model.N) # 约束系数(对应a\_ji)
model.b = pyo.Param(model.M) # 约束右侧值(对应b\_j)
\# 4. 定义变量与目标函数(依赖Set/Param,无需具体数据)
model.x = pyo.Var(model.N, within=pyo.NonNegativeReals)
def obj\_rule(mdl):
return sum(mdl.c\[i] \* mdl.x\[i] for i in mdl.N) # 列表推导式(Pyomo推荐写法)
model.obj = pyo.Objective(rule=obj\_rule, sense=pyo.minimize)
\# 5. 定义约束(用rule函数复用逻辑)
def con\_rule(mdl, j): # j为约束索引,来自Set M
return sum(mdl.a\[j, i] \* mdl.x\[i] for i in mdl.N) >= mdl.b\[j]
model.con = pyo.Constraint(model.M, rule=con\_rule)
\# 6. 传入数据并求解(示例数据)
data = {
"N": {None: \[1, 2]}, # None表示Set无父索引
"M": {None: \[1, 2]},
"c": {None: {1: 1, 2: 2}},
"a": {None: {(1,1): 3, (1,2): 4, (2,1): 2, (2,2): 5}},
"b": {None: {1: 1, 2: 2}}
}
instance = model.create\_instance(data) # 绑定数据生成实例
\# 7. 求解与输出
solver = pyo.SolverFactory("glpk")
result = solver.solve(instance)
pyo.assert\_optimal\_termination(result)
instance.x.pprint() # 打印所有变量的最优解
2. 五大核心组件详解
无论是 Concrete 还是 Abstract 模型,都依赖以下五大组件构建(Bynum et al., 2021):
| 组件 | 作用 | 关键参数 / 用法 |
|---|---|---|
Var |
优化变量 | within(类型:NonNegativeReals、Binary 等)、bounds(边界) |
Objective |
目标函数 | expr(直接表达式)、rule(函数逻辑)、sense(min/max) |
Constraint |
约束条件 | expr(表达式)、rule(函数复用)、支持 ==/<=/>= |
Set |
索引集合(管理变量 / 约束的分组) | initialize(初始化数据)、支持嵌套(如 Set(model.N)) |
Param |
模型参数(固定输入数据) | initialize(初始化)、mutable=True(可动态修改) |
关键技巧:当变量 / 约束数量较多时,用 Set索引定义(如 model.x = pyo.Var(model.N, model.M)),避免逐个声明(如 model.x1、model.x2),大幅简化代码(Bynum et al., 2021)。
三、实战:仓库选址问题(p-median 问题)
理论之后,我们用 Pyomo 解决一个经典优化问题 ——仓库选址,该问题属于 p-median 问题,目标是在候选地点中选 P 个仓库,最小化总运输成本(Bynum et al., 2021)。
1. 问题描述与建模
输入数据:
- 候选仓库集合
N:Harlingen、Memphis、Ashland; - 客户集合
M:NYC、LA、Chicago、Houston; - 运输成本
d[n,m]:仓库 n 到客户 m 的单位成本(如 Harlingen 到 NYC 为 1956); - 仓库数量限制
P=2。
- 候选仓库集合
变量定义:
x[n,m]:客户 m 由仓库 n 服务的比例(0≤x≤1);y[n]:是否选择仓库 n(二进制变量:1 = 选,0 = 不选)。
数学模型:
\(\text{s.t.}\begin{matrix} \min & \sum_{n \in N} \sum_{m \in M} d_{n,m} x_{n,m} \\ & \sum_{n \in N} x_{n,m} = 1\ (\forall m \in M) \quad \text{(客户需求全满足)} \\ & x_{n,m} \leq y_{n}\ (\forall n \in N, m \in M) \quad \text{(未选仓库不服务)} \\ & \sum_{n \in N} y_{n} \leq P \quad \text{(仓库数量限制)} \\ & 0 \leq x_{n,m} \leq 1,\ y_n \in \{0,1\} \end{matrix}\)
2. 代码实现:从 Excel 导入数据
实际项目中,数据常存储在 Excel 中。我们用 pandas读取数据,再传入 Pyomo 模型(Bynum et al., 2021):
步骤 1:Excel 数据格式(示例)
| NYC | LA | Chicago | Houston | |
|---|---|---|---|---|
| Harlingen | 1956 | 1606 | 1410 | 330 |
| Memphis | 1096 | 1792 | 531 | 567 |
| Ashland | 485 | 2322 | 324 | 1236 |
步骤 2:Pyomo 代码
import pyomo.environ as pyo
import pandas as pd
\# 1. 从Excel读取数据
df = pd.read\_excel("wl\_data.xlsx", sheet\_name="Delivery Costs", header=0, index\_col=0)
N = list(df.index.map(str)) # 候选仓库集合
M = list(df.columns.map(str)) # 客户集合
d = {(r, c): df.at\[r, c] for r in N for c in M} # 运输成本字典
P = 2 # 仓库数量限制
\# 2. 定义ConcreteModel
def create\_warehouse\_model(N, M, d, P):
model = pyo.ConcreteModel(name="WarehouseLocation")
\# 变量:x\[n,m](服务比例)、y\[n](是否选仓库)
model.x = pyo.Var(N, M, bounds=(0, 1))
model.y = pyo.Var(N, within=pyo.Binary)
\# 目标函数:最小化总运输成本
def obj\_rule(mdl):
return sum(d\[n, m] \* mdl.x\[n, m] for n in N for m in M)
model.obj = pyo.Objective(rule=obj\_rule, sense=pyo.minimize)
\# 约束1:每个客户需求全满足
def demand\_rule(mdl, m):
return sum(mdl.x\[n, m] for n in N) == 1
model.demand = pyo.Constraint(M, rule=demand\_rule)
\# 约束2:未选仓库不服务
def warehouse\_active\_rule(mdl, n, m):
return mdl.x\[n, m] <= mdl.y\[n]
model.warehouse\_active = pyo.Constraint(N, M, rule=warehouse\_active\_rule)
\# 约束3:仓库数量限制(用mutable Param支持动态修改)
model.P = pyo.Param(initialize=P, mutable=True)
def num\_warehouses\_rule(mdl):
return sum(mdl.y\[n] for n in N) <= mdl.P
model.num\_warehouses = pyo.Constraint(rule=num\_warehouses\_rule)
return model
\# 3. 求解与结果输出
model = create\_warehouse\_model(N, M, d, P)
solver = pyo.SolverFactory("glpk")
result = solver.solve(model)
\# 验证最优解并打印仓库选择结果
pyo.assert\_optimal\_termination(result)
print("最优仓库选择(y\[n]=1表示选中):")
model.y.pprint()
print(f"最小总运输成本:{pyo.value(model.obj)}")
3. 进阶:动态调整参数 P
若需分析 “仓库数量 P 对成本的影响”,可利用 mutable=True的 Param,无需重新构建模型:
\# 循环测试P=1到5的情况
for p in range(1, 6):
model.P = p # 动态修改仓库数量限制
result = solver.solve(model)
pyo.assert\_optimal\_termination(result)
print(f"P={p}时,最小成本:{pyo.value(model.obj)}")
四、求解器与结果验证
Pyomo 本身不实现求解逻辑,而是调用外部求解器。选择合适的求解器是成功的关键(Bynum et al., 2021):
| 求解器 | 适用问题类型 | 特点 |
|---|---|---|
| GLPK | 线性规划(LP)、整数规划(IP) | 开源免费,轻量 |
| IPOPT | 非线性规划(NLP) | 开源,支持二次目标函数 |
| Gurobi | 线性、整数、非线性规划 | 商业求解器,速度快,支持大规模问题 |
结果验证:用 pyo.assert_optimal_termination(result)确保求解成功(如无可行解或超时会报错);用 model.Var.pprint()或 pyo.value(变量)查看结果。
五、总结与学习资源
Pyomo 的核心优势在于用 Python 的灵活性实现专业的优化建模,无论是简单的线性规划,还是复杂的整数 / 非线性问题,都能通过清晰的组件化设计高效实现。对于学生、研究者或工程师,它是连接数学优化理论与实际问题的桥梁(Bynum et al., 2021)。
其他学习资源
- 核心教材:Bynum, M. L., et al. (2021). Pyomo–Optimization Modeling in Python (3rd ed.). Springer.(书中 90 页前为基础知识,第八章 “Block” 为进阶内容);
- 代码仓库:jckantor/ND-Pyomo-Cookbook(GitHub:https://github.com/jckantor/ND-Pyomo-Cookbook),提供大量实战案例;
- 官方示例:Pyomo Gallery(NbViewer:https://nbviewer.org/github/Pyomo/PyomoGallery),含运输问题、调度问题等代码;
- 求解器文档:IPOPT(https://github.com/coin-or/Ipopt),开源非线性求解器的安装与使用指南。
参考文献
Bynum, M. L., Hackebeil, G. A., Hart, W. E., Laird, C. D., Nicholson, B. L., Siirola, J. D., Watson, J.-P., & Woodruff, D. L. (2021). Pyomo–Optimization modeling in Python (3rd ed., Vol. 67). Springer Science & Business Media.
Kallrath, J. (2012). Modeling Languages in Mathematical Optimization. Berlin, Heidelberg: Springer.
Williams, H. P. (1978). Model Building in Mathematical Programming. New York: Wiley.
jckantor. (2025). ND-Pyomo-Cookbook. GitHub. https://github.com/jckantor/ND-Pyomo-Cookbook
Pyomo. (2025). Transport problem. NbViewer. https://nbviewer.org/github/Pyomo/PyomoGallery/blob/master/transport/transport.ipynb
coin-or. (2025). Ipopt. GitHub. https://github.com/coin-or/Ipopt