Java API 概览#
环境#
使用Gurobi Java接口的第一步是创建一个环境对象。环境使用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 these
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
本节 in the Reference
Manual 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.java以获取具体示例。
我们经常提到优化模型的类别。在最高层次上,模型可以是连续的或离散的,这取决于模型中存在的建模元素是否需要做出离散决策。在连续模型中…
具有线性目标函数、线性约束和连续变量的模型称为线性规划(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
, and classes.
Gurobi算法会仔细跟踪模型的状态,因此只有在自上次优化以来相关数据发生变化时,调用GRBModel.optimize
才会执行进一步的优化。如果您希望丢弃先前计算的解决方案信息并在不更改模型的情况下从头开始重新启动优化,您可以调用GRBModel.reset
。
在解决了一个MIP模型之后,你可以调用
GRBModel.fixedModel
来计算相关的固定
模型。这个模型与原始模型相同,除了整数
变量被固定为它们在MIP解决方案中的值。如果你的模型
包含SOS约束,这些约束中出现的一些连续变量
也可能被固定。在某些应用中,计算这个固定模型的信息
(例如,对偶变量,敏感性信息等)可能是有用的,
尽管你应该小心解释这些信息。
多种解决方案、目标和场景#
默认情况下,Gurobi优化器假设您的目标是找到一个经过验证的最优解,针对具有单一目标函数的单一模型。Gurobi提供了以下功能,允许您放宽这些假设:
解决方案池: 允许你找到更多的解决方案(参考示例 Poolsearch.java)。
多种场景: 允许您找到多个相关模型的解决方案 (参考示例 Multiscenario.java)。
多目标: 允许您指定多个目标函数并控制它们之间的权衡(参考示例 Multiobj.java)。
不可行模型#
如果发现模型不可行,您有几个选择。您可以尝试诊断不可行的原因,尝试修复不可行性,或者两者都做。为了获取有助于诊断不可行原因的信息,可以调用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
包括允许您查询或修改一维、二维和三维变量或约束数组的属性值的签名。
完整的属性列表可以在 属性 部分找到。
额外的模型修改信息#
对现有模型的大多数修改都是通过属性接口完成的(例如,变量边界、约束右侧等的更改)。主要的例外是对约束矩阵和目标函数的修改。
约束矩阵可以通过几种方式进行修改。第一种是调用chgCoeff
方法来更改单个矩阵系数。此方法可用于修改现有非零值、将现有非零值设置为零或创建新的非零值。当您从模型中删除变量或约束时(通过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.java,该示例在 更改参数 中被讨论。
我们还包含一个自动参数调优工具,该工具探索许多不同的参数变化集,以找到一组可以提高性能的参数。您可以调用GRBModel.tune
来在模型上调用调优工具。有关更多信息,请参阅参数调优工具部分。
Gurobi参数的完整列表可以在参数部分找到。
内存管理#
用户通常不需要关心Java中的内存管理,因为它由垃圾收集器自动处理。Gurobi Java接口使用与其他Java程序相同的垃圾收集机制,但用户应该了解我们内存管理的一些具体细节。
一般来说,Gurobi对象与其他Java对象存在于同一个Java堆中。当它们不再被引用时,它们将成为垃圾回收的候选对象,并在下一次垃圾回收器调用时返回到空闲空间池中。两个重要的例外是GRBEnv
和GRBModel
对象。GRBModel
对象在Java堆中有一小部分内存与之关联,但模型的大部分空间存在于Gurobi本地代码库的堆中(在Windows中是Gurobi DLL,在Linux或Mac中是Gurobi共享库)。Java堆管理器不知道本地代码库中与模型关联的内存,因此在决定是否调用垃圾回收器时不会考虑这种内存使用情况。当垃圾回收器最终收集JavaGRBModel
对象时,Gurobi本地代码库中与模型关联的内存将被释放,但这种收集可能比你希望的要晚。类似的情况也适用于GRBEnv
对象。
如果您正在编写一个使用多个Gurobi模型或环境的Java程序,我们建议您在使用完相关的GRBModel
对象后调用GRBModel.dispose
,并在使用完相关的GRBEnv
对象后调用GRBEnv.dispose
,且在此之前已经对该GRBEnv
对象创建的所有模型调用了GRBModel.dispose
。
原生代码#
如前所述,Gurobi Java接口是一个位于我们本地代码库(Windows上的Gurobi DLL,以及Linux或Mac上的Gurobi共享库)之上的薄层。因此,使用Gurobi Java库的应用程序将在运行时加载Gurobi本地代码库。为了实现这一点,您需要确保两件事是正确的。首先,您需要确保本地代码库在目标机器的搜索路径中可用(Windows上的PATH
,Linux上的LD_LIBRARY_PATH
,或Mac上的DYLD_LIBRARY_PATH
)。这些路径在Gurobi优化器安装过程中设置,但在未安装完整Gurobi优化器的机器上可能未正确配置。其次,您需要确保Java JVM和Gurobi本地库使用相同的对象格式。特别是,您需要使用64位Java JVM来使用64位Gurobi本地库。
监控进度 - 日志记录和回调#
优化的进度可以通过Gurobi日志进行监控。默认情况下,Gurobi会将输出发送到屏幕。有一些简单的控制可用于修改默认的日志行为。如果您希望将输出同时定向到文件和屏幕,请在GRBEnv
构造函数中指定日志文件名。如果您希望在创建环境对象后将日志重定向到不同的文件,可以修改日志文件参数。日志输出的频率可以通过DisplayInterval参数控制,并且可以使用OutputFlag参数完全关闭日志。Gurobi日志文件的详细描述可以在日志记录部分找到。
更详细的进度监控可以通过
GRBCallback
类来完成。GRBModel.setCallback
方法允许您从Gurobi优化器接收定期回调。您可以通过子类化GRBCallback
抽象类,并在此类上编写您自己的Callback()
方法来实现这一点。您可以在回调中调用GRBCallback.getDoubleInfo
、
GRBCallback.getIntInfo
、
GRBCallback.getStringInfo
或
GRBCallback.getSolution
来获取有关优化状态的额外信息。
请参考示例Callback.java,
该示例在Callbacks中讨论。
此外,您可以为环境对象(GRBEnv.setLogCallback
)或模型对象(GRBModelEnv.setLogCallback
)添加日志回调函数。回调函数将接收由环境或模型对象生成的每条日志行。
修改求解器行为 - 回调#
回调函数也可以用于修改Gurobi优化器的行为。最简单的控制回调是GRBCallback.abort
,它要求优化器在最早方便的点终止。方法GRBCallback.setSolution
允许你在MIP模型的求解过程中注入一个可行解(或部分解)。方法GRBCallback.addCut
和GRBCallback.addLazy
分别允许你在MIP优化过程中添加切割平面和惰性约束(参考示例Tsp.java)。方法GRBCallback.stopOneMultiObj
允许你在多目标MIP问题中中断其中一个优化步骤的优化过程,而不停止分层优化过程。
批量优化#
Gurobi 计算服务器使程序能够将优化计算卸载到专用服务器上。Gurobi 集群管理器在此基础上增加了许多额外的功能。其中一个重要的功能是批量优化,它允许您使用客户端程序构建优化模型,将其提交到计算服务器集群(通过集群管理器),并在稍后检查模型的状态并检索其解决方案。您可以使用Batch 对象
来更轻松地处理批量任务。有关批量的详细信息,请参阅批量优化部分。
错误处理#
Gurobi Java库中的所有方法都可能抛出类型为GRBException
的异常。当异常发生时,可以通过检索错误代码(使用方法GRBException.getErrorCode
)或检索异常消息(从父类中使用方法GRBException.getMessage
)来获取有关错误的更多信息。可能的错误返回代码列表可以在错误代码表中找到。