如何在 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 执行。为此,需要以下两种情况之一:

  1. 用户将求解器的路径传递给求解器初始化。

  2. 用户已将 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 目录的路径。

以下是一个关于编辑环境变量的随机指南:WindowsLinux 或 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

要让这个求解器工作,有两种选项:

  1. 安装与 CPLEX 安装包一起提供的 Python 包(它不会自动安装)。

  2. (推荐) 将包的路径添加到 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 可用,我们会使用它,以便导入/导出可以非常快。