Skip to content

Latest commit

 

History

History
301 lines (250 loc) · 25.8 KB

README.md

File metadata and controls

301 lines (250 loc) · 25.8 KB

基准数据与报告

数据政策(重要)

本仓库不再分发第三方测试数据集。 只提供下载脚本、清单与报告;数据落在 bench/data/,该目录已被 .gitignore 忽略。

数据集 来源 获取方式
MIPLIB 2017 https://miplib.zib.de bench/fetch-instances.ps1 下载 .mps.gz 并就地 gzip 解压为纯 MPS

为什么不用 Netlib LP 测试集

netlib 的 lp/data/ 不是纯 MPS:文件由一行 NAME、一行维度头,随后是它自己的压缩记录组成 (netlib 的 emps 容器)。在实现该容器的解压器之前,这些文件无法直接读取,因此解析报告改用 MIPLIB 2017 —— 它提供同样性质的工业实例,但以纯 MPS(gzip 压缩)发布。

如果将来要接入 Netlib,正确做法是先实现/移植 emps 解压,而不是把压缩数据喂给 MPS 读取器。

脚本

脚本 作用
fetch-instances.ps1 下载实例到 bench/data/instances/,生成 manifest.txt(相对路径,可移植)
report-parse.ps1 通过 moon run cmd/parse -- --manifest ... 解析全部实例,生成 parse-report.md
report-solve.ps1 加 --solve --relax --max-rows N 求解,生成 solve-report.md
report-mip.ps1 加 --mip --max-nodes N 做分支定界,生成 mip-report.md(可换 -Manifest / -Output / -CutRounds:小规模满预算那一份就是这么生成的)
check-relaxation-bounds.ps1 把求解报告里的目标值与 MIPLIB 官方最优值表对拍
check-mip-objectives.ps1 把分支定界报告里每个 optimal 与官方最优值取等对拍,并断言 verified == nodes
report.ps1 生成 report.md:四份报告的索引(每份的范围、关键数字、生成时的提交)+ 一键复现命令 + "这份报告描述的代码是否仍是仓库里那份"(按报告自己记的提交与它依赖的源路径做差分判定)
powershell -NoProfile -ExecutionPolicy Bypass -File bench/fetch-instances.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File bench/report-parse.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File bench/report-solve.ps1 -Relax -MaxRows 2500 -MaxIterations 20000 -Presolve
powershell -NoProfile -ExecutionPolicy Bypass -File bench/check-relaxation-bounds.ps1
powershell -NoProfile -ExecutionPolicy Bypass -File bench/report-mip.ps1 -MaxRows 300 -MaxNodes 600 -MaxIterations 5000
powershell -NoProfile -ExecutionPolicy Bypass -File bench/check-mip-objectives.ps1
# 小规模实例的满预算口径(完成标准"M5 ①:小规模实例求到公开已知最优值"的证据)
powershell -NoProfile -ExecutionPolicy Bypass -File bench/report-mip.ps1 -Manifest bench/data/instances/small.txt -MaxRows 300 -MaxNodes 30000 -MaxIterations 20000 -Output bench/mip-report-small.md
powershell -NoProfile -ExecutionPolicy Bypass -File bench/check-mip-objectives.ps1 -Report bench/mip-report-small.md

check-mip-objectives.ps1 按表头列名读报告,不按列位置。 它此前是按位置取 "nodes / verified" 的: 报告插入 cuts 列之后,它把 verified 读成 nodes、把 cuts 读成 verified,对一个 33/33 全部过校验的 最优运行报 UNVERIFIED 并非零退出 —— 读数的人坏了,而报告里的数字一个都没变。加列就要重新走一遍这条命令。

mip-report.md 是这条命令生成的:noswot 的三个被拒证书在 M5 第三轮全部定论为内核缺陷 (对偶解在病态基上丢精度、方向求解没有自检、非负性自检的尺度与校验器不一致,见 docs/history.md), 32 个实例不再有被拒证书。报告是证据,脚本仍然按纪律拒绝写报告 —— 只要内核退出码非零、条目数不符、 出现被拒证书、或解没通过独立复核,就不写。

report-solve.ps1 用 native release 目标运行内核:同一实例在默认 wasm 目标上要慢约 6 倍 (实测 mod010:wasm 119.6s / native debug 203.6s / native release 18.8s), 报告里的每个结果与耗时都来自 native release。

