如何在 PuLP 中配置求解器
PuLP 用户常见的问题之一是尝试连接到安装在电脑上的求解器。在这里,我们展示了主要概念和确保 PuLP 能够与相关求解器通信的方法。
检查 PuLP 可以访问的求解器
PuLP 有一些辅助函数,允许用户查询哪些求解器可用,并从其名称初始化一个求解器。
import pulp as pl
solver_list = pl.listSolvers()
print(solver_list)
# ['GLPK_CMD', 'PYGLPK', 'CPLEX_CMD', 'CPLEX_PY', 'CPLEX_DLL', 'GUROBI', 'GUROBI_CMD', 'MOSEK', 'XPRESS', 'PULP_CBC_CMD', 'COIN_CMD', 'COINMP_DLL', 'CHOCO_CMD', 'MIPCL_CMD', 'SCIP_CMD']
如果传递了 onlyAvailable=True 参数,PuLP 会列出当前可用的求解器:
import pulp as pl
solver_list = pl.listSolvers(onlyAvailable=True)
print(solver_list)
# ['GLPK_CMD', 'CPLEX_CMD', 'CPLEX_PY', 'GUROBI', 'GUROBI_CMD', 'PULP_CBC_CMD', 'COIN_CMD']
此外,还可以通过使用求解器的名称来获取求解器对象。传递给此函数的任何参数都会传递给构造函数:
import pulp as pl
solver = pl.getSolver('CPLEX_CMD')
solver = pl.getSolver('CPLEX_CMD', timeLimit=10)
在接下来的章节中,我们将解释如何配置一个求解器以便PuLP可以访问。
什么是环境变量
环境变量可能在 其他地方 有更好的解释。为了本文档的目的,它是一个在你的会话期间存储的文本值,允许你配置一些使用它们的应用程序。例如,当你写:
python
在命令行中,它通常会打开一个Python控制台。但是你的电脑是如何知道在哪里找到Python的呢?它知道是因为有一个名为“PATH”的环境变量,它存储了你硬盘上的一系列位置,你的电脑会在这些位置查找与你输入的内容匹配的可执行文件。
它有许多优点,例如在电脑上不留任何痕迹,并且具有相当好的跨平台性,以及其他许多优点。
PuLP 集成(API)到求解器的类型
API 意为“应用程序编程接口”。PuLP 通常有几种方式连接到求解器。根据连接求解器的方式,配置连接可能会有所不同。我们可以将集成总结为两大类:
使用求解器的命令行界面。
使用求解器的Python库。
并非所有求解器都有Python库,但大多数都有命令行接口。如果你想知道自己使用的是哪一个,这很简单。如果求解器API的名称以``CMD``结尾(例如``PULP_CBC_CMD``、CPLEX_CMD
、``GUROBI_CMD``等),那么它是前者。否则,它是后者。
配置求解器路径
为了让 PuLP 能够通过 CMD API 使用求解器,求解器需要通过命令行由 PuLP 执行。为此,需要以下两种情况之一:
用户将求解器的路径传递给求解器初始化。
用户已将 PATH 环境变量配置为求解器所在的目录。
我们将在Windows上为CPLEX做示例,但这个想法对其他求解器和其他操作系统也是一样的。
这两个选项都意味着要知道求解器的位置。所以我们首先要去电脑中查找它。我的在 C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\bin\x64_win64\cplex.exe
。
想象使用 CPLEX_CMD
求解器,第一个非常简单:
path_to_cplex = r'C:\Program Files\IBM\ILOG\CPLEX_Studio128\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)
唯一要做的事情是查找 ‘cplex.exe’ 文件(如果你在 Windows 上,对于 Linux 和 Mac 则查找 ‘cplex’ 文件),并将绝对路径传递给求解器。
第二个稍微麻烦一点,但每台机器只需做一次。你需要配置 PATH
环境变量,使其包含 C:\Program Files\IBM\ILOG\CPLEX_Studio128\cplex\bin\x64_win64
目录的路径。
以下是一个关于编辑环境变量的随机指南:Windows 和 Linux 或 Mac。其理念是,一旦正确配置,你就可以忘记它(直到你更换电脑或求解器版本)。
一旦我们完成了那一步,我们只需做一些与前一个示例非常相似的事情:
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)
唯一的区别是我们不需要告诉 PuLP 求解器的位置。系统会像上面的 python
示例一样,使用 PATH
环境变量找到它。太神奇了!
每个求解器的附加环境变量
有时,仅提供求解器的路径是不够的。这可能是因为求解器需要知道其他文件的位置(运行时将使用的动态库),或者 PuLP API 需要导入一些与求解器一起部署的特定 Python 包(对于那些没有以 _CMD
结尾的求解器)。
无论出于什么原因,安全总比后悔好,这意味着要知道哪些变量通常被哪个求解器使用。以下是每个求解器所需的必要环境变量。该过程与我们使用 PATH
变量的做法非常相似:有时你需要编辑现有的环境变量,有时你需要创建一个新的环境变量。为了看起来明确,我将使用我自己的变量路径,但你将不得不将它们适应到你的实际路径(例如,如果求解器的版本不同)。我将使用我的 Linux 路径,因为这仅仅意味着复制我的 ~.bashrc 文件的最后一行。我已经将它们适应到 Windows 命令行,但最好是通过 Windows 的 GUI 来编辑它们。
CPLEX
Linux / Mac: 将以下行添加到 ~.bashrc(或 ~.profile 或 /etc/profile 或 /etc/bash.bashrc)文件中:
export CPLEX_HOME="/opt/ibm/ILOG/CPLEX_Studio128/cplex"
export CPO_HOME="/opt/ibm/ILOG/CPLEX_Studio128/cpoptimizer"
export PATH="${PATH}:${CPLEX_HOME}/bin/x86-64_linux:${CPO_HOME}/bin/x86-64_linux"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${CPLEX_HOME}/bin/x86-64_linux:${CPO_HOME}/bin/x86-64_linux"
export PYTHONPATH="${PYTHONPATH}:/opt/ibm/ILOG/CPLEX_Studio128/cplex/python/3.5/x86-64_linux"
Windows:添加以下环境变量(通过命令行或图形用户界面):
set CPLEX_HOME=C:/Program Files/IBM/ILOG/CPLEX_Studio128/cplex
set CPO_HOME=C:/Program Files/IBM/ILOG/CPLEX_Studio128/cpoptimizer
set PATH=%PATH%;%CPLEX_HOME%/bin/x64_win64;%CPO_HOME%/bin/x64_win64
set LD_LIBRARY_PATH=%LD_LIBRARY_PATH%;%CPLEX_HOME%/bin/x64_win64;%CPO_HOME%/bin/x64_win64
set PYTHONPATH=%PYTHONPATH%;/opt/ibm/ILOG/CPLEX_Studio128/cplex/python/3.5/x64_win64
GUROBI
Linux / Mac: 将以下行添加到 ~.bashrc(或 ~.profile 或 /etc/profile 或 /etc/bash.bashrc)文件中:
export GUROBI_HOME="/opt/gurobi801/linux64"
export PATH="${PATH}:${GUROBI_HOME}/bin"
export LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${GUROBI_HOME}/lib"
Windows:添加以下环境变量(通过命令行或图形用户界面):
set GUROBI_HOME=/opt/gurobi801/linux64
set PATH=%PATH%;%GUROBI_HOME%/bin
set LD_LIBRARY_PATH=%LD_LIBRARY_PATH%;%GUROBI_HOME%/lib
配置CMD求解器写入临时文件的位置
在使用命令行(再次强调,那些以 CMD
结尾的)的求解器API的情况下,有时用户希望控制文件的写入位置。有很多选项可供选择。
默认情况下,PuLP 不会保留中间文件(*.mps, *.lp, *.mst, *.sol),它们会被写入操作系统的临时目录中。PuLP 会按顺序查找 TEMP、TMP 和 TMPDIR 环境变量来写入文件。使用后,PuLP 会删除这些文件。如果在求解前更改了这些环境变量中的任何一个,您应该能够选择希望 PuLP 将结果写入的位置。
import pulp as pl
model = pl.LpProblem("Example", pl.LpMinimize)
_var = pl.LpVariable('a')
_var2 = pl.LpVariable('a2')
model += _var + _var2 == 1
solver = pl.PULP_CBC_CMD()
result = model.solve(solver)
另一个选项是向求解器传递参数 keepFiles=True。使用此选项,求解器会在当前目录中创建文件,并且这些文件不会被删除(尽管如果你重新执行,它们会被覆盖)。
import pulp as pl
model = pl.LpProblem("Example", pl.LpMinimize)
_var = pl.LpVariable('a')
_var2 = pl.LpVariable('a2')
model += _var + _var2 == 1
solver = pl.PULP_CBC_CMD(keepFiles=True)
result = model.solve(solver)
最后,可以在实际求解之前手动编辑求解器对象的 tmpDir 属性。
import pulp as pl
model = pl.LpProblem("Example", pl.LpMinimize)
_var = pl.LpVariable('a')
_var2 = pl.LpVariable('a2')
model += _var + _var2 == 1
solver = pl.PULP_CBC_CMD()
solver.tmpDir = 'PUT_SOME_ALTERNATIVE_PATH_HERE'
result = model.solve(solver)
使用官方求解器API
PuLP 与以下求解器的官方 Python API 求解器进行了集成:
Mosek (MOSEK)
Gurobi (GUROBI)
Cplex (CPLEX_PY)
Xpress (XPRESS_PY)
HiGHS (HiGHS)
SCIP (SCIP_PY)
XPRESS (XPRESS_PY)
COPT (COPT)
这些API在使用命令行选项时提供了一系列优势:
它们通常更快地初始化一个问题(它们不涉及将文件写入磁盘)。
它们提供了更多的功能和信息(极射线、对偶价格、减少成本)。
安装 CPLEX_PY
要让这个求解器工作,有两种选项:
安装与 CPLEX 安装包一起提供的 Python 包(它不会自动安装)。
(推荐) 将包的路径添加到
PYTHON_PATH
环境变量中。
第二步已经在上述示例中展示。第一步需要管理员权限,第二步则不需要。
安装 GUROBI
要使此求解器工作,唯一的选择是安装与 gurobi 安装一起提供的 python 包。
按照我的安装路径,它将是(Linux):
cd /opt/gurobi801/linux64/
sudo python3 setup.py install
如你所见,安装它需要管理员权限。
使用特定求解器的功能
为了访问此功能,用户需要使用 PuLP 问题中包含的求解器对象。PuLP 使用问题对象上的 solverModel
属性。当执行 buildSolverModel()
方法时,此属性被创建并填充。
例如,使用 CPLEX_PY
API,我们可以在求解完成后访问api对象:
import pulp
x = pulp.LpVariable('x', lowBound=0)
prob = pulp.LpProblem('name', pulp.LpMinimize)
prob += x
solver = pulp.CPLEX_PY()
status = prob.solve(solver)
# you can now access the information from the cplex API python object
prob.solverModel
此外,您可以在求解之前通过使用较低级别的方法访问python api对象:
import pulp
x = pulp.LpVariable('x', lowBound=0)
prob = pulp.LpProblem('name', pulp.LpMinimize)
prob += x
solver = pulp.CPLEX_PY()
solver.buildSolverModel(prob)
# you can now edit the object or do something with it before solving
# for example, load a MIP_START file for CPLEX_PY:
solver.solverModel.MIP_starts.read(SOME_MST_FILE)
# the, you can call the solver to solve the problem
solver.callSolver(prob)
# finally, you fill the PuLP variables with the solution
status = solver.findSolutionValues(prob)
关于如何使用 solverModel 的更多信息,需要根据所使用的求解器查阅官方文档。
导入和导出求解器
导出一个求解器可能有助于备份用于求解模型的配置。
为了导出它,可以将其导出为字典或json文件:
import pulp
solver = pulp.PULP_CBC_CMD()
solver_dict = solver.toDict()
返回的字典结构非常简单:
{'keepFiles': 0,
'mip': True,
'msg': True,
'options': [],
'solver': 'PULP_CBC_CMD',
'timeLimit': None,
'warmStart': False}
也可以直接将其导出到json文件:
solver.toJson("some_file_name.json")
为了导入它,需要执行:
import pulp
solver = pulp.getSolverFromDict(solver_dict)
或从文件中:
import pulp
solver = pulp.getSolverFromJson("some_file_name.json")
对于json,我们使用基础的 json 包。但如果 ujson 可用,我们会使用它,以便导入/导出可以非常快。