1.引言
目前发现HiGHS这一新的完全开源的求解器,在实际使用过程中比SCIP性能要好,但调用起来较为不方便,一是因为官方的教学文档没有写完,二是便捷的方法很少,例如quicksum、expr表达式等。
那么在研究实际问题中,我们更关注求解时间,也就可以手动排除一些障碍,并且经常尝试一些新的东西。网络上除官方文档外对这个求解器的调用方法的相关描述几乎没有,所以我就有幸成为第一批吃螃蟹的人了。
2.HiGHS官网
HiGHS的官方网站就放在这里了,具体的简介可以到官网里面去查:HiGHS - High-performance parallel linear optimization software (ed.ac.uk)
3.进入正题
3.1HiGHS安装
基于Python语言的好处是可以使用pip命令安装HiGHS。因此之间在Python环境中:
pip install highspy
那么highspy就安装到您的环境中了,并且由于HiGHS是完全开源的,因此不用担心自己下错版本。
3.2HiGHS调用
我们首先给出官方文档中的例子:
首先导入包。
import highspy
import numpy as np
然后是初始化highspy的方法
h = highspy.Highs()
第一个例子给出的是读取mps模型文件,读取其他类型文件(.lp)也一样,改后缀即可:
import highspy
h = highspy.Highs()
filename = 'model.mps'
h.readModel(filename)
h.run()
print('Model ', filename, ' has status ', h.getModelStatus())
官方的第二个例子是建立模型方法:
minimize f = x0 + x1
subject to
x1 <= 7
5 <= x0 + 2x1 <= 15
6 <= 3x0 + 2x1
0 <= x0 <= 4; 1 <= x1
给出的代码如下:
#此处获取HighsInf定义的极大值。
inf = highspy.kHighsInf
#定义两个变量x1、x2,先使用的标识符和使用的常量。
# Define two variables, first using identifiers for the bound values,
# and then using constants
lower = 0#下界>=0
upper = 4#上界<=4
h.addVar(lower, upper)#添加变量,第一个参数为变量的最小值,第二个参数为变量的最大值
h.addVar(1, inf)#同理
# Define the objective coefficients (costs) of the two variables,
# identifying the variable by index, and changing its cost from the
# default value of zero
#定义目标函数
cost = 1#目标函数前面的系数
h.changeColCost(0, cost)#函数的第一个参数为第几个变量的系数,第二个参数为系数值。
h.changeColCost(1, 1)
#定义约束
# Define constraints for the model
# The first constraint (x1<=7) has only one nonzero coefficient,
# identified by variable index 1 and value 1
num_nz = 1
index = 1
value = 1
h.addRow(-inf, 7, num_nz, index, value)#第一个参数为下界,第二个参数为上界,第三个参数为约束中的变量个数,第四个参数为约束中变量的索引,即第几个添加进入的变量,是一个list。value为系数值list。
# The second constraint (5 <= x0 + 2x1 <= 15) has two nonzero
# coefficients, so arrays of indices and values are required
num_nz = 2
index = np.array([0, 1])
value = np.array([1, 2])
h.addRow(5, 15, num_nz, index, value)
# The final constraint (6 <= 3x0 + 2x1) has the same indices but
# different values
num_nz = 2
value = np.array([3, 2])
h.addRow(6, inf, num_nz, index, value)
# Access LP
lp = h.getLp()
num_nz = h.getNumNz()
print('LP has ', lp.num_col_, ' columns', lp.num_row_, ' rows and ', num_nz, ' nonzeros')
#调用h.run()求解模型
h.run()
相信看到这大家已经明白了,HiGHS确实有种新的感觉,因为面向用户太差了,但速度快且开源这一优点太棒了。上述代码中模型的约束和变量是一个一个添加的,可能大家觉得这样很麻烦,官方给出了一次添加多行多列的方法:
inf = highspy.kHighsInf#定义极大值
# The constraint matrix is defined with the rows below, but parameters
# for an empty (column-wise) matrix must be passed
cost = np.array([1, 1], dtype=np.double)#定义目标函数系数
lower = np.array([0, 1], dtype=np.double)#定义约束的下界
upper = np.array([4, inf], dtype=np.double)#定义约束的上界
num_nz = 0
start = 0
index = 0
value = 0
h.addCols(2, cost, lower, upper, num_nz, start, index, value)#第一个参数表示添加两列,第二个参数表示添加的cost系数,第三、四个参数表示变量的上下界,第五、六、七、八约束必须得跳过,因为就很无奈,定义这个列的时候还没有行。
#所以这个地方吐槽一下,明明有h.addVars()可以用,你用这个addCols()的好处就只有把目标函数定义了。
#然后添加约束行
# Add the rows, with the constraint matrix row-wise
lower = np.array([-inf, 5, 6], dtype=np.double)#三个约束的上下界
upper = np.array([7, 15, inf], dtype=np.double)
num_nz = 5
start = np.array([0, 1, 3])
index = np.array([1, 0, 1, 0, 1])
value = np.array([1, 1, 2, 3, 2], dtype=np.double)
#这个地方太抽象了,为啥要这么搞数据接口呢,直接就在这放了个稀疏化表达方式,太难用了。
#第一个参数表示约束数量,第二、三个参数表示约束的上下界,第四个参数表示三个约束中用到的变量个数,第四个参数表示每行的起始位置,要根据这个值在index和value中找。index和value就不多解释了。
h.addRows(3, lower, upper, num_nz, start, index, value)
看到这已经让人很破防了,可能HiGHS自己也看不下去了,于是搞了个《Pass a model》,其意思大概就是给用户提供了个模板,然后直接修改这个模板的参数就完成了建模,有点没头没脑的大家一般也用不到。
#调用求解器求解
h.run()
#输出各种结果。
solution = h.getSolution()
basis = h.getBasis()
info = h.getInfo()
model_status = h.getModelStatus()
print('Model status = ', h.modelStatusToString(model_status))
print()
print('Optimal objective = ', info.objective_function_value)
print('Iteration count = ', info.simplex_iteration_count)
print('Primal solution status = ', h.solutionStatusToString(info.primal_solution_status))
print('Dual solution status = ', h.solutionStatusToString(info.dual_solution_status))
print('Basis validity = ', h.basisValidityToString(info.basis_validity))
3.3心得体会(轻松一下)
开源:很棒
求解时间:很快
使用感受:心态爆炸
如果你有耐心看到这里,那么感谢您。作为回礼,我将展示一些高级的HiGHS的用法,目前文档中没有写完,好像也没有写的计划。
4.HiGHS使用方法初探
4.1HiGHS特点
在官方给出的文档中我们可以看出,与Cplex、COPT、SCIP等其他求解器不同的是,HiGHS求解器在整个建模过程中仅仅维护了一个模型。在此过程中我们也可以发现一些问题和不足:
- 变量名称无法定义
- 变量无法直接调用,也就无法通过表达式等方式添加到模型中。
- 模型建立的时候,变量作为了一个列进行添加,通俗来讲,仅能通过添加的次序作为变量的索引建立模型。
- 无法定义整数型、布尔型等类型的变量。
4.2解决方法
对于以上问题,笔者给出了自己的解决方法,如下所示。
首先,应当完善变量的索引值列表,手动记录:
x={}#x变量索引字典,可添加类似x[i,j]状态的变量。
vdx=0#当前变量的数量、也可以是新添加变量的添加次序索引
A,B=4,6#比如添加一个4x6的二维变量X[i][k]
for i in range(A):
for k in range(B):
h.addVar(0,1)#添加一个0~1的连续变量
x[i,k]=vdx
vdx+=1
#那么此时,x[i,k]中就记录了vdx的值,即变量的索引值。
当获取了索引之后,我们便可以更方便的修改所有x变量的类型,需要调用changeColsIntegrality()函数,这个函数还是我用help(h)查找类中方法得到的,甚至没有用法。
#修改所有x变量的类型由连续型变为整数型
h.changeColsIntegrality(len(x),[x[i] for i in x],[1 for i in range(len(x))])
#其中第一个参数为改变类型的变量的数量,第二个参数为变量的索引,第三个参数为变量的类型代码。
这第三个参数的变量类型代码我也在其类里面找到了,如下所示:
#可使用如下命令看到内部的变量类型。
help(highspy.highs_bindings.HighsVarType)
连续变量:kContinuous = <HighsVarType.kContinuous: 0>
整型变量:kInteger = <HighsVarType.kInteger: 1>
半连续变量:kSemiContinuous = <HighsVarType.kSemiContinuous: 2>
半整型变量:kSemiInteger = <HighsVarType.kSemiInteger: 3>
怕大家也和我一样不太懂这个半是什么意思,给出IBM CPLEX的解释:
给出IBM的解释:
半连续变量:
何为半连续变量? 定义半连续变量。 半连续变量是缺省情况下可以采用值 0 或任何介于其半连续下限 (sclb) 与其上限 (ub) 之间的值的变量。 半连续下限 (sclb) 必须是有限值。 上限 (ub) 不必是有限值。 半连续下限 (sclb) 必须大于或等于 0。
当有了索引列表,我们就可以使用表达式的方法了:
#我们假设还定义了另一个变量y,要实现x[i]+y[j]<=10这一表达式。
lsh=[]#表达式的索引
for i in x:
if x[i]%2==1:#假定的条件,取奇数索引建立模型。
lsh+=[x[i]]
for i in y:
if y[i]%2==0:#同理,但已经是不同的变量相加了。
lsh+=[y[i]]
value=[1 for i in range(len(lsh))]#随便定义一下约束里面的系数。
#此处调用
h.addRow(inf,10,len(lsh),lsh,value)#前两个参数就不多解释了,第三个参数是约束中的变量数量,第四个参数是变量索引,第五个参数是变量前的系数。
如上所示,再加上for循环逐行添加,是不是用起来更舒服了呢?