report-solve.ps1 在两种情况下拒绝写报告并以非零码退出:内核退出码非零 (崩溃或某个文件解析失败),或解析到的实例数与清单条数不一致。一次 native 崩溃曾被写成 "22/33 实例成功"的报告,而报告是仓库里唯一的实测证据,不能把一次没跑完的运行写成一次跑完的运行。

行数上限有两个,别混淆

上限 位置 含义
--max-rows N cmd/parse 模型约束数,超过即报 solve=skipped,不进入内核
--max-iterations N cmd/parse 迭代上限,让大实例可以被采样而不是被等待(默认沿用内核自身的 20000)
max_kernel_rows SimplexOptions(默认 200000) 内核行数,超过即返回 SimplexStatus::TooLarge
max_factor_entries SimplexOptions(默认 2×10⁷) 因子分解的填充量预算,超过即分解失败

内核行数曾经 = 模型约束数 + 每个有限上界一行,那套口径下 fast0507 是 507 条约束、 63009 个 0/1 变量、内核 63516 行。现在有限上界是界而不是行(有界变量枢轴,见 simplex/),同一实例的内核规模是 489 行;唯一还会放大行数的是"下界无界的自由变量 + 有限上界"(两个列之差没有单列界,只能留成显式行)。

内核行数值得盯,而且不只是内存问题:实测枢轴数随内核行数走、与列数几乎无关 (小规模覆盖型 LP 探针:20 / 40 / 120 / 240 行 → 57 / 137 / 632 / 2220 枢轴; 20 行时 100 列与 900 列分别是 57 与 35)。所以去掉 fast0507 的 63001 行上界行之后, 每次枢轴从约 47.5 ms 降到约 2.4 ms(实测 150000 次枢轴 400 秒),Phase I 首次跑完 (此前 20000 次上限下 Phase I 还停在人工和约 180)。

内核规模的历史与每次取舍的理由记在 ../docs/history.md:内核最初求 m×m 稠密基逆 (63516 行 ≈ 30.8 GB,native 会在分配时以访问冲突退出),后来改成稀疏 LU 分解(内存 O(nnz + fill)), 再后来有限上界不再占行。现在两条边界是 max_kernel_rows(在分配之前拒绝)与 max_factor_entries (填充量预算,超预算让分解失败而不是无上限分配)。实测:30n20b8(presolve 后 11591 行)16 秒求到最优, fast0507(内核 489 行)能跑完 Phase I。

它现在的边界是 Phase II 的定价:Dantzig 按检验数选列,那是改进的速率而不是改进本身, 覆盖最多未覆盖行的列速率最大、却常被第一行接近紧的约束卡住(实测:Dantzig 选中的列 r = −121.1、步长 0.00632、增益 0.76,而 24 列抽样里就有 r = −6、步长 1.0、增益 6.0)。 改成按实测增益 |r| · step 选列(gain_candidates,每迭代只测一个有界候选集)之后, 同一枢轴预算下的内核目标值:20000 次枢轴 165.115 → 156.778,45000 次 → 133.032 (Dantzig 要 250000 次枢轴才到 160.353);代价是每次枢轴约 2.4 → 14 ms。

非 optimal 的运行不打印目标。 目标列只在真正解出时才有数字,其余状态印 -: 一次失败运行返回的目标是 0,而带 presolve 时 ReducedModel::objective(0.0) 会把它翻成 化简自身的目标常数,看上去像一个结果。fast0507 就因此被记成"Phase I 停滞在 13" (那 13 是化简固定 8 个变量贡献的目标常数,与迭代次数无关)——先用 --max-iterations 1 再摘掉 --presolve 就能看到同一运行分别印 obj=13 与 obj=0。Phase I 的真实进度现在印在 迭代上限消息里(实测人工和),不再需要从别处推断。

四个脚本只用 ASCII 字符:Windows PowerShell 5.1 读取没有 BOM 的 UTF-8 脚本时会按 ANSI 解码, 非 ASCII 字符会变成乱码。新增脚本请遵守这一约定。

行上限挡住的实例:抬高上限之后是什么样

solve-report.md 用的是 --max-rows 2500(行上限的检查在 presolve 之后,量的是归约后真正交给 内核的行数),清单里超过这条线的三个实例被记作 skipped。这些实例(含曾经的第四个 mod011, 等式 doubleton 代入把它降到 2 402 行而进入报告)都抬到 --max-rows 20000 实测过;mod011 后来 进入报告口径并解到最优,其余三个没有一个达到最优(其余口径不变),而且失败原因不止一种 —— 所以这条线排除的是"已知解不完的东西",不是"没试过的东西":

