core 设计

linear-program 把变量、线性表达式、目标函数、约束和两阶段单纯形求解器放在 src 下的一个包中。本页给出求解器实现的数学内容,推导其停止判据,并说明代码如何围绕它们组织。

设计目标

  • 让用户以教科书的记法编写线性规划:命名变量、带优化方向的目标函数,以及等式和不等式约束。
  • 用教科书算法——在稠密单纯形表上的两阶段单纯形法——求解,并公开每一步(Lp::pivot、Lp::simplex_iteration、Lp::phase_1、Lp::phase_2),以便跟踪和讲授算法。
  • 通过 luna-generic 的 trait 保持系数类型泛型,并把单纯形表存为 linear-algebra 的矩阵。

数学背景

线性规划与标准形

变量 x∈Rnx \in \mathbb{R}^n 上的线性规划在线性等式与不等式约束下优化一个线性目标。求解器处理的是标准形

min⁡x∈Rn  c⊤xsubject toAx=b,x≥0,A∈Rm×n,  b∈R≥0m.\min_{x \in \mathbb{R}^n} \; c^\top x \quad \text{subject to} \quad A x = b, \quad x \ge 0, \qquad A \in \mathbb{R}^{m \times n},\; b \in \mathbb{R}^m_{\ge 0}.

用本包编写的任何规划都可以在不改变最优解的前提下化为这种形式。Lp::to_standard 执行以下改写:

max⁡  c⊤x=−min⁡  (−c)⊤x,a⊤x=β, β<0⟺(−a)⊤x=−β,a⊤x≤β, β≥0⟺a⊤x+y=β, y≥0,a⊤x≤β, β<0⟺(−a)⊤x−y=−β, y≥0,a⊤x≥β, β>0⟺a⊤x−y=β, y≥0,a⊤x≥β, β≤0⟺(−a)⊤x+y=−β, y≥0.\begin{aligned} \max\; c^\top x &= -\min\; (-c)^\top x, \\ a^\top x = \beta,\ \beta < 0 \quad &\Longleftrightarrow \quad (-a)^\top x = -\beta, \\ a^\top x \le \beta,\ \beta \ge 0 \quad &\Longleftrightarrow \quad a^\top x + y = \beta,\ y \ge 0, \\ a^\top x \le \beta,\ \beta < 0 \quad &\Longleftrightarrow \quad (-a)^\top x - y = -\beta,\ y \ge 0, \\ a^\top x \ge \beta,\ \beta > 0 \quad &\Longleftrightarrow \quad a^\top x - y = \beta,\ y \ge 0, \\ a^\top x \ge \beta,\ \beta \le 0 \quad &\Longleftrightarrow \quad (-a)^\top x + y = -\beta,\ y \ge 0. \end{aligned}

每个等价关系成立,是因为 yy 度量了不等式的松弛量:对于 a⊤x≤βa^\top x \le \beta,令 y=β−a⊤xy = \beta - a^\top x,它非负当且仅当不等式成立。等式乘以 −1-1 不改变任何东西,而不等式乘以 −1-1 会反向,这就是在 β<0\beta < 0 与 β≤0\beta \le 0 的情形中松弛变量符号翻转的原因。在每种情形下,新的右端项都非负,这是第一阶段所需要的。

标准形假定每个变量都满足 x≥0x \ge 0,包括原规划的变量。Variable 中保存的边界不会被使用。

基与基本解

设 AA 的秩为 mm。基是由 mm 个列下标组成的集合 BB,使得子矩阵 ABA_B 可逆;其余下标构成 NN。令非基变量为零即确定了基本解

xN=0,xB=AB−1b,x_N = 0, \qquad x_B = A_B^{-1} b,

当 xB≥0x_B \ge 0 时它是可行的。线性规划基本定理指出:若规划有最优解,则它有最优的基本可行解,因此单纯形法只需访问各个基。11 参见例如 Chvátal,Linear Programming(1983)第 3 章,或 Bertsimas 与 Tsitsiklis,Introduction to Linear Optimization(1997)定理 2.7。

单纯形表与检验数

