优化建模语言作为优化求解器与终端用户之间的桥梁,是构建、求解和分析优化模型的重要工具。建模语言的效率直接影响优化模型的构建和求解时间。PyOptInterface是一种基于Python编程语言的优化建模语言,相比现有建模语言兼具高效率和灵活性,在优化建模性能方面实现了10-20倍的显著提升。
一、PyOptInterface简介
PyOptInterface是一个基于Python 的开源通用优化建模语言,由合肥工业大学电气与自动化工程学院杨越副教授、清华大学电机系吴文传教授团队合作开发,代码目前已经在Github开源:https://github.com/metab0t/PyOptInterface,并附有详尽的在线文档网站:https://metab0t.github.io/PyOptInterface/,相关论文已经发布在arXiv预印本平台:https://arxiv.org/abs/2405.10130。
PyOptInterface目前支持求解以下八类优化问题:
-
线性规划 (LP)
-
混合整数线性规划(MILP)
-
二次规划 (QP)
-
混合整数二次规划 (MIQP)
-
二次约束二次规划 (QCQP)
-
混合整数二次约束二次规划 (MIQCQP)
-
二阶锥规划 (SOCP)
-
混合整数二阶锥规划 (MISOCP)
在优化求解器的支持方面,PyOptInterface目前支持调用以下四种性能领先的商业和开源优化求解器:
1. COPT
2. Gurobi
3. HiGHS
4. Mosek
二、实际优化建模中的痛点问题
在优化建模方面,已有AMPL、GAMS、Pyomo、JuMP.jl等兼容多个求解器的通用建模语言,也有COPT、Gurobi等求解器自带的Python编程接口,然而在实际使用中存在以下痛点:
-
AMPL、GAMS等基于领域特定语言(Domain Specific Language,DSL)的优化建模语言学习门槛高,难以和Python等数据分析处理的工作流融合,难以实现高效的模型增量修改和求解。
-
Pyomo等兼容多求解器的建模语言,对大规模优化模型的建模速度不尽如人意,与直接使用求解器的底层接口之间的性能差距显著。
-
COPT、Gurobi等求解器自带的Python编程接口性能较好,然而不同求解器的接口存在差异,导致代码与求解器深度耦合,难以切换求解器。
三、PyOptInterface与其他优化建模语言相比的优势:
PyOptInterface使用了创新性的设计架构,通过建立变量、约束等对象与优化求解器底层接口间的高效映射,最大程度压缩了通用建模语言和使用优化求解器底层接口之间的性能差距,与现有的建模语言相比具有以下几大优势:
-
通过C++实现的内核直接调用优化求解器的底层C接口,构建优化模型的速度非常快,在性能测试中建模速度最接近求解器的C++接口,比Pyomo快10-20倍,比一些优化求解器的官方Python接口快2-5倍,并超越了目前速度最快的开源建模语言JuMP.jl。
-
支持多求解器的模型增量修改和重新求解(包括添加/删除/修改变量或者约束条件、更改目标函数等),无需重新构建模型,适合实现Benders分解、列生成、序列线性化等需要逐次求解模型的算法。
-
提供了涵盖常用功能的统一编程接口,只需编写一份代码即可适用于所有优化求解器。
-
在统一接口的基础上,仍然提供了设置优化求解器特定参数等底层接口和高级功能,比如支持混合整数规划(MIP)中的回调函数(callback function),提供与优化求解器官方Python接口相近的完备功能。
四、建模性能测试
我们选择了八个不同规模的优化模型作为测试案例,分别使用 Gurobi 和 COPT 作为优化器进行了两轮基准测试。针对每个模型,记录了不同建模语言生成模型并提交给优化求解器的总时间,并将优化求解的时间限制设为0.0秒,以避免求解过程的影响。从测试结果可以看出:PyOptInterface与现有的优化建模语言相比具有明显的性能优势。
表1 生成模型并将其提交给 Gurobi 优化求解器的时间(秒)
Model | Variables | C++ | PyOptInterface | JuMP | gurobipy | Pyomo |
fac-25 | 67651 | 0.2 | 0.2 | 0.2 | 1.2 | 4.1 |
fac-50 | 520301 | 0.8 | 1.2 | 1.8 | 9.7 | 32.7 |
fac-75 | 1732951 | 2.7 | 4.1 | 6.6 | 32.5 | 119.3 |
fac-100 | 4080601 | 6.3 | 10.0 | 17.8 | 79.1 | 286.3 |
lqcp-500 | 251501 | 0.9 | 1.5 | 1.3 | 6.3 | 23.8 |
lqcp-1000 | 1003001 | 3.7 | 6.0 | 6.1 | 26.7 | 106.6 |
lqcp-1500 | 2254501 | 8.3 | 14.0 | 17.7 | 61.8 | 234.0 |
lqcp-2000 | 4006001 | 14.5 | 24.9 | 38.3 | 106.9 | 444.1 |
表2 生成模型并将其提交给COPT 优化求解器的时间(秒)
Model | Variables | C++ | PyOptInterface | JuMP | coptpy | Pyomo |
fac-25 | 67651 | 0.3 | 0.2 | 0.3 | 0.6 | 4.1 |
fac-50 | 520301 | 2.2 | 1.5 | 2.7 | 5.4 | 32.8 |
fac-75 | 1732951 | 8.1 | 6.6 | 10.2 | 20.3 | 117.4 |
fac-100 | 4080601 | 22.4 | 23.4 | 30.3 | 58.0 | 284.0 |
lqcp-500 | 251501 | 3.8 | 3.1 | 3.0 | 6.6 | 26.4 |
lqcp-1000 | 1003001 | 16.0 | 15.5 | 13.9 | 28.1 | 112.1 |
lqcp-1500 | 2254501 | 37.6 | 32.4 | 33.7 | 64.6 | 249.3 |
lqcp-2000 | 4006001 | 68.2 | 60.3 | 66.2 | 118.4 | 502.4 |
五、安装使用
PyOptInterface 已经在PyPI上正式发布,支持Windows、Linux和MacOS平台,支持Python 3.8-3.12的各个版本,可以通过直接 pip 安装:
pip install pyoptinterface
安装完成后,可以在 Python 代码中导入该软件包:
import pyoptinterface as poi
PyOptInterface无任何第三方依赖,但是如果需要将它与特定的优化求解器配合使用,需要手动安装相应的优化求解器软件。详情请参考文档地址(https://metab0t.github.io/PyOptInterface/getting_started.html)中的安装配置教程。
PyOptInterface也提供开源求解器HiGHS的打包安装方法:
pip install pyoptinterface[highs]
此命令将同时安装HiGHS优化器的二进制版本,可以与PyOptInterface配合使用。
六、结语
PyOptInterface目前已在电力能源优化等领域试用,欢迎读者在科研等领域积极试用PyOptInterface,并通过以下方式提出宝贵的意见和建议,并引用我们的预印本论文。本开源软件的使用、修改和传播需遵守Mozilla Public License Version 2.0。
Github Issue地址:
https://github.com/metab0t/PyOptInterface/issues
论文链接:https://arxiv.org/abs/2405.10130
联系邮箱:yue.yang@hfut.edu.cn ; linchenhui@tsinghua.edu.cn
附录、入门教程
以一个简单的线性规划问题为例:
import pyoptinterface as poi
from pyoptinterface import copt
model = copt.Model()
x = model.add_variable(lb=0, name="x")
y = model.add_variable(lb=0, ub=10, name="y")
con = model.add_linear_constraint(5*x+4*y, poi.Geq, 19)
model.set_objective(2*x+3*y)
model.optimize()
x_value = model.get_value(x)
y_value = model.get_value(y)
首先声明模型,其次调用model.add_variable函数添加变量,可以设置变量的上下界和名称,随后调用model.add_linear_constraint添加线性约束条件,调用model.set_objective设置目标函数,由于使用了运算符重载,线性表达式可以由变量的四则运算直接构建,最后调用model.optimize求解优化模型,并调用model.get_value查询变量的最优解取值。
如果加入整数变量的约束,则问题转化为混合整数线性规划问题,需要在调用model.add_variable的时候声明x和y是整数变量:
import pyoptinterface as poi
from pyoptinterface import copt
model = copt.Model()
x = model.add_variable(lb=0, domain=poi.VariableDomain.Integer, name="x")
y = model.add_variable(lb=0, ub=10, domain=poi.VariableDomain.Integer, name="y")
con = model.add_linear_constraint(5*x+4*y, poi.Geq, 19)
model.set_objective(2*x+3*y)
model.optimize()
x_value = model.get_value(x)
y_value = model.get_value(y)
如果将目标函数变为二次函数,则问题转化为混合整数二次规划问题,修改目标函数即可:
import pyoptinterface as poi
from pyoptinterface import copt
model = copt.Model()
x = model.add_variable(lb=0, domain=poi.VariableDomain.Integer, name="x")
y = model.add_variable(lb=0, ub=10, domain=poi.VariableDomain.Integer, name="y")
con = model.add_linear_constraint(5*x+4*y, poi.Geq, 19)
model.set_objective(2*x*x+3*y*y)
model.optimize()
x_value = model.get_value(x)
y_value = model.get_value(y)
最后展示一个利用PyOptInterface求解8皇后问题的示例,构建8*8的0-1变量数组,建模为混合整数线性规划问题,利用HiGHS求解器找到一个可行解,并打印最终的棋盘布置。示例中我们用Numpy的多维数组储存PyOptInterface的变量对象,表明了PyOptInterface可以与其他Python生态系统中第三方包无缝配合。
import numpy as np
import pyoptinterface as poi
from pyoptinterface import highs
model = highs.Model()
N = 8
x = np.empty((N, N), dtype=object)
for i in range(N):
for j in range(N):
x[i, j] = model.add_variable(domain=poi.VariableDomain.Binary)
for i in range(N):
# 行列约束
model.add_linear_constraint(poi.quicksum(x[i, :]), poi.Eq, 1.0)
model.add_linear_constraint(poi.quicksum(x[:, i]), poi.Eq, 1.0)
for i in range(-N+1, N):
# 对角线约束
model.add_linear_constraint(poi.quicksum(x.diagonal(i)), poi.Leq, 1.0)
model.add_linear_constraint(poi.quicksum(np.fliplr(x).diagonal(i)), poi.Leq, 1.0)
model.optimize()
get_v = np.vectorize(lambda x: model.get_value(x))
x_value = get_v(x)
print(x_value.astype(int))