9.1. 模型文件

文件类型

说明

格式类型

目的

普通的MPS格式文件
MPS格式文件,但隐含它是一个QP问题
MPS格式文件,但它的变量和约束被重命名过
MPS格式文件,但它写出的是对偶问题

MPS

紧凑,高效的存储模型

普通的LP格式文件
LP格式文件,但它写出的是计算 IIS 后的松弛模型
LP格式文件,但它的变量和约束被重命名过
LP格式文件,但它写出的是对偶问题

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 结尾,它可能包含以下段中的部分或全部:

段名称

出现顺序

必须

说明

NAME

1

模型名称

OBJSENSE

2

优化方向

ROWS

3

约束列表以及不等式方向

COLUMNS

4

变量以及它关联的约束列表

RHS

ROWS后

约束的右值

RANGES

ROWS后

约束的区间长度

BOUNDS

COLUMNS后

变量的边界

QUADOBJ

COLUMNS后

二次目标矩阵(上三角)

QMATRIX

COLUMNS后

二次目标矩阵

QCMATRIX

COLUMNS后

二次约束

SOS

COLUMNS后

SOS约束

INDICATORS

COLUMNS后

Indicator约束

9.1.1.1. NAME section

NAME          foo     FREE

NAME段记录了模型的名称,以及模型的输出格式,MindOpt 忽略模型格式,全部当做自由格式处理。

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

描述了两个目标,OBJ0OBJ1。出现在目标名称后面的4个数值,分别对应 ObjNPriorityObjNWeightObjNAbsTolObjNRelTol。 关于多目标优化,请参考 多目标问题求解

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个项。

\[\begin{split}R0: 10 \times C0 + 1 \times C1 \\ R1: 1 \times C0 + 10 \times C1 \\ R2: 1 \times C0 + 1 \times C1\end{split}\]

另外目标函数为:\(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段中,目标函数名称对应的右侧值,为目标函数常数项的相反数。 直觉上,我们可以这样理解:

\[\begin{split}\begin{align} OBJ: &\sum_{i}x_{i} &\quad ineq &\quad RHS \\ => &\sum_{i}x_{i} - RHS &\quad ineq &\quad 0 \end{align}\end{split}\]

备注

每一行中只能有一个或者两个右侧值描述。

备注

如果在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 约束的界为:

\[-4 \le R0 \le 5\]

假设约束目前的下界为 \(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个连续变量 x0x1x2 ,它们的上界都为 1

界描述符是一套约定的保留字,它规定如何用 更新变量,以及是否改变变量的类型(是否转换为整型变量)。 假设当前的 \(v\),所有可能出现的界描述符,以及对应的规则见下表。

界描述符

下界

上界

改变变量类型

FR

\(-\infty\)

\(\infty\)

FX

\(v\)

\(v\)

LO

\(v\)

MI

\(-\infty\)

PL

\(\infty\)

UP

\(v\)

BV

0

1

转整型

LI

\(v\)

转整型

UI

\(v\)

转整型

备注

由上表得知,FRMI 以及 PL,是不需要 \(v\) 值的。当界描述符是这三者之一时,一行可以只有3个字段,后续即使还有其他字段也会被 MindOpt 忽略。

备注

同一个变量,可以被描述多次。当对一个变量的描述有多行时,如果把这些行重新排序,可能会影响最后的结果。

9.1.1.8. QUADOBJ section

QUADOBJ段,描述了模型中的二次目标,它以 Coordinate matrix 的格式,记录二次目标的系数矩阵(通常称 \(Q\) 矩阵)。 由于二次目标的系数矩阵是对称的(甚至通常是正定的),QUADOBJ段只记录它的上三角。

二次规划的标准型中,目标函数如下:

\[\frac{1}{2}x^T Q x + C^T x\]

我们假设模型有两个变量,分别为 xy,我们有如下描述:

QUADOBJ
 x        x        2.0
 x        y        -1.0

则对称矩阵复制上三角后,标准型中的 \(Q\) 矩阵为:

\[\begin{split}\begin{bmatrix} 2 & -1 \\ -1 & 0 \end{bmatrix}\end{split}\]

由于标准型是中有系数 \(\frac{1}{2}\),所以实际建模时对应的目标函数表达式为:

\[x^2 - \frac{1}{2} xy - \frac{1}{2} yx\]

\[x^2 - xy\]

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往往直接记录系数,所以,这个约束的建模表达式为

\[2x^2 - 2xy\]

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问题(即包含段 QUADOBJQMATRIXQCMATRIX 中的一个或多个)。 但并不意味着所有的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

它看起来非常直观,它描述的模型为:

\[\begin{split}\begin{align} Maximize \quad &C0 + 3 \cdot C1 + 10 \\ s.t. \quad &10 \cdot C0 + C1 \le 10 \\ &C0 + 10 \cdot C1 \le 10 \\ &C0 + C1 \le 1.5 \\ &0 \le C0 \le \infty \\ &0 \le C1 \le \infty \end{align}\end{split}\]

备注

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 要求变量和约束的名称,不能和关键字重复(忽略大小写)。关键字如下:

  • 指示目标函数段开始的关键字

    • 最小化优化:minimizeminimummin

    • 最大化优化:maximizemaximummax

  • 指示约束段开始的关键字:subject tosuch thats.t.st

  • 指示变量上下界段开始的关键字:boundsbound

  • 指示SOS段开始的关键字:sos

  • 指示变量类型段开始的关键字

    • 二元变量:binarybinaries

    • 一般整数变量:generalgeneralsintegerintegers

    • 半连续型变量:semi-continuoussemissemi

  • 指示文件结束的关键字:end

9.1.5.1. Objective section

一个简单的示例如下:

Maximize
 OBJ: C0 + 3 C1 + 10

Objective段,以一个描述优化方向的关键字开头,除了 maximize 外,其他的支持的关键字有: maximummax,以及 minimizeminimummin

接下来的一行或多行,描述目标函数的表达式。它包含一个可选的目标函数名称,以 : 结尾,: 两边 的空格都是可选的,以下的目标函数表达式都是合法的。

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'。接下来描述多个目标, 每个目标都要先描述 PriorityWeightAbsTolRelTol 4个参数的取值。表达式需要从下一行开始。

备注

既不出现在目标函数中,也不出现在约束中的变量名称,将被忽略。如果真的需要这些变量,可以在目标函数中,添加系数为 0 的项。

9.1.5.2. Constraints section

约束段,以以下关键字开始:subject tosuch thats.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 -> 就是指示器部分,它必须出现在约束名称之后,线性约束表达式之前,它表示当整数变量 X0 值时,激活线性约束 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个变量类型段。分别为:

  • 二元变量类型。以 binarybinaries 作为段的开始。

  • 一般整型类型。以 generalgeneralsintegerintegers 作为段的开始。

  • 半连续型类型。以 semi-continuoussemissemi 作为段的开始。

未被任何变量类型段提及的变量,默认为连续型变量。

变量类型段格式很简单,每一行一个变量。如以下的例子包含两个变量类型段,声明了两个二元变量,和一个一般整型变量

Binaries
 x
 y
Generals
 z

9.1.6. ILP文件

ILP文件,就是为了能让原问题可行,将原问题做松弛后,再写出的LP文件。

备注

只有在问题不可行时,成功计算 IIS 后,才可以写出ILP文件。否则API将返回错误。

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格式命名规则)。