本文将结合官方文档和其他相关介绍,对Matlab + Gurobi的使用做一个完全零基础的入门介绍(我也是小白,这也是对自己学习的一个记录)
该问题是一个非常简单的铸币厂生产硬币问题,一共有五种类型的硬币,价值分别对应0.01$, 0.05$, 0.1$, 0.25$, 和1$,每种硬币由不同的原材料组成,一共四种,使用的原材料分量如表1-1所示(原问题详见官网:Solving a Simple Model - The Gurobi Command Line):
Penny | Nickel | Dime | Quarter | Dollar | |
Copper(Cu) | 0.06g | 3.8g | 2.1g | 5.2g | 7.2g |
Nickel(Ni) | 1.2g | 0.2g | 0.5g | 0.2g | |
Zinc(Zi) | 2.4g | 0.5g | |||
Manganses(Mn) | 0.3g |
表1-1
不同材料的剩余量分别为Cu=1000, Ni=50, Zi=50, Mn=50,决策变量不同硬币的生产数量(注意这里的变量需为正数),求如何生产能让产出的总价值最大。
在具体的LP格式的Gurobi文档中,需要注意以下四点:
1)不同部分的顺序。需要按照目标函数,约束条件,变量范围,整数要求等顺序进行;
2)分离符号。不同的符号如+-号,数字,变量之间必须用空格或新的一行间隔开;
3)变量安排。约束条件中所有的变量必须放在约束的左手边;
4)变量默认范围。如果没有特殊说明,变量的下界为0,上界无穷大。
在cmd中运行如下代码,可以得到铸币厂的最大产值为113.45$。
gurobi_cl D:\Software\Gurobiv9\win64\examples\data\coins
用如下命令可以得到包含决策变量最优解的文件:
gurobi_cl ResultFile=coins.sol D:\Software\Gurobiv9\win64\examples\data\coins.lp
若没有指定目录,便会用户文件夹下生成结果文件。
m=read('D:\Software\Gurobiv9\win64\examples\data\coins')
可以直接读取一个写好的模型,并将其赋予变量m中。
m.read('D:\Software\Gurobiv9\win64\examples\*\coin*')
也可以使用通配符*,但注意末尾的*必须存在。如果在电脑上安装了gzip,bzip2,7zip等,可以在文件名后面加上合适的后缀来读写压缩文件。
m.optimize()
对调用优化方法对模型m进行优化。
当一个模型被求解后,可以用以下命令查看模型的最优解
m.printAttr('X')
此例程将输出所有的指定属性X的非零值。另外也可以用以下命令得到模型中所有的变量,并且用其他的命令获取变量的不同属性:
gurobi> v=m.getVars()
gurobi> print(v)
[, , , , , , , , ]
gurobi> print(v[0].varName, v[0].x)
Pennies 0.0
gurobi求解器会时刻追踪模型的状态,当模型的条件改变时,它会知道之前得出的解不是修改后模型的最优解。用以下函数就可以得出最少生产10个便士时的最优解。此时用v[0].x也可以检验下界
gurobi> v = m.getVars()
gurobi> v[0].lb = 10
gurobi> m.optimize()
在例子中读取一个更复杂的模型,并进行优化。在运行到30s(因电脑性能而异,10s也行)时输入Ctrl+c打断模型优化,可以得到此时的优化结果,输入m.optimize()继续优化时将从中断点开始进行。
gurobi> m=read('D:\Software\Gurobiv9\win64\examples\data\glass4.mps')
gurobi> m.optimize()
Interrupt request received
Explored 89036 nodes (563337 simplex iterations) in 31.58 seconds
Thread count was 8 (of 8 available processors)
Solution count 10: 1.60001e+09 1.60002e+09 1.60002e+09 ... 1.81668e+09
Solve interrupted
Best objective 1.600012700136e+09, best bound 9.333401166667e+08, gap 41.6667%
gurobi> m.optimize()
Optimal solution found (tolerance 1.00e-04)
Best objective 1.200012600000e+09, best bound 1.200008900000e+09, gap 0.0003%
当模型的下界移动缓慢时,一个可能有用参数是MIPFocus,它能够调整高级MIP的求解策略。我们将这个参数设为1,这会将 MIP 搜索的重点转移到寻找好的可行解上。改变参数的方式有两种:
gurobi> m.setParam('MIPFocus',1)
Changed value of parameter MIPFocus to 1
Prev: 0 Min: 0 Max: 3 Default: 0
gurobi> m.Params.MIPFocus = 1
Parameter MIPFocus unchanged
Value: 1 Min: 0 Max: 3 Default: 0
参数重置后,可以调用m.reset()对模型的优化结果进行重置,并利用m.optimize()开始一个新的优化过程,但在实际操作中,只在23s时得到了1.5e9,并没有得到模型的最优结果。
参数设置命令setParam() 被设计得非常灵活且具有包容性。 它接受通配符作为参数,并且忽略字符大小写。 因此,以下命令都是等效的:
gurobi> m.setParam('NODELIMIT', 100)
gurobi> m.setParam('NodeLimit', 100)
gurobi> m.setParam('Node*', 100)
gurobi> m.setParam('N???Limit, 100)
也可以用通配符对一系列符合的参数赋值:
gurobi> m.setParam('*Cuts', 2)
Matching parameters: ['Cuts', 'CliqueCuts', 'CoverCuts', 'FlowCoverCuts',
'FlowPathCuts', 'GUBCoverCuts', 'ImpliedCuts', 'MIPSepCuts', 'MIRCuts', 'ModKCuts',
'NetworkCuts', 'SubMIPCuts', 'ZeroHalfCuts']
相对应的Model.Params就不像setParam()有那么高的包容性,因而不可以使用通配符*,但同样不必担心大小写的问题。
m.Params.Heuristics和m.Params.heuristics的含义相同。所有的gurobi参数可以通过paramHelp()命令浏览,同时,针对一个具体参数的信息也可以通过paramHelp('MIPGap')获取。gurobi的参数种类如下:
Termination: affect the termination of an optimize() call
Tolerances: control the allowable feasibility or optimality violations
Simplex: affect the simplex algorithms
Barrier: affect the barrier algorithms
MIP: affect the MIP algorithms
Presolve: affect the presolve algorithms
Tuning: affect the operation of the tuning tool
Multiple solutions: determines how the MIP search looks for solutions
MIP cuts: affect the generation of MIP cutting planes
Distributed algorithms: used for distributed optimization
Cloud: parameters used for cloud-based optimization
Compute Server and Cluster Manager: used for optimization with Remote Services
Token Server: affect token server parameters
Other:
当面临选择可能导致模型性能更好的参数值的任务时,一长串 Gurobi 参数似乎令人生畏。 为了简化流程,我们提供了一个简单的自动参数调整工具。 在gurobi交互环境中,命令是 tune():
gurobi> m=read('D:\Software\Gurobiv9\win64\examples\data\misc07')
gurobi> m.tune()
Tested 14 parameter sets in 35.90s
Baseline parameter set: mean runtime 1.23s
Improved parameter set 1 (mean runtime 1.00s):
Heuristics 0
GomoryPasses 0
Improved parameter set 2 (mean runtime 1.04s):
GomoryPasses 0
可见,将GomoryPasses设置为1,能让模型misc07的运行时间从1.23s降至1.04s。需要注意的是,调参旨在针对可能有助于性能提升的参数提供一般性建议。 应该确保它在一个模型上给出的结果对计划解决的所有模型都有帮助。 有时可能会发现仅通过更改参数无法解决性能问题,尤其是在模型存在严重的数值问题时。调参也是一个独立的程序,在cmd中可以通过一下命令调用:
grbtune D:\Software\Gurobiv9\win64\examples\data\p0033
我们已经利用windows的cmd,gurobi交互环境对参数进行了设置,另一个参数设置选项是通过gurobi.env文件进行。无论gurobi库何时启动,它总会在当前的工作目录下浏览gurobi.env文件,并应用文件中任何的参数改变。无论gurobi库从cmd,gurobi交互环境,还是其他的gurobi API中被唤起,情况都是如此。参数设定被存储在文件中的每一行,以参数名开头,接着是至少一个空格,然后是参数值。#号开头的行代表注释。
在gurobi交互环境中允许多模型同时工作,例如:
gurobi> a = read('D:\Software\Gurobiv9\win64\examples\data\p0033')
Read MPS format model from file D:\Software\Gurobiv9\win64\examples\data\p0033.mps
Reading time = 0.00 seconds
P0033: 16 rows, 33 columns, 98 nonzeros
gurobi> b = read('D:\Software\Gurobiv9\win64\examples\data\stein9')
Read MPS format model from file D:\Software\Gurobiv9\win64\examples\data\stein9.mps
Reading time = 0.00 seconds
STEIN9: 13 rows, 9 columns, 45 nonzeros
gurobi> models()
Currently loaded models:
a :
b :
对指定的模型进行参数设定可以用Model.setParam()或Model.Params class,再或者用全局参数设定方法同时改变所有模型的参数setParam()。
help()命令对相应的命令和参数进行详细的解释
在gurobi的交互界面中,可以使用python语言定制化需求,如从指定路径读取文件:
gurobi> def myread(filename):
....... return read('D:/Software/Gurobiv9/win64/examples/data/'+filename)
.......
gurobi> m = myread('coins')
Read LP format model from file D:/Software/Gurobiv9/win64/examples/data/coins.lp
Reading time = 0.00 seconds
: 4 rows, 9 columns, 16 nonzeros
注意定义函数时的斜线方向,否则会报错。对于常用的命令也可以写一个文件,这样每次调用相应的文件即可。比如
官方的快速入门指南中给出了一个matlab和gurobi的简单优化例子,代码如下,后面是针对模型的具体解释:
function mip1()
% Copyright 2021, Gurobi Optimization, LLC
% This example formulates and solves the following simple MIP model:
% maximize
% x + y + 2 z
% subject to
% x + 2 y + 3 z <= 4
% x + y >= 1
% x, y, z binary
names = {'x'; 'y'; 'z'}; % 变量
model.A = sparse([1 2 3; 1 1 0]); % 约束矩阵A
model.obj = [1 1 2]; % 目标函数
model.rhs = [4; 1]; % 右手边向量
model.sense = '<>'; % 约束感知,<=,=,还是>=
model.vtype = 'B'; % 变量类型
model.modelsense = 'max'; % 目标函数类型
model.varnames = names; % 模型变量
gurobi_write(model, 'mip1.lp');
params.outputflag = 0; % 关闭gurobi输出
params.resultfile = 'mip1.lp'; % 根据模型结果生成文件
result = gurobi(model, params); % 执行优化过程
disp(result);
for v=1:length(names)
fprintf('%s %d\n', names{v}, result.x(v));
end
fprintf('Obj: %e\n', result.objval);
end
与优化模型有关的数据一定要存储为matlab结构类型,结构中的不同字段代表模型中的不同部分。最终要的区域如下,约束矩阵A,目标向量obj,右手边向量rhs,约束感知向量sense(<=, =, 还是>=)。其中只有约束矩阵是强制需要的,模型其他的部分会用默认值代替。
本例使用的是稀疏函数来构建约束矩阵A。gurobi matlab接口中只接受稀疏矩阵作为输入变量。如果有一个密集矩阵,需要用稀疏函数进行转换。
除了以上的字段,模型中还有其他两个字段:modelsense和vtype。modelsense是对目标函数的判断,默认值是最小化,所以在这个例子中将字段设置为'max',以指示我们要最大化目标函数。vtype字段指示了模型中变量的类型。这个例子中,所有的例子都是二进制0-1变量('B')。注意,该接口允许对sense和vtype类进行标量值的具体说明。gurobi接口会将标量值扩展为具有合适长度的常量矩阵。在这个例子中,标量值'B'将会被扩展成一个长度为3的矩阵,对A中的每一列都包含一个'B'值。
这个例子中还创建了一个结构变量,可以用于修改两个gurobi参数。outputflag=0代表关闭gurobi输出,params.resultfile = 'mip1.lp'根据模型结果生成文件。gurobi-matlab接口允许设置所有的gurobi参数。需要注意的是,参数结构的字段名需要匹配gurobi的参数名称,字段的值也需要匹配可选参数值。所有的gurobi参数值详见gurobi参考手册。
优化过程由这句代码执行,result = gurobi(model, params),这里我们将model和参数的可选列表传递给了gurobi()函数。它将计算模型的最优解并返回计算结果。
gurobi()函数返回一个struct作为结果。这个struct中包含一系列的字段,每个字段都包含了计算结果的相应信息。可获取字段依赖于优化模型的结果,求解模型的类型,以及使用的算法。返回的struct总会包含status字段,指示gurobi是否能够计算模型的最优解。可以从gurobi参考手册中查看所有可能的状态码。如果gurobi能够求解模型,返回的值包括objval和x字段。objval代表模型的目标值,x代表决策变量。对于连续模型,将返回双重信息(降低的成本和双因子),以及一个可能的最优基础。
>> mip1
status: 'OPTIMAL'
versioninfo: [1×1 struct]
runtime: 0
objval: 3
x: [3×1 double]
slack: [2×1 double]
poolobjbound: 3
pool: [1×2 struct]
mipgap: 0
objbound: 3
objboundc: 3
itercount: 0
baritercount: 0
nodecount: 0
x 1 % 三个决策变量
y 0
z 1
Obj: 3.000000e+00 %目标值