本仓库不再分发第三方测试数据集。 只提供下载脚本、清单与报告;数据落在 bench/data/,该目录已被
.gitignore 忽略。
| 数据集 | 来源 | 获取方式 |
|---|---|---|
| MIPLIB 2017 | https://miplib.zib.de | bench/fetch-instances.ps1 下载 .mps.gz 并就地 gzip 解压为纯 MPS |
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.mdcheck-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 Ncore2536-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(),不要用列位去切文本。
上表里唯一"尺度大"的实例是 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 个证到公开已知最优值(2243339 节点 /khb05250113 /p0201577 /gt2432 /flugpl13812 /blend224940),check-mip-objectives.ps1 -Report bench/mip-report-small.md对 6 项取等通过。节点预算 30000 是实测定的:清单里最深的证明(blend2)落在 24 940 个松弛上, 30000 是最小的整千覆盖值;早先 20000 的口径下同一实例报node-limit(两场实验,旧数字留在docs/history.md)。 清单文件本身也是证据:它把每个实例实测的每节点成本与排除理由写在里面(rout391 ms/节点、misc071000 节点超过 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 方向正确。
- 只报告实测结果,不写没有测量支撑的推测;
- 失败要显式:超时、迭代上限、数值失败、超出能力边界分别统计,不合并成“其他”;
- 每次报告携带运行环境(
moon version --all)与提交哈希,保证可复现; - 数据不入库,脚本入库,报告入库。