求解器把规划存为一个 (m+1)×(n+1)(m + 1) \times (n + 1) 的单纯形表:

T=[c⊤0Ab].T = \begin{bmatrix} c^\top & 0 \\ A & b \end{bmatrix}.

第 0 行是目标行,第 1,…,m1, \dots, m 行是约束,最后一列为右端项。在 Trs≠0T_{rs} \ne 0 的位置 (r,s)(r, s) 上的转轴是一步 Gauss–Jordan 消元:将第 rr 行除以 TrsT_{rs},再从其他每一行 ii(包括第 0 行)中减去新第 rr 行的 TisT_{is} 倍。之后第 ss 列成为单位向量 ere_r。这正是 Lp::pivot。

假设一系列转轴已经把基 BB 的各列变成单位向量。每次转轴都是左乘一个可逆矩阵,而第 0 行只被加上过约束行的倍数,因此单纯形表具有如下形式

TB=[c⊤−y⊤A−y⊤bAB−1AAB−1b]T_B = \begin{bmatrix} c^\top - y^\top A & -y^\top b \\ A_B^{-1} A & A_B^{-1} b \end{bmatrix}

其中 y∈Rmy \in \mathbb{R}^m 是某个向量。第 0 行的基列为零,所以 cB⊤−y⊤AB=0c_B^\top - y^\top A_B = 0,即

y⊤=cB⊤AB−1.y^\top = c_B^\top A_B^{-1}.

代入 yy 即得典范单纯形表

TB=[cˉ⊤−zBAˉbˉ],cˉ⊤=c⊤−cB⊤AB−1A,zB=cB⊤AB−1b,Aˉ=AB−1A,bˉ=AB−1b.T_B = \begin{bmatrix} \bar c^\top & -z_B \\ \bar A & \bar b \end{bmatrix}, \qquad \bar c^\top = c^\top - c_B^\top A_B^{-1} A, \quad z_B = c_B^\top A_B^{-1} b, \quad \bar A = A_B^{-1} A, \quad \bar b = A_B^{-1} b.

其中 cˉj\bar c_j 是检验数(约简成本),bˉ=xB\bar b = x_B 是基本解,第 0 行的最后一项是取负的目标值 −zB=−c⊤x-z_B = -c^\top x。这就是 Lp::phase_2 返回 −z-z、而 Lp::two_stage 对最小化目标再次取负的原因。

检验数描述目标沿约束如何变化。对任意满足 Ax=bAx = b 的 xx,解出基变量:xB=bˉ−AB−1ANxNx_B = \bar b - A_B^{-1} A_N x_N。于是

c⊤x=cB⊤xB+cN⊤xN=cB⊤bˉ−cB⊤AB−1ANxN+cN⊤xN=zB+cˉN⊤xN.\begin{aligned} c^\top x &= c_B^\top x_B + c_N^\top x_N \\ &= c_B^\top \bar b - c_B^\top A_B^{-1} A_N x_N + c_N^\top x_N \\ &= z_B + \bar c_N^\top x_N . \end{aligned}

最优性

若 cˉ≥0\bar c \ge 0,则该基本可行解是最优的。 每个可行的 xx 都满足 xN≥0x_N \ge 0,因此由上面的恒等式得 c⊤x=zB+cˉN⊤xN≥zBc^\top x = z_B + \bar c_N^\top x_N \ge z_B,而基本解取到 zBz_B。当第 0 行没有负项时 Lp::simplex_iteration 停止,这正是该判据(cˉB=0\bar c_B = 0 由构造保证)。

选择进基列

若某个 cˉs<0\bar c_s < 0,则将 xsx_s 从 00 增大会使目标以速率 ∣cˉs∣|\bar c_s| 减小。求解器选择最负的检验数,

s=arg min⁡j  cˉj,cˉs<0,s = \operatorname*{arg\,min}_{j} \; \bar c_j, \qquad \bar c_s < 0,

即 Dantzig 规则,相同时取最左列。

比值检验与无界性