实例 模型行数 矩阵系数(model 读出的最小/最大非零,比值) 抬到 20000 行之后
core2536-691 2 539 0.1 … 100(1e+03)—— 绝大多数行的最大系数就是 1,只有 UB 行到 100 Phase I 走不完:500 枢轴后人工和仍有 1 295.0(2.4 s)
dano3_3 3 202 0.5 … 1 000(2e+03)—— 行最大系数到 1 000 Phase I 太慢:2 000 枢轴后人工和 32 658.6(33.2 s)
mod011 4 497 0.14 … 42 336(3.0e+05);目标系数另跨 7.9 … 2.7e+06(3.4e+05) 数值失败:10 742 枢轴后 solve=numerical-failure("basis became numerically singular",103 s)
2club200v15p5scn 17 013 全部是 1(1) 仍在 Phase II:200 枢轴的内核目标 −23.000000363(7.9 s);1 000 枢轴那一轮 7.5 分钟(450 s CPU)仍未结束

两条读数:① 失败方式不止一种 —— mod011 是数值失败(基在 10 742 次枢轴后变成奇异), core2536-691 与 dano3_3 是 Phase I 走不完,2club200v15p5scn 尺度完全正常(系数全是 1)、贵在 17 013 行本身;② 系数最宽的是 mod011(矩阵 3e+05、目标 3.4e+05),而另外三个的尺度都在 1e+03 以内 —— 所以 "尺度大 ⇒ 解不完"不成立(dano3_3 只有 2e+03 也解不完),它只解释了四个里的一个。两侧都对 2200 没有 异议:它恰好是"最小覆盖已测最大可解模型"(cap6000,2 176 行)之后的第一个整百。

后续(等式 doubleton 代入落地后):mod011 在同一 20 000 行口径下从上表的数值失败变成 solve=optimal(−62121982.55190222,10 177 枢轴,途中一次奇异由 Bland 重启救回)——presolve 把它的 2 077 条等式 doubleton 行代入消去,基的规模减半后奇异性不再致命。其余三个实例没有等式 doubleton 猎物 (dano3_3 684 条全是不等式,属隐含界地盘),结局不变。命令(一个实例一行, N 取上表的枢轴预算):

