Python API 概览#
全局函数#
为了方便通过Python解释器交互式地使用Gurobi,我们提供了一组全局函数。其中最重要的函数可能是read
函数,它允许您从文件中读取并求解模型。另一个有用的全局函数是disposeDefaultEnv
,它用于释放默认环境。其他全局函数允许您读取、修改或写入Gurobi参数(readParams
、setParam
和writeParams
)。
模型#
Most actions in the Gurobi Python interface are performed by calling
methods on Gurobi objects. The most commonly used object is the
Model
. A model consists of a set of decision variables
(objects of class Var
or MVar
), a linear or
quadratic objective function on these variables (specified using
Model.setObjective
), and a set of constraints on these
variables (objects of class Constr
, MConstr
,
QConstr
, MQConstr
, SOS
,
GenConstr
, or MGenConstr
). Each variable has an
associated lower bound, upper bound, and type (continuous, binary,
etc.). Each linear or quadratic constraint has an associated sense
(less-than-or-equal, greater-than-or-equal, or equal), and right-hand
side value. Refer to 本节
for more information on variables, constraints, and objectives.
优化模型可以一次性指定,通过从文件加载模型(使用前面提到的read
函数),也可以逐步构建,首先构造一个Model
类的空对象,然后随后调用Model.addVar
、Model.addVars
或Model.addMVar
来添加额外的变量,以及Model.addConstr
、Model.addConstrs
、Model.addLConstr
、Model.addQConstr
、Model.addSOS
或任何Model.addGenConstr*
方法来添加额外的约束。请参阅构建模型以获取一般指导,或参阅mip1_remote.py以获取具体示例。
线性约束通过构建线性表达式(类LinExpr
或MLinExpr
的对象)来指定,然后指定这些表达式之间的关系(例如,要求一个表达式等于另一个表达式)。二次约束以类似的方式构建,但使用二次表达式(类QuadExpr
或MQuadExpr
的对象)代替。一般约束使用一组专用方法或一组一般约束辅助函数加上重载运算符来构建。
非线性约束通过构建非线性表达式(类NLExpr
或MNLExpr
的对象)并指定一个选择的结果变量来指定,该变量取表达式的值。然后,该结果变量可以参与其他约束。在Python API中,非线性表达式是通过运算符重载构建的。通常,调用任何非线性函数,或创建任何在模型变量方面不是严格线性或二次的表达式都会产生非线性表达式。
模型是动态实体;您可以随时添加或删除变量或约束。
我们经常提到优化模型的类别。在最高层次上,模型可以是连续的或离散的,这取决于模型中存在的建模元素是否需要做出离散决策。在连续模型中…
具有线性目标函数、线性约束和连续变量的模型称为线性规划(LP)。
如果目标是二次的,模型就是二次规划(QP)。
如果任何约束是二次的,模型就是二次约束规划(QCP)。我们有时会提到QCP的一些特殊情况:具有凸约束的QCP、具有非凸约束的QCP、双线性规划,以及二阶锥规划(SOCP)。
如果任何约束是非线性的(从可用的通用约束中选择),则该模型是一个非线性规划(NLP)。
一个包含任何整数变量、半连续变量、半整数变量、特殊有序集(SOS)约束或一般约束的模型是离散的,被称为混合整数规划(MIP)。MIP的特殊情况,即我们已经描述的连续模型类型的离散版本,是…
混合整数线性规划 (MILP)
混合整数二次规划 (MIQP)
混合整数二次约束规划 (MIQCP)
混合整数二阶锥规划 (MISOCP)
混合整数非线性规划 (MINLP)
Gurobi优化器处理所有这些模型类别。请注意,它们之间的界限并不像人们希望的那样清晰,因为我们通常能够将一个模型从一个类别转换为一个更简单的类别。
环境#
在Gurobi Python接口中,环境的作用比其他语言API中要小得多,主要是因为Python接口有一个默认环境。除非你显式地将自己的环境传递给需要环境的例程,否则将使用默认环境。
您可能希望创建自己的环境的主要情况是当您希望精确控制与环境相关的资源(特别是许可证令牌或计算服务器)何时释放时。如果您使用自己的环境来创建模型(使用read
或Model
构造函数),那么一旦您的程序不再引用您的环境或使用该环境创建的任何模型,与环境相关的资源将被释放。
请注意,你可以通过调用disposeDefaultEnv
手动移除对默认环境的引用,从而使其可用于垃圾回收。调用此方法后,并且在默认环境中构建的所有模型都被垃圾回收后,默认环境也将被垃圾回收。如果你调用一个需要默认环境的例程,新的默认环境将自动创建。
对于更高级的用例,您可以使用空环境来创建一个未初始化的环境,然后通过编程方式为您的特定需求设置所有必需的选项。更多详细信息,请参阅环境部分。
解决模型#
一旦你构建了一个模型,你可以调用Model.optimize
来计算一个解决方案。默认情况下,optimize
将使用并发优化器来解决LP模型,使用屏障算法来解决具有凸目标的QP模型和具有凸约束的QCP模型,否则使用分支切割算法。解决方案存储在模型的一组属性中,这些属性可以随后查询(我们将很快回到这个话题)。
Gurobi算法会仔细跟踪模型的状态,因此只有在自上次优化以来相关数据发生变化时,调用Model.optimize
才会执行进一步的优化。如果您希望丢弃先前计算的解决方案信息并在不更改模型的情况下从头开始重新启动优化,您可以调用Model.reset
。
在解决了一个MIP模型之后,你可以调用Model.fixed
来计算相关的固定模型。这个模型与原始模型相同,除了整数变量被固定为它们在MIP解中的值。如果你的模型包含SOS约束,这些约束中出现的一些连续变量也可能被固定。在某些应用中,计算这个固定模型上的信息(例如,对偶变量、敏感性信息等)可能是有用的,尽管你应该小心解释这些信息。
多种解决方案、目标和场景#
默认情况下,Gurobi优化器假设您的目标是找到一个经过验证的最优解,针对具有单一目标函数的单一模型。Gurobi提供了以下功能,允许您放宽这些假设:
解决方案池: 允许你找到更多的解决方案(参考示例 poolsearch.py)。
多场景: 允许您找到多个相关模型的解决方案(参考示例 multiscenario.py)。
多目标: 允许您指定多个目标函数并控制它们之间的权衡 (参考示例 multiobj.py)。
不可行模型#
如果发现模型不可行,您有几个选择。您可以尝试诊断不可行的原因,尝试修复不可行性,或者两者都做。为了获取有助于诊断不可行原因的信息,可以调用Model.computeIIS
来计算一个不可约不一致子系统(IIS)。此方法可用于连续和MIP模型,但您应该注意,MIP版本可能会非常昂贵。此方法填充了一组IIS属性。
为了尝试修复不可行性,调用Model.feasRelaxS
或Model.feasRelax
来计算模型的可行性松弛。这种松弛允许你找到一个最小化约束违反程度的解决方案。你可以在Relaxing for feasibility部分找到关于此功能的更多信息。示例在Diagnose and cope with infeasibility中讨论。
查询和修改属性#
与Gurobi模型相关的大部分信息存储在一组属性中。一些属性与模型的变量相关,一些与模型的约束相关,还有一些与模型本身相关。举一个简单的例子,求解优化模型会导致x
变量属性被填充。由Gurobi优化器计算的属性(如x
)不能由用户直接修改,而其他属性(如变量下界(lb
属性))则可以。
Attributes can be accessed in two ways in the Python interface. The
first is to use the getAttr()
and setAttr()
methods, which are
available on variables (Var.getAttr
/ Var.setAttr
),
matrix variables (MVar.getAttr
/ MVar.setAttr
),
linear constraints (Constr.getAttr
/
Constr.setAttr
), matrix constraints
(MConstr.getAttr
/ MConstr.setAttr
), quadratic
constraints (QConstr.getAttr
/ QConstr.setAttr
),
matrix constraints (MQConstr.getAttr
/
MQConstr.setAttr
), SOSs (SOS.getAttr
), general
constraints (GenConstr.getAttr
/
GenConstr.setAttr
), and models (Model.getAttr
/
Model.setAttr
). These are called with the attribute name as
the first argument (e.g., var.getAttr("x")
or
constr.setAttr("rhs", 0.0)
). The full list of available attributes
can be found in the 属性 section of this
manual.
属性也可以更直接地访问:你可以在对象名称后面加上一个句点,然后是该对象的属性名称。请注意,在引用属性时,大小写是被忽略的。因此,b = constr.rhs
等同于 b = constr.getAttr("rhs")
,而 constr.rhs = 0.0
等同于 constr.setAttr("rhs", 0.0)
。
额外的模型修改信息#
对现有模型的大多数修改都是通过属性接口完成的(例如,变量边界、约束右侧等的更改)。主要的例外是对约束矩阵和目标函数的修改。
约束矩阵可以通过几种方式进行修改。第一种是调用Model.chgCoeff
方法。此方法可用于修改现有非零值、将现有非零值设置为零或创建新的非零值。当您从模型中删除变量或约束时(通过Model.remove
方法),约束矩阵也会被修改。与被删除的约束或变量相关的非零值会与约束或变量本身一起被删除。
模型的目标函数也可以通过几种方式进行修改。最简单的方法是构建一个表达式来捕获目标函数(一个LinExpr
、MLinExpr
、QuadExpr
或MQuadExpr
对象),然后将该表达式传递给方法Model.setObjective
。如果您希望修改目标函数,您可以简单地再次调用Model.setObjective
,并传入一个新的LinExpr
或QuadExpr
对象。
对于线性目标函数,除了使用Model.setObjective
之外,还可以使用Obj变量属性来修改单个线性目标系数。
如果你的变量具有分段线性目标,你可以使用Model.setPWLObj
方法来指定它们。对每个相关变量调用此方法一次。Gurobi单纯形求解器包含对凸分段线性目标函数的算法支持,因此对于连续模型,你应该会看到使用此功能的显著性能提升。要清除先前指定的分段线性目标函数,只需将相应变量的Obj属性设置为0。
一些例子在 修改模型中讨论。
延迟更新#
关于Gurobi优化器中的模型修改,一个需要注意的重要事项是,它是以惰性方式执行的,这意味着修改不会立即影响模型。相反,它们会被排队并在稍后应用。如果你的程序只是创建一个模型并解决它,你可能永远不会注意到这种行为。然而,如果你在修改应用之前询问有关模型的信息,惰性更新方法的细节可能对你很重要。
正如我们刚才提到的,模型修改(边界变化、右侧变化、目标变化等)被放入队列中。这些排队的修改可以通过三种不同的方式应用到模型中。第一种是通过显式调用Model.update
。第二种是通过调用Model.optimize
。第三种是通过调用Model.write
来写出模型。第一种情况让你可以精细控制何时应用修改。第二种和第三种情况假设你希望在优化模型或将其写入磁盘之前应用所有待处理的修改。
为什么Gurobi接口会以这种方式运行?有几个原因。首先,这种方法使得对模型进行多次修改变得更加容易,因为模型在修改之间保持不变。其次,处理模型修改可能会非常昂贵,特别是在计算服务器环境中,修改需要机器之间的通信。因此,了解这些修改何时应用是非常有用的。一般来说,如果你的程序需要对模型进行多次修改,你应该分阶段进行,即进行一组修改,然后更新,再进行更多修改,然后再次更新,等等。每次单独修改后更新可能会非常昂贵。
如果你忘记调用update,你的程序不会崩溃。你的查询将简单地返回自上次更新以来请求数据的值。如果你尝试查询的对象不存在,Gurobi将抛出一个错误代码为NOT_IN_MODEL的异常。
自早期Gurobi版本以来,延迟更新的语义已经发生了变化。虽然绝大多数程序不受此变化的影响,但如果您遇到问题,可以使用UpdateMode参数恢复到早期的行为。
管理参数#
Gurobi优化器提供了一组参数,允许您控制优化过程的许多细节。诸如可行性和最优性容差、算法选择、探索MIP搜索树的策略等因素,都可以通过在开始优化之前修改Gurobi参数来控制。参数使用Model.setParam
方法设置。当前值也可以通过Model.getParamInfo
检索。您还可以通过Model.Params
类更直接地访问参数。例如,要将模型m
的MIPGap参数设置为0.0,您可以执行m.setParam('MIPGap', 0)
或m.Params.MIPGap = 0
。请参考params.py示例,该示例在Change parameters中进行了讨论。
您可以使用Model.read
从文件中读取一组参数设置,或者使用Model.write
写入一组更改后的参数。
我们还包含一个自动参数调优工具,该工具探索许多不同的参数变化集,以找到提高性能的集合。您可以调用Model.tune
来在模型上调用调优工具。有关更多信息,请参阅参数调优工具部分。
我们应该注意的一件事是,更改一个模型的参数不会影响其他模型的参数值。使用全局的setParam
方法为所有加载的模型设置参数。
Gurobi参数的完整列表可以在参数部分找到。
监控进度 - 日志记录#
优化的进度可以通过Gurobi日志进行监控。默认情况下,Gurobi会将输出发送到屏幕。有一些简单的控制可用于修改默认的日志行为。如果您希望将Gurobi日志也定向到文件,可以设置日志文件参数。可以使用LogToConsole参数来抑制将日志消息打印到屏幕(标准输出)。日志输出的频率可以通过DisplayInterval参数进行控制,并且可以使用OutputFlag参数完全关闭日志记录。
日志输出也会发送到名为 gurobipy
的 Python 日志记录器,级别为
INFO
。你可以使用 Python 的 logging
模块来连接
这个日志。除非 OutputFlag 设置为 0,否则消息总是会发送到日志记录器。
如果您希望将日志消息发送到Python记录器但不打印到标准输出,您应该将OutputFlag保留为其默认值,并将LogToConsole设置为0。要禁止所有Gurobi日志消息在标准输出上显示,您必须在启动环境时设置此参数。以下示例演示了正确的用法,以及调用logging.basicConfig
通过Python记录器发出Gurobi日志消息:
import logging
import gurobipy as gp
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
)
env = gp.Env(params={"LogToConsole": 0})
model = gp.Model(env=env)
...
model.optimize()
监控进度 - 回调#
可以通过回调函数进行更详细的进度监控。如果您将一个接受两个参数model
和where
的函数传递给Model.optimize
,您的函数将在优化过程中定期被调用。然后,您的回调可以调用Model.cbGet
来检索有关优化状态的附加信息。您可以参考Callbacks部分以获取更多信息。请参阅示例callback.py,该示例在Callbacks中讨论。
修改求解器行为 - 回调#
回调函数也可以用于修改Gurobi优化器的行为。最简单的控制回调是Model.terminate
,它要求优化器在最早方便的点终止。方法Model.cbSetSolution
允许你在MIP模型求解过程中注入一个可行解(或部分解)。方法Model.cbCut
和Model.cbLazy
允许你在MIP优化过程中分别添加切割平面和惰性约束(参考示例tsp.py)。方法Model.cbStopOneMultiObj
允许你在多目标MIP问题中中断其中一个优化步骤的优化过程,而不停止分层优化过程。
批量优化#
Gurobi 计算服务器使程序能够将优化计算卸载到专用服务器上。Gurobi 集群管理器在此基础上增加了许多额外的功能。其中一个重要的功能是批量优化,它允许您使用客户端程序构建优化模型,将其提交到计算服务器集群(通过集群管理器),并在稍后检查模型的状态并检索其解决方案。您可以使用Batch 对象
来更轻松地处理批量任务。有关批量的详细信息,请参阅批量优化部分。
错误处理#
Gurobi Python库中的所有方法都可能抛出GurobiError
类型的异常。当异常发生时,可以通过检索GurobiError
对象的errno
或message
成员来获取有关错误的更多信息。errno
字段的可能值列表可以在错误代码表中找到。