C++ API 概览#
环境#
使用Gurobi C++接口的第一步是创建一个环境对象。环境使用GRBEnv
类表示。环境充当与一组优化运行相关的所有数据的容器。在您的程序中,通常只需要一个环境对象。
对于更高级的用例,您可以使用空环境来创建一个未初始化的环境,然后通过编程方式为您的特定需求设置所有必需的选项。更多详细信息,请参阅环境部分。
模型#
You can create one or more optimization models within an environment.
Each model is represented as an object of class GRBModel
. A
model consists of a set of decision variables (objects of class
GRBVar
), a linear or quadratic objective function on those
variables (specified using GRBModel::setObjective
), and a
set of constraints on these variables (objects of class
GRBConstr
, GRBQConstr
, GRBSOS
, or
GRBGenConstr
). 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.
线性约束通过构建线性表达式(类GRBLinExpr
的对象)来指定,然后指定这些表达式之间的关系(例如,要求一个表达式等于另一个)。二次约束以类似的方式构建,但使用二次表达式(类GRBQuadExpr
的对象)代替。
优化模型可以一次性指定,通过从文件加载模型(使用适当的GRBModel
构造函数),或者逐步构建,首先构造一个空的GRBModel
类对象,然后随后调用GRBModel::addVar
或GRBModel::addVars
来添加额外的变量,以及GRBModel::addConstr
、GRBModel::addQConstr
、GRBModel::addSOS
或任何GRBModel::addGenConstr*
方法来添加约束。模型是动态实体;您可以随时添加或删除变量或约束。请参阅构建模型以获取一般指导,或mip1_c++.cpp以获取具体示例。
我们经常提到优化模型的类别。在最高层次上,模型可以是连续的或离散的,这取决于模型中存在的建模元素是否需要做出离散决策。在连续模型中…
具有线性目标函数、线性约束和连续变量的模型称为线性规划(LP)。
如果目标是二次的,模型就是二次规划(QP)。
如果任何约束是二次的,模型就是二次约束规划(QCP)。我们有时会提到QCP的一些特殊情况:具有凸约束的QCP、具有非凸约束的QCP、双线性规划,以及二阶锥规划(SOCP)。
如果任何约束是非线性的(从可用的通用约束中选择),则该模型是一个非线性规划(NLP)。
一个包含任何整数变量、半连续变量、半整数变量、特殊有序集(SOS)约束或一般约束的模型是离散的,被称为混合整数规划(MIP)。MIP的特殊情况,即我们已经描述的连续模型类型的离散版本,是…
混合整数线性规划 (MILP)
混合整数二次规划 (MIQP)
混合整数二次约束规划 (MIQCP)
混合整数二阶锥规划 (MISOCP)
混合整数非线性规划 (MINLP)
Gurobi优化器处理所有这些模型类别。请注意,它们之间的界限并不像人们希望的那样清晰,因为我们通常能够将一个模型从一个类别转换为一个更简单的类别。
解决模型#
Once you have built a model, you can call GRBModel::optimize
to compute a solution. By default,
optimize
will use the
并发优化器 to solve LP models, the
barrier algorithm to solve QP models with convex objectives and QCP
models with convex constraints, and the branch-and-cut algorithm
otherwise. The solution is stored in a set of attributes of the model.
These attributes can be queried using a set of attribute query methods
on the GRBModel
, GRBVar
,
GRBConstr
, GRBQConstr
, GRBSOS
,
and GRBGenConstr
classes.
Gurobi算法会仔细跟踪模型的状态,因此只有在自上次优化以来相关数据发生变化时,调用GRBModel::optimize
才会执行进一步的优化。如果您希望丢弃先前计算的解决方案信息并在不更改模型的情况下从头开始重新启动优化,您可以调用GRBModel::reset
。
在解决了一个MIP模型之后,你可以调用
GRBModel::fixedModel
来计算相关的固定
模型。这个模型与原始模型相同,除了整数
变量被固定为它们在MIP解中的值。如果你的模型
包含SOS约束,这些约束中出现的一些连续变量
也可能被固定。在某些应用中,计算这个固定模型的信息
(例如,对偶变量,敏感性信息等)可能是有用的,
尽管你应该小心解释这些信息。
多种解决方案、目标和场景#
默认情况下,Gurobi优化器假设您的目标是找到一个经过验证的最优解,针对具有单一目标函数的单一模型。Gurobi提供了以下功能,允许您放宽这些假设:
解决方案池: 允许你找到更多的解决方案(参考示例 poolsearch_c++.cpp)。
多种场景: 允许您找到多个相关模型的解决方案(参考示例 multiscenario_c++.cpp)。
多目标: 允许您指定多个目标函数并控制它们之间的权衡(参考示例 multiobj_c++.cpp)。
不可行模型#
如果发现模型不可行,您有几个选择。您可以尝试诊断不可行的原因,尝试修复不可行性,或者两者都做。为了获取有助于诊断不可行原因的信息,可以调用GRBModel::computeIIS
来计算一个不可约不一致子系统(IIS)。此方法可用于连续和MIP模型,但您应该注意,MIP版本可能非常昂贵。此方法填充了一组IIS属性。
为了尝试修复不可行性,调用
GRBModel::feasRelax
来计算模型的可行性松弛。这种松弛允许你找到一个最小化约束违反程度的解决方案。
你可以在章节
Relaxing for feasibility 中找到关于此功能的更多信息。示例在
Diagnose and cope with infeasibility 中讨论。
查询和修改属性#
与Gurobi模型相关的大部分信息存储在一组属性中。一些属性与模型的变量相关,一些与模型的约束相关,还有一些与模型本身相关。举一个简单的例子,求解优化模型会导致X变量属性被填充。由Gurobi优化器计算的属性(如X)不能由用户直接修改,而其他属性(如变量下限(LB属性))则可以。
属性使用GRBVar::get
、GRBConstr::get
、GRBQConstr::get
、GRBSOS::get
、GRBGenConstr::get
或GRBModel::get
进行查询,并使用GRBVar::set
、GRBConstr::set
、GRBQConstr::set
、GRBGenConstr::set
或GRBModel::set
进行修改。属性按类型分组为一组枚举(GRB_CharAttr
、GRB_DoubleAttr
、GRB_IntAttr
、GRB_StringAttr
)。get()
和set()
方法是重载的,因此属性的类型决定了返回值的类型。因此,constr.get(GRB.DoubleAttr.RHS)
返回一个double,而constr.get(GRB.CharAttr.Sense)
返回一个char。
如果您希望检索一组变量或约束的属性值,通常更有效的方法是使用关联的GRBModel
对象上的数组方法。方法GRBModel::get
包括允许您查询或修改变量或约束数组的属性值的签名。
完整的属性列表可以在 属性 部分找到。
额外的模型修改信息#
对现有模型的大多数修改都是通过属性接口完成的(例如,变量边界、约束右侧等的更改)。主要的例外是对约束矩阵和目标函数的修改。
约束矩阵可以通过几种方式进行修改。第一种是调用chgCoeffs
方法来更改单个矩阵系数。此方法可用于修改现有非零值、将现有非零值设置为零或创建新的非零值。当您从模型中删除变量或约束时(通过GRBModel::remove
方法),约束矩阵也会被修改。与被删除的约束或变量相关的非零值将随约束或变量本身一起被删除。
模型的目标函数也可以通过几种方式进行修改。最简单的方法是构建一个捕获目标函数的表达式(一个GRBLinExpr
或GRBQuadExpr
对象),然后将该表达式传递给方法GRBModel::setObjective
。如果您希望修改目标函数,您可以简单地再次调用GRBModel::setObjective
,并传入一个新的GRBLinExpr
或GRBQuadExpr
对象。
对于线性目标函数,除了使用GRBModel::setObjective
之外,还可以使用Obj变量属性来修改单个线性目标系数。
如果你的变量具有分段线性目标,你可以使用GRBModel::setPWLObj
方法来指定它们。为每个相关变量调用此方法一次。Gurobi单纯形求解器包括对凸分段线性目标函数的算法支持,因此对于连续模型,你应该会看到使用此功能的显著性能优势。要清除先前指定的分段线性目标函数,只需将相应变量的Obj属性设置为0。
一些例子在 修改模型中讨论。
延迟更新#
关于Gurobi优化器中的模型修改,一个需要注意的重要事项是,它是以惰性方式执行的,这意味着修改不会立即影响模型。相反,它们会被排队并在稍后应用。如果你的程序只是创建一个模型并解决它,你可能永远不会注意到这种行为。然而,如果你在修改应用之前询问有关模型的信息,惰性更新方法的细节可能对你很重要。
正如我们刚才提到的,模型修改(边界更改、右侧更改、目标更改等)被放入队列中。这些排队的修改可以通过三种不同的方式应用到模型中。第一种是通过显式调用GRBModel::update
。第二种是通过调用GRBModel::optimize
。第三种是通过调用GRBModel::write
来写出模型。第一种情况让你可以精细控制何时应用修改。第二种和第三种情况假设你希望在优化模型或将其写入磁盘之前应用所有待处理的修改。
为什么Gurobi接口会以这种方式运行?有几个原因。首先,这种方法使得对模型进行多次修改变得更加容易,因为模型在修改之间保持不变。其次,处理模型修改可能会非常昂贵,特别是在计算服务器环境中,修改需要机器之间的通信。因此,了解这些修改何时应用是非常有用的。一般来说,如果你的程序需要对模型进行多次修改,你应该分阶段进行,即进行一组修改,然后更新,再进行更多修改,然后再次更新,等等。每次单独修改后更新可能会非常昂贵。
如果你忘记调用update,你的程序不会崩溃。你的查询将简单地返回自上次更新以来请求数据的值。如果你尝试查询的对象不存在,Gurobi将抛出一个错误代码为NOT_IN_MODEL的异常。
自早期Gurobi版本以来,延迟更新的语义已经发生了变化。虽然绝大多数程序不受此变化的影响,但如果您遇到问题,可以使用UpdateMode参数恢复到早期的行为。
管理参数#
Gurobi优化器提供了一组参数,允许您控制优化过程的许多细节。诸如可行性和最优性容差、算法选择、探索MIP搜索树的策略等因素,都可以通过在开始优化之前修改Gurobi参数来控制。参数可以是int、double或string类型。
设置参数的最简单方法是通过模型对象上的GRBModel::set
方法。同样,可以使用GRBModel::get
查询参数值。
参数也可以在Gurobi环境对象上设置,使用GRBEnv::set
。请注意,每个模型在创建时都会获得其自己的环境副本,因此对原始环境的参数更改不会影响现有模型。
你可以使用GRBEnv::readParams
从文件中读取一组参数设置,或者使用GRBEnv::writeParams
写入一组更改后的参数。
请参考示例params_c++.cpp,该示例在更改参数中被讨论。
我们还包含一个自动参数调优工具,该工具探索许多不同的参数变化集,以找到提高性能的集合。您可以调用GRBModel::tune
来在模型上调用调优工具。有关更多信息,请参阅参数调优工具部分。
Gurobi参数的完整列表可以在参数部分找到。
内存管理#
在C++程序中必须始终考虑内存管理。特别是,Gurobi库和用户程序共享同一个C++堆,因此用户必须了解Gurobi库如何使用这个堆的某些方面。使用Gurobi优化器时管理内存的基本规则如下:
与其他动态分配的C++对象一样,
GRBEnv
或GRBModel
对象应使用相关的析构函数进行释放。换句话说,给定一个GRBModel
对象m
,当你不再使用m
时,你应该调用delete m
。与模型关联的对象(例如,
GRBConstr
,GRBQConstr
,GRBSOS
,GRBGenConstr
, 和GRBVar
对象)由模型管理。特别是,删除模型将删除所有关联的对象。同样,从模型中移除对象(使用GRBModel::remove
)也会删除该对象。一些Gurobi方法返回一个对象或值的数组。例如,
GRBModel::addVars
返回一个GRBVar
对象的数组。用户有责任释放返回的数组(使用delete[]
)。参考手册会指出方法何时返回堆分配的结果。
这些规则的一个后果是,你必须小心不要在对象被释放后使用它。这对于环境和模型来说无疑是非常清楚的,因为你显式地调用了析构函数,但对于约束和变量来说可能就不那么清楚了,它们在关联的模型被删除时会被隐式删除。
监控进度 - 日志记录和回调#
优化的进度可以通过Gurobi日志进行监控。默认情况下,Gurobi会将输出发送到屏幕。有一些简单的控制可用于修改默认的日志行为。如果您希望将输出同时定向到文件和屏幕,请在GRBEnv
构造函数中指定日志文件名。如果您希望在创建环境对象后将日志重定向到不同的文件,可以修改日志文件参数。日志输出的频率可以通过DisplayInterval参数控制,并且可以使用OutputFlag参数完全关闭日志。Gurobi日志文件的详细描述可以在日志记录部分找到。
更详细的进度监控可以通过GRBCallback
类来完成。GRBModel::setCallback
方法允许你从Gurobi优化器接收定期回调。你可以通过子类化GRBCallback
抽象类,并在该类上编写你自己的callback()
方法来实现这一点。你可以在回调中调用GRBCallback::getDoubleInfo
、GRBCallback::getIntInfo
、GRBCallback::getStringInfo
或GRBCallback::getSolution
来获取有关优化状态的额外信息。请参考示例callback_c++.cpp,该示例在Callbacks中讨论。
修改求解器行为 - 回调#
回调函数也可以用于修改Gurobi优化器的行为。最简单的控制回调是
GRBCallback::abort
,它要求优化器在最早方便的点终止。方法
GRBCallback::setSolution
允许你在MIP模型的求解过程中注入一个可行的
解决方案(或部分解决方案)。方法
GRBCallback::addCut
和
GRBCallback::addLazy
允许你在MIP优化过程中分别添加切割平面和
惰性约束(参考示例
tsp_c++.cpp)。方法
GRBCallback::stopOneMultiObj
允许你中断
多目标MIP问题中的一个优化步骤的优化过程,而不停止
分层优化过程。
批量优化#
Gurobi 计算服务器使程序能够将优化计算卸载到专用服务器上。Gurobi 集群管理器在此基础上增加了许多额外的功能。其中一个重要的功能是批量优化,它允许您使用客户端程序构建优化模型,将其提交到计算服务器集群(通过集群管理器),并在稍后检查模型的状态并检索其解决方案。您可以使用Batch 对象
来更轻松地处理批量任务。有关批量的详细信息,请参阅批量优化部分。
错误处理#
Gurobi C++ 库中的所有方法都可能抛出类型为 GRBException
的异常。当异常发生时,可以通过检索错误代码(使用方法 GRBException::getErrorCode
)或检索异常消息(使用方法 GRBException::getMessage
)来获取有关错误的更多信息。可能的错误返回代码列表可以在 错误代码 表中找到。