_build/native/release/build/cmd/parse/parse.exe bench/data/instances/<name> `
  --solve --presolve --relax --max-rows 20000 --max-iterations N

core2536-691 此前另有一条记录:20 000 枢轴的整段预算都花在 Phase I 上(没有结论),与表里 500 枢轴的 采样方向一致。

这张表的"系数"列在第一次发表时是错的,本表已更正:第一版用一次性 PowerShell 脚本按 MPS 的固定列位 (24/36 起 12 列)取值,而 mod011 / core2536-691 的这些行是自由格式,于是读出来的是半截 token 拼的 数 —— core2536-691 被写成 1e+11、mod011 写成 1.9e+07,而 model 实际读到的分别是 1e+03 与 3.0e+05 (矩阵)。取这些数的正确做法是把实例交给 format.parse_mps,再遍历 constraint(i).terms 与 objective(),不要用列位去切文本。

均衡(行 / 列缩放)实测:救不回任何一个实例,但把 dano3_3 的 Phase I 加快了一倍

上表里唯一"尺度大"的实例是 mod011,而它的失败方式是数值的,所以下一个问题是:把实例先均衡(缩放) 到系数可比,它能不能解出来?做法是把每个实例重写成一个等价模型 —— 行除以该行最大系数、列再除以该列 (行缩放后)最大系数,即 x = s·x'、系数 a·s、界 l/s、u/s、右端项 b/r,目标值不变 —— 其余口径与报告一致(--solve --presolve --relax --max-rows 20000)。

变换本身先在三个能解的实例上验过(目标值必须不变):gt2 13460.233074418948 → 13460.233074450756、 danoint 62.637280418466226 → 62.637280418543554、mod010 6532.083391108283 → 6532.083353875393, 相对差都在 1e-8 以内。

实例 原样 均衡后
mod011 10 742 枢轴后基奇异 仍然数值失败:9 578 枢轴后基奇异;再把目标系数也除以 2.68e6 后 10 585 枢轴、改由内核终检拦下(scaled residual 7.9e-8、负值与界违反 9.3e-7)
core2536-691 500 枢轴后人工和 1 295.0 逐位不变,带不带 presolve 都一样 —— 它的行最大系数本来就是 1,列上一半也在 0.1 的量级,均衡无事可做
2club200v15p5scn 200 枢轴 −23.000000363 逐位不变(系数全是 1)
dano3_3 2 000 枢轴后人工和 32 658.6 17 343.7(Phase I 约快一倍);但 20 000 枢轴的整段预算在约 30 分钟 CPU 内没跑完(每枢轴成本随填充增长),所以它同样没被解出来
gt2 / danoint / mod010(对照) 35 / 1 907 / 3 455 枢轴 32 / 1 641 / 4 316 枢轴(最后一个慢 25%)

⇒ 均衡不是覆盖面的杠杆:没有一个实例因为它被解出来,mod011 的数值失败也不是"尺度大"这一件事 (缩放到系数全是 1 之后,它在 1e+04 个枢轴处仍写不出证书)。它真正的效果在 Phase I 的速度上,而且不是 单调的(同一张表里 mod010 反而慢 25%),所以"按尺度阈值决定要不要缩放"这种规则不能靠一张表拍下来。

另外两把"再加一点"的旋钮:枢轴上限与割轮次

覆盖面还剩两条"把某个上限往上调"的路,两条都量过了,结论都是不值得。

① LP 的枢轴上限(fast0507 是唯一撞上它的实例)。 三个点:报告口径 -MaxIterations 20000 下停在 164.252869961398;60 000 枢轴(647 s)到 139.78200546250716;150 000 枢轴(1 587 s,26 分钟)到 133.1772397680534 —— 还在降,但增益在衰减:20 000→60 000 每千枢轴买到 0.61 个目标单位, 60 000→150 000 每千枢轴只剩 0.073。也就是说它离最优还有一段,而要到那里至少还得几十万枢轴(按 10.6 ms/枢轴 算是一小时以上一次),代价是把求解报告从约 2 分钟变成一小时以上 ⇒ 枢轴上限维持 20 000,这条"抬上限拿 覆盖"的路在 LP 侧关闭。

② MIP 的割轮次。 11 个到节点预算的实例,在固定 200 节点的口径下比 --cut-rounds 2 与 --cut-rounds 16:

实例 --cut-rounds 2 --cut-rounds 16
gt2 20 条割,界 20869.350537660535 30 条割,界 21099.97754330556(第 3 轮还买到 10 条,之后饱和)
flugpl 18 条割,界 1176093.3333337635 逐位相同
blend2 16 条割,界 7.0992203805417535 逐位相同
markshare1 15 条割,界 0 逐位相同

也就是说割的生成器在第二到第三轮就饱和:11 个实例的累计割数在轮次 2/4/8/16 上分别是 flugpl 18/18/18/18、blend2 16、dcmulti 20、markshare1 15、markshare2 17、misc07 20、 noswot 20、pk1 20、rout 20,只有 gt2(20→30)与 50v-10(20→40)多买到一轮。默认的 cut_rounds = 4(前两轮无条件、之后按"上一轮把根界抬 ≥ 1.4%"放行)已经落在饱和点上,再加轮次是纯成本。

口径提醒(这轮踩到的):把节点预算写成 --max-nodes (r+2) 去"测轮次"是错的 —— 那个预算里 割轮重解和树节点共用一个计数,于是 r 越大、真正走的树节点越少,界的变化里混着"树更深"的效果 (本机实测:同一实例 r=2 与 r=16 在 (r+2) 口径下界能差 0.06%,在固定 200 节点口径下逐位相同)。 测轮次必须固定树预算。

报告

  • report.md:全部报告的入口(先读这一份)。 它由 bench/report.ps1 生成,内容是三件事: ① 四份报告各覆盖什么、关键数字是多少、生成时的提交;② 按顺序的一键复现命令; ③ 每份报告是否仍被当前代码支持——脚本读取报告自己记下的提交,把该报告依赖的源路径在 "那个提交..HEAD"之间做差分,再把工作树里这些路径上的未提交改动(含未跟踪的新文件)并进来, 有任何一条就标 STALE 并列出改动文件。 两条都要问:报告是「某个提交跑出来的」,所以它从来没说过未提交的改动;而一份数字由改过的 文件跑出来的报告,正是这个索引存在的意义。未跟踪文件也算——mip/ 里多一个 .mbt 就是那次测量 所在的包的一部分,跟 git 认不认识它无关。 这条判据故意偏保守:改了注释也算改(注释不可能动数字),因为方向要偏安全——只在"可能不再描述当前代码" 时标出来,绝不在"可能仍然有效"时放行;被标出来的报告只有两种结局:重跑它的命令,或者测出它要打印的数字没变 并把那次测量写在某处。脚本自己在任何 scope 路径之外,所以"脚本被改过"不会把四份报告误标成陈旧。 脚本在三种情况下拒绝写(都用非零码退出且不落盘):列出的报告文件缺失、某份报告的提交或某个汇总数字 读不出来(索引宁可不出也不能印一个猜的数)、git 无法回答陈旧性。已验证:缺报告 exit 2、无 git 历史 exit 4。
  • parse-report.md:解析报告。含工具链版本与产生它的提交哈希,因此每个数字都可追溯到具体代码; 记录每个实例的规模、整数列数与校验结论,失败的实例逐条给出原因与位置。
  • mip-report.md:分支定界报告(行数上限 300、节点预算 600、迭代上限 5000)。清单里 32 个实例,逐实例给状态、 目标值、仍在开的界、nodes / verified / cuts 与点复核结论;脚本在退出码非零、条目数不符、 出现被拒证书或点没通过复核时拒绝写报告。
  • mip-report-small.md:小规模实例的满预算口径(清单 bench/data/instances/small.txt、行数上限 300、 节点预算 30000、迭代上限 20000、开根割)。它是完成标准"M5 ①:小规模实例求到公开已知最优值"的证据: 10 个实例里 6 个证到公开已知最优值(22433 39 节点 / khb05250 113 / p0201 577 / gt2 432 / flugpl 13812 / blend2 24940),check-mip-objectives.ps1 -Report bench/mip-report-small.md 对 6 项取等通过。节点预算 30000 是实测定的:清单里最深的证明(blend2)落在 24 940 个松弛上, 30000 是最小的整千覆盖值;早先 20000 的口径下同一实例报 node-limit(两场实验,旧数字留在 docs/history.md)。 清单文件本身也是证据:它把每个实例实测的每节点成本与排除理由写在里面(rout 391 ms/节点、 misc07 1000 节点超过 8 分钟 → 满预算的臂跑不起)。 blend2 曾因一条证书拒签被记为 BLOCKED(M5 第十二~十四轮),内核补上对偶侧自检后已回到清单; 它跑空的路上还有过一格写不出证书的松弛,第四十三轮给恢复梯子加了第三次走法(同一个模型、Dantzig 定价) 之后它报 optimal(见 docs/history.md)。 verify 列因此有两种可能小于 nodes 的原因:松弛撞上迭代上限,或该松弛的最优基写不出满足 符号约定的证书(内核把它报成 NumericalFailure,搜索按"无结论"计)—— 两种都是"预算没做完的活", 而不是"被校验器拒绝的主张"。
  • solve-report.md:求解报告。记录模式(是否 LP 松弛)、行数上限、枢轴上限、工具链版本与提交哈希, 逐实例给出状态、目标值与枢轴迭代数;非最优结果逐条如实列出,包含内核自检测得的残差与负值。 枢轴上限是基准口径的一部分(报告头部写明,且 pivots 列等于上限的实例就是在那里停下的): 上限不同的两份报告是两场实验,不能互相比较—— 这条以前只靠人记,现在写在报告里。 当前这份由 -Relax -MaxRows 2500 -MaxIterations 20000 -Presolve 生成(两个上限现在都是实测定的: 枢轴上限 20 000 与同一份清单里 MIP 报告给每个松弛的上限一致——早先那个 1200 的口径下同一清单只有 18 个最优、3 个到上限;行上限的定标逻辑是"最小覆盖已测最大可解模型"——稀疏 LU 之后内核不再分配稠密基逆, 实测 cap6000(2176 行)能在枢轴上限内解完,于是上限先定到 2 200,而它挡住的四个实例抬到 20 000 行之后 没有一个达到最优(逐条实测见上文「行上限挡住的四个实例」,失败原因是数值尺度与 Phase I/枢轴成本, 不是行数本身);等式 doubleton 代入落地后,行上限的检查挪到了 presolve 之后(量的是内核真正拿到的问题), mod011 归约后 2 402 行、在 20 000 枢轴内解到最优,按同一条定标逻辑口径升到 2 500—— 归约后 2 505 行的 core2536-691 恰在它之外。三场实验的数字都留在 docs/history.md 里): 32 个实例全覆盖、28 最优、3 因行数上限跳过、refused 归零、1 到枢轴上限(fast0507,20 000 枢轴), 29 个实例经过化简、28 个重建解全部通过三项检查、1 次需要 Bland 恢复;总枢轴 36 010、 最大已解模型 4 497 行(mod011 归约后 2 402 行进入报告并解到最优)。从"上限挡住的"变成"解完的"一共 9 个实例:danoint(1907 枢轴)与 mod010(3455 枢轴)是 1200→20000 枢轴那场解放出来的,qiu / blp-ar98 / dsbmip / aflow40b / bc1 / mitre / cap6000(1128~2176 行)是 1000→2200 行这场解放出来的。 refused 归零与两个实例从"上限挡住"变成"解完"是稀疏 LU 与增益定价的直接结果:30n20b8 (presolve 后 11591 行)16 秒求到最优并进入对拍表;bienst1 / bienst2 需要约 2130 次枢轴, 在 1200 上限下一直报 iteration-limit,改按实测增益选列后各 582 次枢轴解完。 报告里每行还带 presolve(化简前后规模与各项计数)与 check(还原解在原模型上的 最大行/界违反,以及用原模型目标向量重算出的目标值)两列 —— 化简是实验的一部分, 它的记账必须自己站得住。 化简对两个大实例的效果正好说明它能做什么、不能做什么:30n20b8 被化简掉 7282 个变量 (18380→11098,内核行 18956→11591,降 39%),稀疏 LU 之后这 11591 行 16 秒就解到最优; fast0507 是集覆盖问题,几乎没有可约的上界行(63009→63001 个变量),化解它需要的不是化简, 而是把上界当界用(内核行 63490 → 489,已落地)与按实测增益定价(也已在默认里), 剩下的仍是 Phase II 的枢轴数,见 docs/roadmap.md。

交叉校验(外部权威,独立于本实现)

check-relaxation-bounds.ps1 把求解报告里的目标值与 MIPLIB 2017 官方最优值表 (miplib2017-v26.solu)对拍。松弛问题的目标值不可能超过原问题最优值,所以每个求到最优的实例 都给出一个可判定不等式;一旦松弛值超过官方最优值,就说明内核错了。

下表是较早口径(1200 枢轴上限、16 个实例)下的一次快照,方向性结论不变。当前口径的完整对拍就是这条 命令的输出(checked 28, violations 0, not in the table 0,口径见 solve-report.md 头部):

实例 松弛目标值 官方最优值 结论
flugpl 1 167 185.7256 1 201 500 松弛 ≤ 最优
blend2 6.9157 7.598985 松弛 ≤ 最优
dcmulti 183 975.5397 188 182 松弛 ≤ 最优
fiber 156 082.5176 405 935.18 松弛 ≤ 最优
gt2 13 460.2331 21 166 松弛 ≤ 最优
khb05250 95 919 464.0001 106 940 226 松弛 ≤ 最优
markshare1 0 0.999999999999 松弛 ≤ 最优
markshare2 0 1 松弛 ≤ 最优
misc07 1 415 2 810 松弛 ≤ 最优
noswot −43.000000000053 −41.00000885 松弛 ≤ 最优
p0201 6 875.0000006 7 615 松弛 ≤ 最优
pk1 1.37e−12 11 松弛 ≤ 最优
rout 981.8643 1 077.56 松弛 ≤ 最优
22433 21 240.5262 21 477 松弛 ≤ 最优
30n20b8 1.5664 302 松弛 ≤ 最优(稀疏 LU 后才进入可解范围)
50v-10 2 879.0657 3 311.1799841 松弛 ≤ 最优

这不是正确性证明,但能廉价地排掉一整类错误,而且结论是记录下来的,不是假定的——表里每一行都由 check-relaxation-bounds.ps1 从报告文件直接生成并与官方最优值表对拍。noswot 一行 也说明了为什么这类对拍值得做:它的松弛值 -43.000000000053 比 MIP 最优值 -41.00000885 更小 才是正确的方向。30n20b8 一行则是稀疏 LU 的成果:它在稠密基逆时代需要约 1 GB 基逆而被拒绝, 现在 16 秒解出、目标值 1.5664 ≤ 302 方向正确。

报告原则

  1. 只报告实测结果,不写没有测量支撑的推测;
  2. 失败要显式:超时、迭代上限、数值失败、超出能力边界分别统计,不合并成“其他”;
  3. 每次报告携带运行环境(moon version --all)与提交哈希,保证可复现;
  4. 数据不入库,脚本入库,报告入库。