将 xsx_s 增大到 t≥0t \ge 0,其他非基变量保持为零。约束迫使 xB(t)=bˉ−t aˉsx_B(t) = \bar b - t\, \bar a_s,其中 aˉs\bar a_s 是 Aˉ\bar A 的第 ss 列,目标变为 zB+t cˉsz_B + t\, \bar c_s。

若 aˉs≤0\bar a_s \le 0,则规划无界。 xB(t)x_B(t) 的每个分量关于 tt 不减,因此对所有 t≥0t \ge 0,x(t)x(t) 都可行,而 zB+t cˉs→−∞z_B + t\,\bar c_s \to -\infty。此时 Lp::simplex_iteration 以 Problem is unbounded abort。

否则,可行性要求对每个 aˉis>0\bar a_{is} > 0 的行有 bˉi−t aˉis≥0\bar b_i - t\, \bar a_{is} \ge 0,因此最大步长为

t∗=min⁡i : aˉis>0bˉiaˉis,t^\ast = \min_{i \,:\, \bar a_{is} > 0} \frac{\bar b_i}{\bar a_{is}},

即最小比值检验。取到最小值的行 rr 出基:其基变量在 t∗t^\ast 处降为零。在 (r,s)(r, s) 上转轴就移到新基 B′=B∖{Br}∪{s}B' = B \setminus \{B_r\} \cup \{s\},其基本解为 x(t∗)≥0x(t^\ast) \ge 0,因此保持可行,其目标值为

