9.1. 模型文件¶
文件类型 |
说明 |
格式类型 |
目的 |
|---|---|---|---|
普通的MPS格式文件
MPS格式文件,但隐含它是一个QP问题
MPS格式文件,但它的变量和约束被重命名过
MPS格式文件,但它写出的是对偶问题
|
MPS |
紧凑,高效的存储模型 |
|
LP |
便于人类阅读,检查 |
可以看见,同一种 格式类型 下包含了多种 文件类型 。比如 MPS格式 就有四种 文件类型。
MindOpt 读取模型时,同一种 格式类型 下的 文件类型 没有任何区别。 而 MindOpt 在写文件时,不同的 文件类型,会将不同的模型写出到文件。
也即是说,写文件时,文件类型 对应数据内容,而 格式类型 对应数据格式。
9.1.1. MPS文件格式¶
MPS格式是业界最流行的模型存储格式,相比于LP文件格式,它有以下优点:
存储更紧凑。同样的问题,MPS通常比LP文件更小。
对变量或约束的名称,容忍度更高,限制更少。
解析器友好。同样的问题,读入MPS格式通常比读入LP格式更快。
表达能力传统上更强。尽管 LP 格式后来通过非标准扩展增强了表达能力,可在某些方面接近 MPS,但 MPS 仍是更通用的标准。
MPS有两种形式:固定格式(fixed format)和自由格式(free format)。固定格式要求文件中的字段, 必须在固定的位置开始,在固定的位置结束。可以看出,这限制了字段的长度,比如一个实体名称,被允许最多8个字符。 很明显,固定格式是很传统的,而自由格式更加现代。固定格式看起来更加美观,自由格式表达能力更强。
一般来说,自由格式是兼容固定格式的,MindOpt 以自由格式来读入和输出模型。
备注
标准固定格式中,虽然变量和约束名称只有8个字符,但它可以包含空格。然而,MindOpt 不支持名称中包含空格的情况。
让我们先来观察一个简单的MPS文件。
1* mps example
2NAME foo FREE
3OBJSENSE
4 MAX
5ROWS
6 N OBJ
7 L R0
8 L R1
9 L R2
10COLUMNS
11 C0 OBJ 1 R0 10
12 C0 R1 1 R2 1
13 C1 OBJ 3 R0 1
14 C1 R1 10 R2 1
15RHS
16 RHS R0 10 R1 10
17 RHS R2 1.5
18ENDATA
通过观察,我们发现:
由 * 开始的行,看起来属于注释。事实上,只有当 * 出现在行首时,才会被当做注释。
从行的缩进来看,好像一个MPS是由多个“段”组成的。事实上,只有段名称、ENDATA、以及 * 才可以无缩进的出现在行首,其他的内容都要归属到“段”里。“段”内的行必须有缩进,但缩进的空格数量无限制。
MPS文件里,有一些看起来像是保留字(如 MAX),有些像是名称(如 C0),有些则是数值。
ENDATA 指示了文件的结束。
由于MPS是以出现的位置来解析字段名称的(而LP解析时,一定程度上依赖上下文),所以它对变量名称和约束名称,除了不能包含空格外,几乎没有限制。
一个MPS文件以 ENDATA 结尾,它可能包含以下段中的部分或全部:
段名称 |
出现顺序 |
必须 |
说明 |
|---|---|---|---|
1 |
是 |
模型名称 |
|
2 |
是 |
优化方向 |
|
3 |
是 |
约束列表以及不等式方向 |
|
4 |
是 |
变量以及它关联的约束列表 |
|
ROWS后 |
约束的右值 |
||
ROWS后 |
约束的区间长度 |
||
COLUMNS后 |
变量的边界 |
||
COLUMNS后 |
二次目标矩阵(上三角) |
||
COLUMNS后 |
二次目标矩阵 |
||
COLUMNS后 |
二次约束 |
||
COLUMNS后 |
SOS约束 |
||
COLUMNS后 |
Indicator约束 |
9.1.1.2. OBJSENSE section¶
OBJSENSE
MAX
OBJSENSE段记录了模型的优化方向,可选的两个保留字为:
MAX:优化方向为目标函数最大化
MIN:优化方向为目标函数最小化
9.1.1.3. ROWS section¶
ROWS
N OBJ
L R0
L R1
L R2
ROWS段列举了所有的约束名称,以及它的不等式方向。特别的,第一个不等式方向为 N 的约束,会被当成目标函数。
以上例子,描述了3个约束和一个目标函数,这3个约束的不等式符号都是 \(\le\)。下表列举了所有可能出现的不等式方向。
保留字 |
不等式符号 |
|---|---|
L |
小于等于 |
G |
大于等于 |
E |
等于 |
N |
目标函数名称 |
如果模型是单目标模型,则只有第一个 N 标注的名称会被视为目标函数。但如果模型是多目标优化模型,则可以有多个 N 标记。如:
N OBJ0 0 1 1e-06 0
N OBJ1 -1 1 0.2 0
描述了两个目标,OBJ0 和 OBJ1。出现在目标名称后面的4个数值,分别对应 ObjNPriority, ObjNWeight,ObjNAbsTol 和 ObjNRelTol。 关于多目标优化,请参考 多目标问题求解。
9.1.1.4. COLUMNS section¶
COLUMNS
C0 OBJ 1 R0 10
C0 R1 1 R2 1
C1 OBJ 3 R0 1
C1 R1 10 R2 1
COLUMNS段描述了目标函数表达式(不包括常数项),以及约束矩阵。
除开目标函数比较特殊以外,COLUMNS段其实描述了一个 Compressed sparse column 结构。 它的每一行,以一个变量名称开头,可以包含一个或者两个 项(Term)。
上述例子中,COLUMNS段描述了两个变量,以及它们在三个约束中的系数,总共6个项。
另外目标函数为:\(1 \times C0 + 3 \times C1\)
备注
COLUMNS段的一行,只能包含一个或者两个项。
备注
COLUMNS段中,相同变量开头的多行,必须紧邻。
9.1.1.5. RHS section¶
RHS
RHS R0 10 R1 10
RHS R2 1.5
RHS段,描述了约束的右侧值。
每一行由一个名称开始,MindOpt 忽略这个名称。然后是对约束的右侧值表述。如上例中,表述了三个约束的 右侧值。
特别的,RHS段中,目标函数名称对应的右侧值,为目标函数常数项的相反数。 直觉上,我们可以这样理解:
备注
每一行中只能有一个或者两个右侧值描述。
备注
如果在ROWS段中出现的约束,在这里没有出现,则它的右侧值默认为0。
9.1.1.6. RANGES section¶
RANGES
RANGE0 R0 10 R1 10
RANGES段,它遵循和RHS段一样的格式语法,即首先是一个会被 MindOpt 忽略的名称,之后是一个或者两个对约束区间长度的描述。 作为RHS段的补充,通过描述约束区间长度的方式,为约束提供左侧值。 比如:有一个约束 R0
在 ROWS 段中,R0 的不等式符号是 \(\le\)
在 RHS 段中,R0 的右侧值为 5
在 RANGES 段中,R0 的区间长度为 9
则 R0 约束的界为:
假设约束目前的下界为 \(l\),其上界为 \(u\),在RANGES段中,约束有值为 \(v\), 则约束的上下界,会按照下列表格更新。
约束不等号 |
\(v\) 的符号 |
约束下界(\(l\)) |
约束上界(\(u\)) |
|---|---|---|---|
E |
\(-\) |
\(u + v\) |
|
E |
\(+\) |
\(l + v\) |
|
G |
\(+/-\) |
\(u + |v|\) |
|
L |
\(+/-\) |
\(u - |v|\) |
备注
和RHS段一样,每一行中只能有一个或者两个区间长度值描述。
9.1.1.7. BOUNDS section¶
BOUNDS段,描述了模型中变量的上下界。变量默认的边界为 \([0, \infty)\),BOUNDS段提供的值, 用来更新变量的上下界。如:
BOUNDS
UP BND1 x0 1
UP BND1 x1 1
UP BND1 x2 1
BOUNDS段的一行,分别由一个界描述符,一个会被 MindOpt 忽略的名称,一个变量名称,以及一个值来组成。 如上面的例子,描述了3个连续变量 x0,x1 和 x2 ,它们的上界都为 1。
界描述符是一套约定的保留字,它规定如何用 值 更新变量,以及是否改变变量的类型(是否转换为整型变量)。 假设当前的 值 为 \(v\),所有可能出现的界描述符,以及对应的规则见下表。
界描述符 |
下界 |
上界 |
改变变量类型 |
|---|---|---|---|
FR |
\(-\infty\) |
\(\infty\) |
|
FX |
\(v\) |
\(v\) |
|
LO |
\(v\) |
||
MI |
\(-\infty\) |
||
PL |
\(\infty\) |
||
UP |
\(v\) |
||
BV |
0 |
1 |
转整型 |
LI |
\(v\) |
转整型 |
|
UI |
\(v\) |
转整型 |
备注
由上表得知,FR,MI 以及 PL,是不需要 \(v\) 值的。当界描述符是这三者之一时,一行可以只有3个字段,后续即使还有其他字段也会被 MindOpt 忽略。
备注
同一个变量,可以被描述多次。当对一个变量的描述有多行时,如果把这些行重新排序,可能会影响最后的结果。
9.1.1.8. QUADOBJ section¶
QUADOBJ段,描述了模型中的二次目标,它以 Coordinate matrix 的格式,记录二次目标的系数矩阵(通常称 \(Q\) 矩阵)。 由于二次目标的系数矩阵是对称的(甚至通常是正定的),QUADOBJ段只记录它的上三角。
二次规划的标准型中,目标函数如下:
我们假设模型有两个变量,分别为 x 和 y,我们有如下描述:
QUADOBJ
x x 2.0
x y -1.0
则对称矩阵复制上三角后,标准型中的 \(Q\) 矩阵为:
由于标准型是中有系数 \(\frac{1}{2}\),所以实际建模时对应的目标函数表达式为:
即
9.1.1.9. QMATRIX section¶
QMATRIX段,和QUADOBJ段几乎一样。唯一不同的是,它不只记录 \(Q\) 矩阵的上三角,而是记录完整的 \(Q\) 矩阵。 很显然,QMATRIX段的存在只是为了兼容性。QUADOBJ段更高效,更紧凑。
备注
一个MPS文件中,不应该同时出现QUADOBJ段和QMATRIX段。
9.1.1.10. QCMATRIX section¶
QCMATRIX段,可以有多个,每一个QCMATRIX段,描述一个二次约束中的系数矩阵。如:
QCMATRIX qc1
x x 2.0
x y -1.0
y x -1.0
可以观察到,每个QCMATRIX段,都要关联一个已知的约束(即ROWS段中出现过的约束名称),如本例中的 qc1。 之后则是该约束中的二次项系数矩阵。和QMATRIX段相同,和QUADOBJ段不同的是,QCMATRIX也不利用对称性,它完整的记录了二次约束项的系数矩阵。
注意,和QUADOBJ段不一样的是,QCMATRIX往往直接记录系数,所以,这个约束的建模表达式为
9.1.1.11. SOS section¶
SOS段描述了模型中的SOS约束。
SOS
S2 sos1
x1 1
x2 2
S1 sos2
x2 2
x3 3
可以观察到,SOS段里有不同层次的缩进。实际上,SOS段又被分成了多个小段,每个小段描述一条SOS约束。
如第一个小段
S2 sos1
x1 1
x2 2
第一行中,S2 表示这个约束是SOS2约束(另一个可能的值是 S1,表示SOS1约束),约束的名称为 sos1。
保留字 |
SOS约束类型 |
含义 |
|---|---|---|
S1 |
Special Ordered Set of Type 1 |
最多只有一个变量可以取非零值 |
S2 |
Special Ordered Set of Type 2 |
按权重排序后,最多只有两个相邻的变量可以取非零值 |
接下来的行,列举了SOS约束中的变量,以及变量对应的权重。
9.1.1.12. INDICATORS section¶
INDICATORS段,描述了模型中的指示器约束。
INDICATORS
IF row1 x 0
IF row2 y 1
INDICATORS段的每一行,描述一个指示器约束。如
IF R0 x 0
描述的指示器约束为:当变量 x 取得 0 值时,约束 R0 生效。其中:
R0 必须是在ROWS段中出现的约束名称,并且必须是线性约束
x 必须是在COLUMNS段中出现的变量名称
最后一个字段必须是 0 或者 1
9.1.2. QPS文件¶
可以简单理解为,QPS是MPS的一个别名,它们没有任何区别。
我们约定(但不必须)一个 .qps 文件,存储的是QP或QCP问题(即包含段 QUADOBJ,QMATRIX, QCMATRIX 中的一个或多个)。 但并不意味着所有的QP和QCP问题,写出文件都必须以 .qps 作为扩展名。
可以理解为:QPS文件存在的意义,仅仅是定义了一种命名约定。
9.1.3. REW文件¶
即是将变量和约束重命名后,写出的MPS格式文件。
它有两个主要的典型用途:
数据脱敏。数据的业务含义可能隐含在变量或约束名称里,重命名为无意义的名称,可以隐藏敏感信息。
过滤非法名称。当变量和约束包含非法字符时,某些求解器可能无法读入。此时如果写出REW格式文件,则几乎可以让所有求解器读入。
REW如何重命名变量和约束呢?
我们假设一个变量的索引为 i,有 n 个约束引用了该变量,当:
它是一个连续变量时,它被命名为:C{i}({n}),如 C0(7)
它是一个一般整型变量时,它被命名为:I{i}({n})
它是一个二元变量时,它被命名为:B{i}({n})
而假设一个约束的索引为 j,则它的名称即是 c{j}。 可以看出,一个约束的名称仅仅和它的索引号有关,如 c7 表示它是第8个约束。
9.1.4. DUA文件¶
写出一个DUA文件,即是将当前问题的对偶问题,写出为REW文件。
对偶问题的变量,对应原问题的约束。 而对偶问题的约束,对应原问题的变量。 然而实际操作中会发现,写出的对偶问题,和原问题的行列数未必能对应起来。
事实上,我们只保证,如果原问题有 N 个约束。则DUA格式文件里的 前 N 个变量与它们一一对应。 而确定前 N 个变量的最安全方式,是分析变量名中出现的索引号(参考REW格式命名规则)。
9.1.5. LP文件格式¶
虽然MPS格式相对LP格式有更多的优点,但LP格式是一种便于阅读的格式。如果需要观察模型结构,或排查模型问题时,输出LP格式可以更加高效的完成工作。 LP格式和MPS最大的不同在于,它使用算术表达式来描述目标函数、约束、以及各种界信息等等。
一个简单的LP文件示例如下所示:
\Problem name:
Maximize
OBJ: C0 + 3 C1 + 10
Subject To
R0: 10 C0 + C1 <= 10
R1: C0 + 10 C1 <= 10
R2: C0 + C1 <= 1.5
Bounds
End
它看起来非常直观,它描述的模型为:
备注
MindOpt 支持在目标函数中设置常数项。但行业内的一些求解器如Gurobi,却不支持。使用这些求解器时,上例中的 10 会被误认为变量名称处理。 并赋予其默认边界 :math:[0, infty),导致模型求解结果为 Unbounded。
从以上例子中,我们可以看出:
LP文件中,以 '\' 开头的行,会被当做注释处理
LP文件是由多个段组成的,以 End 来指示文件结束
LP文件中,有一些有特殊意义的标点符号,如 ':'、'+' 等
LP文件中,我们规定变量和约束的名称,必须要符合一些要求,包括:
不能包含空格
不能包含':'和'^'
不能是一个合法的数字,如 '0','.0','0.','1.2E3','nan','inf' 等
不能以下列字符开头:'*', '+','-','<','>','=','[',']'
不能和关键字同名,或和关键字中的token同名(有些关键字包含以空格连接的多个token)
虽然LP文件通常有不同的缩进,可以表示段的层次结构,但由于LP文件相对自由,甚至很多时候是手写的, MindOpt 不使用缩进。 而使用关键字来开始和结束段,MindOpt 要求变量和约束的名称,不能和关键字重复(忽略大小写)。关键字如下:
指示目标函数段开始的关键字
最小化优化:minimize,minimum,min
最大化优化:maximize,maximum,max
指示约束段开始的关键字:subject to,such that,s.t.,st
指示变量上下界段开始的关键字:bounds,bound
指示SOS段开始的关键字:sos
指示变量类型段开始的关键字
二元变量:binary,binaries
一般整数变量:general,generals,integer,integers
半连续型变量:semi-continuous,semis,semi
指示文件结束的关键字:end
9.1.5.1. Objective section¶
一个简单的示例如下:
Maximize
OBJ: C0 + 3 C1 + 10
Objective段,以一个描述优化方向的关键字开头,除了 maximize 外,其他的支持的关键字有: maximum,max,以及 minimize,minimum,min。
接下来的一行或多行,描述目标函数的表达式。它包含一个可选的目标函数名称,以 : 结尾,: 两边 的空格都是可选的,以下的目标函数表达式都是合法的。
3 X + Y
:3 X + Y
: 3 X + Y
: 3 X + Y
OBJ:3 X + Y
OBJ :3 X + Y
OBJ : 3 X + Y
名称之后,是真正的算术表达式,多个 项(term) 由 '+' 或 '-' 连接起来。每个项包含一个可选的系数(若无系数则系数为1),和一个可选的变量名,但二者必须有一个。
备注
上文已经提到,变量名不能是合法的数字表达式,否则在目标函数中会引起二义性,MindOpt 会将其优先理解为常数项。
9.1.5.1.1. 二次目标¶
二次目标的支持,属于对标准LP格式的扩展。一个简单的例子如下:
Maximize
OBJ: 3 X + Y + [ X ^ 2 + 2 X * Y + 3 Y ^ 2 ] / 2 + 10
以上例子中,'['和']'之间的部分,即是二次目标,二次目标的书写必须满足以下条件:
必须出现在所有一次项之后,常数项之前
'[' 和 ']',以及 '*' 前后,必须有空格
'^' 后面只能是 '2', '^' 和 '2' 之间的空格是可选的
']' 后必须有 '/ 2', '/' 和 '2' 之间必须有空格
9.1.5.1.2. 多目标¶
多目标也是对标准LP格式的扩展,一个简单的例子如下:
Maximize multi-objectives
OBJ0: Priority=1 Weight=1 AbsTol=0 RelTol=0
X + Y
OBJ1: Priority=2 Weight=1 AbsTol=0 RelTol=0
X - Y
多目标优化,需要在优化方向的关键字后,紧跟一个 'multi-objectives'。接下来描述多个目标, 每个目标都要先描述 Priority,Weight,AbsTol,RelTol 4个参数的取值。表达式需要从下一行开始。
备注
既不出现在目标函数中,也不出现在约束中的变量名称,将被忽略。如果真的需要这些变量,可以在目标函数中,添加系数为 0 的项。
9.1.5.2. Constraints section¶
约束段,以以下关键字开始:subject to,such that,s.t.,st。
Subject To
R0: 10 C0 + C1 <= 10
R1: C0 + 10 C1 <= 10
R2: C0 + C1 + [ C0 * C1 ] <= 1.5
和目标函数一样,每条约束可以有一个可选的名称,一条约束也可以跨多行。不等式的左侧,几乎遵循和目标函数表达式一样的规则,除了
不能有常数项
二次约束中,二次表达式结束后,不需要'/ 2'
而不等式的右侧,必须是一个常数。可选的不等号有:>=,<=,==,>,<,=。其中,> 和 < 并不是严格的 大于 和 小于,它只是 >= 和 <= 的别名。
特别的,模型中的Indicator约束,我们也放在约束段中,这也是对标准LP格式的扩展。它仅仅是在线性约束的前面,增加了一个指示器部分:
R0: X = 0 -> C0 + C1 <= 0
其中 X = 0 -> 就是指示器部分,它必须出现在约束名称之后,线性约束表达式之前,它表示当整数变量 X 取 0 值时,激活线性约束 C0 + C1 <= 0。
备注
即不出现在目标函数中,又不出现在约束中的变量名称,将被忽略。
9.1.5.3. Bounds section¶
以下是一个Bounds段的例子。
Bounds
x <= 0
y free
-inf <= z <= 0
Bounds段描述模型中变量的边界。变量的默认下边界是 0, 默认的上边界是 \(\infty\)。 Bounds段中的每一行,描述对某个变量边界的修改。它包括以下几种类型(其中 \(l\) 和 \(u\) 为常量,\(x\) 为变量名称):
修改双边型
\[\begin{split}\begin{align} l \le &x \le u \\ u \ge &x \ge l \\ &x = l \\ &l = x \\ &x \space free \end{align}\end{split}\]修改单边型(另一边维持默认值)
\[\begin{split}\begin{align} x &\le u \\ x &\ge l \\ l &\le x \\ u &\ge x \end{align}\end{split}\]
备注
使用“修改单边型”表达式,将上边界设置为一个负数将导致变量没有可行域。
即: x <= -1 会被理解为 0 <= x <= -1
如果变量无下边界,正确的写法是:-inf <= x <= -1
9.1.5.4. SOS section¶
SOS段描述了模型中的SOS约束。
SOS
sos1: S1 :: x : 1 y : 2
sos2: S2 :: y : 1 z : 3
SOS段每行一个约束,以一个约束名称开始(对于SOS约束,名称是必要的),紧跟一个约束类型的保留字:
S1:Special Ordered Set of Type 1
S2:Special Ordered Set of Type 2
之后紧跟两个连续的':',即'::',然后是多个变量的权重列表,变量和权重之间以':'分隔。
备注
和目标函数与约束不一样的是,SOS约束必须有约束名称。
9.1.5.5. Variable type sections¶
变量类型段可以有多个,不同的类型都可以有一个变量类型段,即最多可以有3个变量类型段。分别为:
二元变量类型。以 binary,binaries 作为段的开始。
一般整型类型。以 general,generals,integer,integers 作为段的开始。
半连续型类型。以 semi-continuous,semis,semi 作为段的开始。
未被任何变量类型段提及的变量,默认为连续型变量。
变量类型段格式很简单,每一行一个变量。如以下的例子包含两个变量类型段,声明了两个二元变量,和一个一般整型变量
Binaries
x
y
Generals
z
9.1.7. RLP文件¶
即是将变量和约束重命名后,写出的LP格式文件。
它有两个主要的典型用途:
数据脱敏。数据的业务含义可能隐含在变量或约束名称里,重命名为无意义的名称,可以隐藏敏感信息。
过滤非法名称。当变量和约束包含非法字符时,某些求解器可能无法读入。此时如果写出RLP格式文件,则几乎可以让所有求解器读入。
RLP如何重命名变量和约束呢?和REW文件是类似的。
我们假设一个变量的索引为 i,有 n 个约束引用了该变量,当:
它是一个连续变量时,它被命名为:C{i}({n}),如 C0(7)
它是一个一般整型变量时,它被命名为:I{i}({n})
它是一个二元变量时,它被命名为:B{i}({n})
而假设一个约束的索引为 j,则它的名称即是 c{j}。 可以看出,一个约束的名称仅仅和它的索引号有关,如 c7 表示它是第8个约束。
9.1.8. DLP文件¶
写出一个DLP文件,即是将当前问题的对偶问题,写出为RLP文件。
对偶问题的变量,对应原问题的约束。 而对偶问题的约束,对应原问题的变量。 然而尝试一下会发现,写出的对偶问题,和原问题的行列数未必能对应起来。
事实上,我们只保证,如果原问题有 N 个约束。则DLP格式文件里的 前 N 个变量与它们一一对应。 而确定前 N 个的方式,是分析变量名中出现的索引号(参考RLP格式命名规则)。