zB′=zB+t∗ cˉs≤zB.z_{B'} = z_B + t^\ast\, \bar c_s \le z_B .

相同时求解器取最上面的行。

终止性与退化

若每次转轴都有 t∗>0t^\ast > 0(规划非退化),目标严格下降,基不会重复,方法至多经过 (nm)\binom{n}{m} 次转轴即停止。当出基行的 bˉr=0\bar b_r = 0 时,t∗=0t^\ast = 0:基改变了,但点和目标值都不变。一串这样的退化转轴可能回到先前的基并无限重复。本包实现的“Dantzig 规则 + 最小下标打破平局”已知会在小例子上出现循环。22 E. M. L. Beale,“Cycling in the dual simplex algorithm”,Naval Research Logistics Quarterly 2(1955),给出了一个含三个约束的循环例子。R. G. Bland,“New finite pivoting rules for the simplex method”,Mathematics of Operations Research 2(1977),证明了在候选者中总选择下标最小的进基变量和出基变量可以防止循环。 本包没有防循环规则;一次运行在 max_iterations 次转轴(默认 1000)后停止并打印消息,此时返回的单纯形表不是最优的。

设计决策

带人工变量的两阶段法

问题。 单纯形法需要从一个基本可行解出发,而标准形规划并不自带这样的解。

可选方案。 (a) 大 M 法:在原目标中加入代价为大数 MM 的人工变量。(b) 两阶段法:先用辅助目标找到可行基,再进行优化。(c) 要求用户提供可行基。

选择。 (b)。大 M 法需要一个数值 MM:对该规划足够大,又要足够小,以免在浮点运算中淹没其他系数;两阶段法不需要这样的常数,并且其第一阶段还能判定可行性。

第一阶段。 对每个没有可用单位列的约束行 ii,添加一个在第 ii 行系数为 11 的人工变量 ai≥0a_i \ge 0。设 RR 为这些行的集合。第一阶段求解

min⁡  w=∑i∈Raisubject toAx+∑i∈Raiei=b,x≥0, a≥0.\min\; w = \sum_{i \in R} a_i \quad \text{subject to} \quad A x + \sum_{i \in R} a_i e_i = b, \quad x \ge 0,\ a \ge 0 .

其初始基由人工列和已有的单位列组成,基本解为 xB=b≥0x_B = b \ge 0;由于 to_standard 已使 bb 非负,它是可行的。

原规划可行当且仅当第一阶段的最优值 w∗=0w^\ast = 0。 若 xx 满足 Ax=bAx = b,则 (x,a=0)(x, a = 0) 是第一阶段的可行解且 w=0w = 0,而总有 w≥0w \ge 0,所以 w∗=0w^\ast = 0。反之,w∗=0w^\ast = 0 且 a≥0a \ge 0 迫使 a=0a = 0,因此最优的 xx 满足 Ax=bAx = b。

第一阶段单纯形表的典范形式。 第一阶段目标的第 0 行起初为 [ 0∣1⊤∣0 ][\,0 \mid \mathbf 1^\top \mid 0\,];由于基中的人工列代价为 11,它不是典范的。以 AB=IA_B = I 代入公式 y⊤=cB⊤AB−1y^\top = c_B^\top A_B^{-1} 得 y=1Ry = \mathbf 1_R,因此典范的第 0 行为

[  −∑i∈RAi⋅  ∣  0  ∣  −∑i∈Rbi  ],\Bigl[\; -\sum_{i \in R} A_{i\cdot} \;\Big|\; 0 \;\Big|\; -\sum_{i \in R} b_i \;\Bigr],

即从第 0 行中减去每个人工行所得。Lp::phase_1 在调用 simplex_iteration 之前恰好执行这些减法。

第二阶段。 若 w∗≠0w^\ast \ne 0(超出下述容差),Lp::phase_2 会 abort:规划不可行。否则它删去人工列,把原代价 cc 写入第 0 行,并对每个基列 jj 减去其所在行的 cjc_j 倍,使第 0 行重新成为典范形式,这与上面的消元相同。然后它从第一阶段找到的可行基出发运行 simplex_iteration。

复用单位列作为初始基

对于 b≥0b \ge 0 的 <= 行,其松弛变量的列等于单位向量,因此无需人工变量即可作为初始基变量。构造第一阶段单纯形表的私有辅助函数在某列有一项等于 11、且在约束行中其余项都为 00 时把它识别为单位列,并只为其余的行添加人工变量。因此,约束全为 <= 且右端项非负的规划完全不需要人工变量,第一阶段实际上被跳过:其单纯形表保留原目标,所以第一阶段已经在优化它。

稠密单纯形表

单纯形表是稠密的 @mutable.Matrix。每次转轴需要 (m+1)(n+1)(m + 1)(n + 1) 次乘减运算,即 O(mn)O(mn)。对稀疏规划而言,带基分解的修正单纯形法每次迭代代价更低;但稠密单纯形表把上述推导中的每个量都表示为同一个矩阵的元素,这符合本包的教学目标,也让代码保持简短。

以 abort 代替返回错误

不可行和无界的规划、零主元、未知的优化方向以及未知的关系字符串都会以 abort 终止程序。本包早于 Luna-Flow 返回带结构化错误的 Result 的约定。调用者无法从这些情况中恢复;参见下文的边界。

数值容差

单纯形迭代把各项与零做精确比较:由舍入产生的 −10−17-10^{-17} 的检验数被视为负数,并触发又一次转轴。第二阶段在两处使用 trait ApproximatelyZero:判定 w∗w^\ast 是否为零,以及识别单位列。对 Double,阈值为 ∣x∣<10−15|x| < 10^{-15},这是一个绝对界。

转轴中的舍入误差与所涉及元素的大小成比例。浮点运算中的一次更新 tij−tis trjt_{ij} - t_{is}\, t_{rj} 满足

fl⁡(tij−tis trj)=(tij−tis trj(1+δ1))(1+δ2),∣δ1∣,∣δ2∣≤u=2−53≈1.11×10−16,\operatorname{fl}\bigl(t_{ij} - t_{is}\, t_{rj}\bigr) = \bigl(t_{ij} - t_{is}\, t_{rj}(1 + \delta_1)\bigr)(1 + \delta_2), \qquad |\delta_1|, |\delta_2| \le u = 2^{-53} \approx 1.11 \times 10^{-16},

因此其绝对误差大约以 u (∣tij∣+2∣tis trj∣)u\,(|t_{ij}| + 2|t_{is}\, t_{rj}|) 为界。在量级约为 MM 的元素上进行 kk 次转轴后,w∗w^\ast 的误差量级为 k u Mk\,u\,M。因此阈值 10−15≈9u10^{-15} \approx 9u 适用于元素接近 11、转轴次数少的良好缩放的规划。测试中出现过 w∗=−4.44×10−16=−4uw^\ast = -4.44 \times 10^{-16} = -4u 这样的残差。当系数达到数千时,残差可能超过阈值,可行的规划会被报告为不可行。按 bb 的大小缩放的相对容差可以避免这一点,但尚未实现。

正确性与不变量

求解器在转轴之间维持以下不变量:

  1. 典范形式。 约束行中的每个基列都是单位向量,且第 0 行在每个基列上为零。Lp::pivot 由构造保持这一点,Lp::phase_1 为人工基建立它,Lp::phase_2 在替换第 0 行后重新建立它。
  2. 原始可行性。 右端项 bˉ\bar b 非负。to_standard 使 b≥0b \ge 0,而比值检验如上所推导保持它。
  3. 第 0 行中的目标。 第 0 行的最后一项是 −zB-z_B。

在这些不变量下,停止判据如上所推导是正确的:没有负检验数意味着最优,而负检验数配上非正列意味着无界。

实现中存在一些已知缺陷,会在特定情形下破坏这些保证。这里列出它们以便用户规避;代码本身未作修改。

  • 目标行对齐。 Lp 用 Obj_func::to_vector 构造第 0 行,它按变量名顺序取已存储的系数并跳过零系数。因此只要某个目标系数为零,或变量未按名称顺序声明,第 0 行就与列顺序不一致,求解器优化的是被打乱的目标。例如,在 x1≥1x_1 \ge 1、x2≥2x_2 \ge 2 下最小化 x2x_2,返回 11 而不是 22。
  • 基的识别。 第二阶段把单位列识别为基变量,并在每行取第一个这样的列。当两列是相同的单位向量时,即使目标值正确,报告的解也可能指错变量。
  • 退化的人工变量。 若第一阶段结束后某个人工变量仍以零值留在基中,第二阶段会直接删去其列而不先将其转出,该行在第二阶段中就没有基列。
  • 人工变量索引。 对没有人工变量的行,Lp::phase_1 收到的人工列为 0,它无法与第 0 列区分。若此时第 0 列的第一阶段目标系数恰为 11,就会从第 0 行中减去不该减的行。

被否决的方案

  • 大 M 法。 出于“带人工变量的两阶段法”中所述的数值原因而被否决。
  • Bland 规则。 它保证终止,但在非退化规划上通常比 Dantzig 规则需要多得多的转轴。本包保留 Dantzig 规则,转而限制迭代次数。
  • 修正单纯形法与内点法。 它们在大型稀疏规划上扩展性更好,但会隐藏单纯形表,而本包有意公开单纯形表。
  • 通过代换实施变量边界。 边界 l≤x≤ul \le x \le u 可以通过 x=l+x′x = l + x' 和额外约束 x′≤u−lx' \le u - l 化为标准形。本包只为显示而保存边界,此类约束交由用户添加。

边界

本包刻意不做以下事情:

  • 以值的形式返回错误。不可行和无界的规划、零主元以及非法参数都会使程序 abort;
  • 实施 x≥0x \ge 0 以外的变量边界;
  • 在每次单纯形运行 1000 次迭代的上限之外防止循环;
  • 利用稀疏性、热启动或基分解;
  • 计算对偶值或灵敏度区间,尽管最终基的向量 y=(AB−1)⊤cBy = (A_B^{-1})^\top c_B 已隐含在单纯形表中;
  • 求解整数、混合整数或非线性规划。

Footnotes

  1. 参见例如 Chvátal,Linear Programming(1983)第 3 章,或 Bertsimas 与 Tsitsiklis,Introduction to Linear Optimization(1997)定理 2.7。 ↩

  2. E. M. L. Beale,“Cycling in the dual simplex algorithm”,Naval Research Logistics Quarterly 2(1955),给出了一个含三个约束的循环例子。R. G. Bland,“New finite pivoting rules for the simplex method”,Mathematics of Operations Research 2(1977),证明了在候选者中总选择下标最小的进基变量和出基变量可以防止循环。 ↩