From 060fd43f41eb0cf17dce4b69e2cfcbb4c66f64c0 Mon Sep 17 00:00:00 2001 From: Suquster <289166199+Suquster@users.noreply.github.com> Date: Mon, 6 Jul 2026 13:26:28 +0000 Subject: [PATCH] =?UTF-8?q?feat(directed):=20Customizable=20CH=EF=BC=88CCH?= =?UTF-8?q?=EF=BC=8CDibbelt=202014=EF=BC=89=E2=80=94=E2=80=94=E6=9D=83?= =?UTF-8?q?=E9=87=8D=E6=8D=A2=E7=BB=91=2013=E2=80=9319=C3=97=20=E4=BA=8E?= =?UTF-8?q?=20CH=20=E9=87=8D=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - src/directed/cch.mbt:度量无关最小度贪心收缩 + 弦图补全一次成型, 换权只跑 basic customization(下三角 relax,无堆无搜索),查询同 CH 双向向上 Dijkstra 含 stall-on-demand - OSM 实测(benches/results/cch-osm-20260706.md):换权成本北京 1.90 s vs CH 全量重建 25.3 s(13.4×)、厦门 18.8×;查询相对双向 Dijkstra 4.4–7.9×(比 CH 慢 3.9–5.9×,论文已知取舍,诚实归档) - 守卫:差分 PBT 100 迭代(含换权 recustomize 再对拍)+ OSM 厦门 8 组守卫 + bench 全量对拍(原权/扰动权各一轮均一致) - 文档同步:双语 README 第 38 项、paper-to-code §4.7、slides/QA/ video_script/backlog/开发文章 7→8 种前沿算法 - 三后端 2161/2161 全绿 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- README.md | 3 +- README.zh-CN.md | 3 +- benches/advanced_bench/osm_cch_bench.mbt | 179 ++++++++++ benches/results/cch-osm-20260706.md | 39 ++ docs/CHAMPIONSHIP_BACKLOG.md | 2 +- docs/presentation/slides.md | 4 +- docs/presentation/video_script.md | 4 +- docs/rehearsal/qa.md | 2 +- docs/verification/paper-to-code-advanced.md | 16 + docs/zh/development-article.md | 4 +- src/directed/cch.mbt | 375 ++++++++++++++++++++ src/directed/cch_test.mbt | 92 +++++ src/directed/pkg.generated.mbti | 20 ++ 13 files changed, 733 insertions(+), 10 deletions(-) create mode 100644 benches/advanced_bench/osm_cch_bench.mbt create mode 100644 benches/results/cch-osm-20260706.md create mode 100644 src/directed/cch.mbt create mode 100644 src/directed/cch_test.mbt diff --git a/README.md b/README.md index 65e534e..87bedd8 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ The current local guard passes with one environment warning: `moon publish ## Algorithm Catalog -当前已落地 **30 种经典图/路径算法** 与 **7 种前沿算法**。 +当前已落地 **30 种经典图/路径算法** 与 **8 种前沿算法**。 CH / ALT / Hub Labeling 已有生产级稠密快路径变体(`src/directed/`) 并附真实 OSM 路网基准证据(北京驾车网:CH 相对双向 Dijkstra **46×**,HL 距离查询 **0.44 µs(14304×)**,PHAST 一到全 SSSP @@ -235,6 +235,7 @@ CH **16–25×**,RPHAST 目标子集限定再提 **6.9–9.8×**,见 | 35 | 🔥 PHAST (一到全 SSSP) | [`src/directed/phast.mbt`](./src/directed/phast.mbt) | ✅ OSM 实测 6.15× | [Delling, Goldberg, Nowatzyk & Werneck 2011](https://doi.org/10.1109/IPDPS.2011.89) | | 36 | 🔥 Many-to-many 距离表 | [`src/directed/many_to_many.mbt`](./src/directed/many_to_many.mbt) | ✅ OSM 实测 16–25× | [Knopp, Sanders, Schultes, Schulz & Wagner 2007](https://doi.org/10.1137/1.9781611972870.4) | | 37 | 🔥 RPHAST (目标子集限定) | [`src/directed/rphast.mbt`](./src/directed/rphast.mbt) | ✅ OSM 实测 6.9–9.8× | [Delling, Goldberg, Nowatzyk & Werneck 2011](https://doi.org/10.1109/IPDPS.2011.89) | +| 38 | 🔥 Customizable CH (CCH) | [`src/directed/cch.mbt`](./src/directed/cch.mbt) | ✅ OSM 实测换权 13–19× | [Dibbelt, Strasser & Wagner 2014](https://doi.org/10.1007/978-3-319-07959-2_24) | > ✅ v0.0.1 = 源码 + 单元测试 + PBT 已合入主干 > ✅ v0.0.2 = 新增算法(Prim / DAG-SP / 桥与割点 / 双向 Dijkstra),源码 + 单元测试已合入主干 diff --git a/README.zh-CN.md b/README.zh-CN.md index 3021c6a..f6049d9 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -133,7 +133,7 @@ pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\release_guard.ps1 ## 算法目录 -当前已落地 **30 种经典图 / 路径算法** 与 **7 种前沿算法**。 +当前已落地 **30 种经典图 / 路径算法** 与 **8 种前沿算法**。 CH / ALT / Hub Labeling 已有生产级稠密快路径变体(`src/directed/`) 并附真实 OSM 路网基准证据(北京驾车网:CH 相对双向 Dijkstra **46×**,HL 距离查询 **0.44 µs(14304×)**,PHAST 一到全 SSSP @@ -181,6 +181,7 @@ CH **16–25×**,RPHAST 目标子集限定再提 **6.9–9.8×**,见 | 35 | 🔥 PHAST(一到全 SSSP) | `src/directed/phast.mbt` | ✅ OSM 实测 6.15× | Delling, Goldberg, Nowatzyk & Werneck 2011 | | 36 | 🔥 Many-to-many 距离表 | `src/directed/many_to_many.mbt` | ✅ OSM 实测 16–25× | Knopp, Sanders, Schultes, Schulz & Wagner 2007 | | 37 | 🔥 RPHAST(目标子集限定) | `src/directed/rphast.mbt` | ✅ OSM 实测 6.9–9.8× | Delling, Goldberg, Nowatzyk & Werneck 2011 | +| 38 | 🔥 Customizable CH(CCH) | `src/directed/cch.mbt` | ✅ OSM 实测换权 13–19× | Dibbelt, Strasser & Wagner 2014 | > 🔥 = **Rust `pathfinding` crate 未实现的独家算法** > 🧪 experimental = 源码与测试已存在,但 API / 性能证据尚未冻结 diff --git a/benches/advanced_bench/osm_cch_bench.mbt b/benches/advanced_bench/osm_cch_bench.mbt new file mode 100644 index 0000000..c525490 --- /dev/null +++ b/benches/advanced_bench/osm_cch_bench.mbt @@ -0,0 +1,179 @@ +// osm_cch_bench.mbt —— 真实 OSM 路网上的 Customizable CH 基准。 +// +// CCH 的卖点不是查询更快(其骨架无 witness 剪枝、比 CH 密),而是 +// **换权重只需 customization 而非整套重建**。本基准量三件事: +// 1. 度量无关构建耗时(一次性,可跨权重复用); +// 2. customization 耗时(换权成本)vs CH 全量重建耗时(对比项); +// 3. CCH 查询耗时 vs CH / 双向 Dijkstra(代价全量对拍护栏,含换权 +// 后再 customize 的第二轮对拍)。 +// 执行约定与 osm_alt_bench.mbt 一致(预热/采样/中位数)。 + +///| +/// 单数据集采集主体:返回 Markdown 证据。 +fn run_osm_cch_benchmark_on( + dataset : String, + n : Int, + edge_count : Int, + payload : Array[String], + qcount : Int, + warmup : Int, + samples : Int, +) -> String { + let (g, rg) = build_osm_weighted_csr(n, edge_count, payload) + let qrng = Lcg::new(query_seed_int) + let queries : Array[(Int, Int)] = [] + for _ in 0.. + if x.to_int64() != y || x != z { + parity_ok = false + } + (None, None, None) => () + _ => parity_ok = false + } + } + // 查询耗时:双向 Dijkstra / CH / CCH。 + let sink : Array[Int64] = [0L] + let dij_samples = collect_samples(warmup, samples, fn() { + for q in queries { + match @dir.dijkstra_bidirectional_ctx(g, rg, cf, cb, q.0, q.1) { + Some((_, c)) => sink[0] = sink[0] + c.to_int64() + None => () + } + } + }) + let ch_samples = collect_samples(warmup, samples, fn() { + for q in queries { + match ch.query(q.0, q.1) { + Some((_, c)) => sink[0] = sink[0] + c.to_int64() + None => () + } + } + }) + let cch_samples = collect_samples(warmup, samples, fn() { + for q in queries { + match cch.query_dist(q.0, q.1) { + Some(c) => sink[0] = sink[0] + c + None => () + } + } + }) + let dij_med = csr_bench_median(dij_samples) + let ch_med = csr_bench_median(ch_samples) + let cch_med = csr_bench_median(cch_samples) + let qd = qcount.to_double() + // 换权重(全边权 ×3+7 扰动)后 customize,再对拍守卫语义。 + let edges2 : Array[(Int, Int, Int)] = [] + let mask = (1L << 21) - 1L + for u in 0..> 21).to_int() + edges2.push((u, v, w * 3 + 7)) + } + } + let g2 = @dir.WeightedCsr::from_edges(n, edges2) + let rg2edges : Array[(Int, Int, Int)] = [] + for e in edges2 { + rg2edges.push((e.1, e.0, e.2)) + } + let rg2 = @dir.WeightedCsr::from_edges(n, rg2edges) + let recust_us = elapsed(fn() { cch.customize(g2) }) + let mut parity2_ok = true + for q in queries { + let (s, t) = q + let a = @dir.dijkstra_bidirectional_ctx(g2, rg2, cf, cb, s, t) + match (a, cch.query_dist(s, t)) { + (Some((_, x)), Some(y)) => if x.to_int64() != y { parity2_ok = false } + (None, None) => () + _ => parity2_ok = false + } + } + cch.customize(g) + let tb = @infra_text.TextBuilder::new() + tb.push_str("### \{dataset}(节点 \{n},边 \{edge_count})CCH\n\n") + tb.push_str( + "- 代价对拍一致(原权重): \{parity_ok}; 换权重后: \{parity2_ok}\n", + ) + tb.push_str( + "- CH 全量重建: \{ch_pre_us / 1.0e3} ms; CCH 度量无关构建(含首次 customize): \{cch_build_us / 1.0e3} ms\n", + ) + tb.push_str( + "- **customization(换权成本)中位: \{cust_med / 1.0e3} ms(相对 CH 重建 \{ch_pre_us / cust_med}×)**; 实测换权再绑: \{recust_us / 1.0e3} ms\n", + ) + tb.push_str( + "- 每查询中位: 双向 Dijkstra \{dij_med / qd} µs; CH \{ch_med / qd} µs; CCH \{cch_med / qd} µs(相对双向 Dijkstra \{dij_med / cch_med}×、相对 CH \{cch_med / ch_med}× 慢因子)\n", + ) + if !parity_ok || !parity2_ok { + tb.push_str( + "\n**警告:代价对拍存在不一致,数字无效。**\n", + ) + } + tb.build() +} + +///| +test "bench: collect real OSM road-network CCH artifacts" (b : @bench.T) { + let xm_md = run_osm_cch_benchmark_on( + "厦门驾车网", osm_xiamen_node_count, osm_xiamen_edge_count, osm_xiamen_payload, + osm_bench_queries, csr_bench_warmup, csr_bench_samples, + ) + let bj_md = run_osm_cch_benchmark_on( + "北京驾车网", osm_beijing_node_count, osm_beijing_edge_count, osm_beijing_payload, + osm_bench_queries, csr_bench_warmup, csr_bench_samples, + ) + println("===OSM_CCH_EVIDENCE_BEGIN===") + println(xm_md) + println(bj_md) + println("===OSM_CCH_EVIDENCE_END===") + b.bench(name="osm_cch_artifacts_emitted", fn() { + b.keep(xm_md.length() + bj_md.length()) + }) +} + +///| +test "guard: 真实 OSM 路网 CCH 代价对拍(前 8 组,含换权 customize)" { + let (g, rg) = build_osm_weighted_csr( + osm_xiamen_node_count, osm_xiamen_edge_count, osm_xiamen_payload, + ) + let cch = @dir.CustomizableCch::build(g) + let cf = @dir.SearchCtx::new(osm_xiamen_node_count) + let cb = @dir.SearchCtx::new(osm_xiamen_node_count) + let qrng = Lcg::new(query_seed_int) + for _ in 0..<8 { + let s = qrng.next() % osm_xiamen_node_count + let t = qrng.next() % osm_xiamen_node_count + let a = @dir.dijkstra_bidirectional_ctx(g, rg, cf, cb, s, t) + match (a, cch.query_dist(s, t)) { + (Some((_, x)), Some(y)) => assert_eq(x.to_int64(), y) + (None, None) => () + _ => abort("reachability mismatch") + } + } +} diff --git a/benches/results/cch-osm-20260706.md b/benches/results/cch-osm-20260706.md new file mode 100644 index 0000000..3317559 --- /dev/null +++ b/benches/results/cch-osm-20260706.md @@ -0,0 +1,39 @@ +# Customizable CH(CCH)真实 OSM 路网证据(2026-07-06) + +采集命令(与 osm_alt/ch 系列同口径:预热 3、采样 12、中位数、全查询代价对拍护栏): + +```bash +moon bench --target native -p benches/advanced_bench -f osm_cch_bench.mbt +``` + +实现:`src/directed/cch.mbt`(Dibbelt, Strasser, Wagner 2014)。度量无关 +最小度贪心收缩 + 弦图补全一次成型,之后换权只跑 basic customization +(下三角 relax,无堆无搜索),查询同 CH 双向向上 Dijkstra(含 stall)。 + +## 采集结果(原样粘贴) + +### 厦门驾车网(节点 23925,边 54151)CCH + +- 代价对拍一致(原权重): true; 换权重后: true +- CH 全量重建: 2110.014022 ms; CCH 度量无关构建(含首次 customize): 363.730591 ms +- **customization(换权成本)中位: 112.46901 ms(相对 CH 重建 18.760848183868607×)**; 实测换权再绑: 112.641383 ms +- 每查询中位: 双向 Dijkstra 649.5853541666667 µs; CH 37.903656250000004 µs; CCH 147.80785416666666 µs(相对双向 Dijkstra 4.394795918180375×、相对 CH 3.899567186652783× 慢因子) + +### 北京驾车网(节点 163501,边 406591)CCH + +- 代价对拍一致(原权重): true; 换权重后: true +- CH 全量重建: 25311.87677 ms; CCH 度量无关构建(含首次 customize): 5968.030457999999 ms +- **customization(换权成本)中位: 1895.7728155 ms(相对 CH 重建 13.35174582262597×)**; 实测换权再绑: 1919.576763 ms +- 每查询中位: 双向 Dijkstra 6131.049677083334 µs; CH 131.81360416666666 µs; CCH 780.2530208333334 µs(相对双向 Dijkstra 7.857771150357343×、相对 CH 5.9193664096064955× 慢因子) + +## 结论(诚实口径) + +- **换权成本(CCH 的卖点)**:北京 1.90 s vs CH 全量重建 25.3 s(**13.4×**), + 厦门 112 ms vs 2.11 s(**18.8×**)。适用于交通拥堵/自定义代价等频繁换权场景。 +- **度量无关构建**(一次性、可跨权重复用)也比 CH 重建快:北京 6.0 s、厦门 0.36 s。 +- **查询侧比 CH 慢 3.9–5.9×**(北京 780 µs vs 132 µs)——basic customization + 的骨架无 witness 剪枝、比 CH 密,这是 CCH 论文已知的取舍;相对双向 + Dijkstra 仍有 4.4–7.9× 加速。若查询占主导且权重稳定应选 CH/HL。 +- 正确性:48 组查询与双向 Dijkstra 全量对拍一致(原权重与 ×3+7 扰动换权后 + 各一轮),另有 `src/directed/cch_test.mbt` 100 迭代差分 PBT(含换权 + recustomize 再对拍)与厦门网 8 组守卫测试。 diff --git a/docs/CHAMPIONSHIP_BACKLOG.md b/docs/CHAMPIONSHIP_BACKLOG.md index f76ab81..9dccad6 100644 --- a/docs/CHAMPIONSHIP_BACKLOG.md +++ b/docs/CHAMPIONSHIP_BACKLOG.md @@ -129,7 +129,7 @@ Official contest page: - Evidence: `docs/verification/paper-to-code-advanced.md` — CH (Geisberger 2008) / JPS (Harabor & Grastien 2011) / ALT (Goldberg & Harrelson 2005), paper construct → code lines → tests, plus documented departures (witness budget, uniform-cost JPS); production variants in section 4 add HL / PHAST / RPHAST / many-to-many traceability, and stall-on-demand is now implemented in `src/directed/ch.mbt`. - [x] Prepare defense assets. - Acceptance: slides, script, Q&A, and offline demo all reflect the current repository instead of future plans. - - Evidence: `docs/presentation/slides.md`, `docs/presentation/video_script.md`, and `docs/rehearsal/qa.md` refreshed (2026-07-05) to cite measured OSM speedups and archived artifacts instead of hedged future-work language; bilingual READMEs list all 37 algorithms with identical status columns. + - Evidence: `docs/presentation/slides.md`, `docs/presentation/video_script.md`, and `docs/rehearsal/qa.md` refreshed (2026-07-05) to cite measured OSM speedups and archived artifacts instead of hedged future-work language; bilingual READMEs list all 38 algorithms with identical status columns (CCH added 2026-07-06, `benches/results/cch-osm-20260706.md`). ## Next Attack Order diff --git a/docs/presentation/slides.md b/docs/presentation/slides.md index 2cbed64..e8e003f 100644 --- a/docs/presentation/slides.md +++ b/docs/presentation/slides.md @@ -93,7 +93,7 @@ pub fn dijkstra[N, W](start, successors: (N) -> Array[(N, W)], goal) -> (Array[N | 图结构 | Kruskal / Connected Components / Tarjan SCC / Topo Sort | 已实现并测试 | | 流与匹配 | Edmonds-Karp / Kuhn-Munkres | 已实现并测试 | | 组合路径 | Bidirectional BFS / IDA* / Yen | 已实现并测试 | -| 前沿方向 | CH / JPS / ALT / Hub Labeling / PHAST / RPHAST / many-to-many | 生产级实现 + OSM 真实路网实测(北京:CH 46×、HL 14304×)+ 论文追踪 | +| 前沿方向 | CH / JPS / ALT / Hub Labeling / PHAST / RPHAST / many-to-many / CCH | 生产级实现 + OSM 真实路网实测(北京:CH 46×、HL 14304×、CCH 换权 13.4×)+ 论文追踪 | --- @@ -263,7 +263,7 @@ let result = ch_query(graph, source, target) | 可执行文档 | crate docs / tests | README.mbt.md 可由 `moon test` 执行 | | 合约验证 | 无内建证明链路 | runtime proof predicates 已测试 | | 多后端 | Rust native / wasm 需额外链路 | MoonBit wasm-gc / js / native CI 目标 | -| 前沿算法 | 无 CH/HL/PHAST 等路网 SOTA | 7 种已实现,OSM 实测证据归档(benches/results/ch-osm-20260705.md) | +| 前沿算法 | 无 CH/HL/PHAST 等路网 SOTA | 8 种已实现(含 CCH 权重换绑),OSM 实测证据归档(benches/results/ch-osm-20260705.md、cch-osm-20260706.md) | --- diff --git a/docs/presentation/video_script.md b/docs/presentation/video_script.md index a2507b8..c711c31 100644 --- a/docs/presentation/video_script.md +++ b/docs/presentation/video_script.md @@ -18,7 +18,7 @@ | 2:20-3:10 | README 即测试 | 运行 `moon test README.mbt.md`,说明 README 示例不是截图而是黑盒测试。 | 终端 + README | | 3:10-4:10 | Proof predicates | 展示 `src/proofs/bfs_proof.mbt` 与 `src/proofs/bfs_proof_test.mbt`,解释 runtime minimality witness。 | 代码高亮 | | 4:10-5:00 | 质量保障 | 运行 `scripts\acceptance.ps1 -SkipCoverage`,展示 check/fmt/test/doc/audit 链路。 | 终端录屏 | -| 5:00-5:45 | 高级算法 | 展示 7 种前沿路网算法(CH/ALT/HL/PHAST/RPHAST/m2m/JPS)与 OSM 真实路网实测归档(北京 CH 46×、HL 0.44 µs/14304×)。 | PPT + benches/results | +| 5:00-5:45 | 高级算法 | 展示 8 种前沿路网算法(CH/ALT/HL/PHAST/RPHAST/m2m/CCH/JPS)与 OSM 真实路网实测归档(北京 CH 46×、HL 0.44 µs/14304×、CCH 换权 13.4×)。 | PPT + benches/results | | 5:45-6:30 | 对标与差异化 | 对标 Rust pathfinding:MoonBit 原生、多后端、README doctest、runtime predicates、release evidence。 | 表格页 | | 6:30-7:00 | 路线图与结尾 | 下一步是边界回归、双语文档/答辩打磨、OSM benchmark 与 playground 取舍。 | 路线图页 | @@ -84,7 +84,7 @@ pwsh -NoLogo -NoProfile -ExecutionPolicy Bypass -File scripts\acceptance.ps1 -Sk ### 对标与差异化 (5:45-6:30) -> “和 Rust pathfinding 对标,我避免使用无法一次证明的绝对化说法。当前能证明的差异化是:MoonBit 原生、多后端目标、README 可执行、runtime proof predicates、中英文文档、7 种前沿路网算法的生产级 MoonBit 实现、OSM 真实路网实测归档(北京 CH 46×、HL 14304×)、跨语言等价负载对比基础设施与 release readiness evidence。未验证的外部语言绑定不当作当前事实。” +> “和 Rust pathfinding 对标,我避免使用无法一次证明的绝对化说法。当前能证明的差异化是:MoonBit 原生、多后端目标、README 可执行、runtime proof predicates、中英文文档、8 种前沿路网算法的生产级 MoonBit 实现(含 CCH 权重秒级换绑)、OSM 真实路网实测归档(北京 CH 46×、HL 14304×)、跨语言等价负载对比基础设施与 release readiness evidence。未验证的外部语言绑定不当作当前事实。” ### 路线图与结尾 (6:30-7:00) diff --git a/docs/rehearsal/qa.md b/docs/rehearsal/qa.md index 868ac1c..71ce690 100644 --- a/docs/rehearsal/qa.md +++ b/docs/rehearsal/qa.md @@ -53,7 +53,7 @@ > 2. successor function 风格适合 AI Agent 生成调用代码,也适合真实项目把数组、Map 或外部数据源接入。 > 3. `README.mbt.md` 可被 `moon test` 执行,文档不是静态宣传页。 > 4. runtime proof predicates 让路径合法性、代价一致性、BFS minimality 变成可运行检查。 -> 5. 7 种前沿路网算法(CH / ALT / HL / PHAST / RPHAST / m2m / JPS)Rust pathfinding 均未提供,且附真实 OSM 路网实测证据(北京 CH 46×、HL 14304×)。 +> 5. 8 种前沿路网算法(CH / ALT / HL / PHAST / RPHAST / m2m / CCH / JPS)Rust pathfinding 均未提供,且附真实 OSM 路网实测证据(北京 CH 46×、HL 14304×、CCH 换权 13.4×)。 > 6. 跨语言等价工作负载对比基础设施(`bench_rust/` + 逐位一致随机源 + 黄金交叉校验)把“和 Rust 比”变成可复现命令而非口号。 > > 也就是说,我的差异化不是“语言换皮”,而是把 MoonBit 的多后端、可执行文档和未来证明链路组合成一个可交付库。 diff --git a/docs/verification/paper-to-code-advanced.md b/docs/verification/paper-to-code-advanced.md index 2f6a8cf..3a28019 100644 --- a/docs/verification/paper-to-code-advanced.md +++ b/docs/verification/paper-to-code-advanced.md @@ -147,6 +147,22 @@ OSM 实测每对相对逐对 CH 北京 16.0× / 厦门 24.8×。 - **ALT 生产级(`src/directed/alt.mbt`)**:farthest 选点 + 节点主序 布局 + INF 统一截断保证下界可采纳一致;OSM 北京 6.6×。 +### 4.7 Customizable CH 生产级(`src/directed/cch.mbt`) + +- **论文**:Dibbelt, Strasser & Wagner 2014(Customizable Route Planning + in Road Networks 谱系)。 +- **实现要点**:度量无关最小度贪心收缩 + 弦图补全一次成型 + (`CustomizableCch::build`);换权只跑 basic customization——按 + rank 升序下三角 relax(`customize`,无堆无搜索);查询与 CH 同款 + 双向向上 Dijkstra 含 stall-on-demand(`query_dist`)。 +- **测试**:`src/directed/cch_test.mbt` 差分 PBT 100 迭代(含同拓扑 + 换权重 recustomize 再对拍);`benches/advanced_bench/osm_cch_bench.mbt` + 厦门网 8 组守卫。 +- **OSM 实测**(`benches/results/cch-osm-20260706.md`):换权成本北京 + 1.90 s vs CH 全量重建 25.3 s(**13.4×**)、厦门 **18.8×**;查询比 CH + 慢 3.9–5.9×(论文已知取舍,无 witness 剪枝骨架更密),相对双向 + Dijkstra 仍 4.4–7.9×。 + --- ## 5. 取舍与已知边界(答辩 Q&A 素材) diff --git a/docs/zh/development-article.md b/docs/zh/development-article.md index 997bd45..9e31373 100644 --- a/docs/zh/development-article.md +++ b/docs/zh/development-article.md @@ -20,7 +20,7 @@ NetworkX,而 MoonBit 在本项目之前没有对应基础设施。 而是真实 OSM 路网上可测量的数量级加速; 3. **可信度**:每个声明都有可复现证据,每个优化都有差分测试守卫。 -最终交付:**30 种经典算法 + 7 种前沿路网算法**,三后端 +最终交付:**30 种经典算法 + 8 种前沿路网算法**,三后端 (native / wasm-gc / js)2158 项测试全绿,真实北京驾车路网上 距离查询从双向 Dijkstra 的 6.3 ms 压到 Hub Labeling 的 **0.44 µs(14304×)**。 @@ -135,7 +135,7 @@ PHAST(Delling 2011)把一到全查询拆成向上 Dijkstra + rank 降序 的用例标注并排除。方法学声明、机器信息与两套工具链版本全部写进 报告工件(`benches/results/latest-rust-comparison.{md,json}`)。 -差异化不在"语言换皮",而在 Rust `pathfinding` 未提供的 7 种 +差异化不在"语言换皮",而在 Rust `pathfinding` 未提供的 8 种 前沿路网算法,以及 MoonBit 的多后端一键部署(同一份算法代码 驱动 native 基准与浏览器 playground)。 diff --git a/src/directed/cch.mbt b/src/directed/cch.mbt new file mode 100644 index 0000000..a5fe325 --- /dev/null +++ b/src/directed/cch.mbt @@ -0,0 +1,375 @@ +// cch.mbt —— Customizable Contraction Hierarchies(Dibbelt, Strasser, +// Wagner 2014)。 +// +// CH 的预处理把「收缩序 + 捷径拓扑」与「边权」耦合在一起:权重一变 +// (交通拥堵、限行、自定义代价)就要整套重建(OSM 北京 ~24.6 s)。 +// CCH 把两者解耦: +// 1. **度量无关阶段**(一次性):只看拓扑,按最小度贪心序收缩, +// 对被删节点的存活邻居对补骨架边(弦图补全,不做 witness 搜索 +// ——没有权重可搜); +// 2. **customization**(每次换权,毫秒~秒级):把原图权写进骨架 +// 边的上/下行两个方向,再按 rank 升序枚举每个节点的上邻三角 +// 做下三角 relax,使上行图恢复最短路保持性; +// 3. **查询**:与 CH 相同的双向「向上」Dijkstra(含 stall-on- +// demand),距离与原图 Dijkstra 一致。 +// +// 语义由差分 PBT(含换权重再 customize 再对拍)与 OSM 全量对拍守卫。 + +///| +/// CCH 产物:度量无关骨架(拓扑 + 收缩序)与当前 customize 的权。 +/// 骨架边按低 rank 端点分桶存高 rank 邻居(上邻表,按节点编号有序, +/// 供三角枚举二分定位),每条骨架边带两个可换绑方向权。 +pub struct CustomizableCch { + n : Int + /// rank[v] = 收缩序(越大越重要)。 + rank : Array[Int] + /// rank 升序节点序(customization 处理序)。 + ord : Array[Int] + /// 上邻 CSR:fo[u]..fo[u+1] 为低端点 u 的高 rank 邻居(按编号有序)。 + fo : Array[Int] + ft : Array[Int] + /// 上行方向权 u→v(customize 写入;INF_DIST = 无原图对应)。 + fw : Array[Int64] + /// 下行方向权 v→u。 + gw : Array[Int64] + // 查询暂存(代戳懒失效,单线程复用)。 + df : Array[Int64] + db : Array[Int64] + sf : Array[Int] + sb : Array[Int] + mut gen : Int + hf : RadixHeap + hb : RadixHeap +} + +///| +/// 有序数组二分查 target,返回下标或 -1。 +fn cch_bsearch(ft : Array[Int], lo0 : Int, hi0 : Int, target : Int) -> Int { + let mut lo = lo0 + let mut hi = hi0 + while lo < hi { + let mid = lo + (hi - lo) / 2 + let t = ft[mid] + if t == target { + return mid + } else if t < target { + lo = mid + 1 + } else { + hi = mid + } + } + -1 +} + +///| +/// 度量无关构建:最小度贪心懒更新收缩 + 弦图补全,随后用 g 的权做 +/// 首次 customization。拓扑只依赖 g 的边集(方向合并为无向骨架)。 +pub fn CustomizableCch::build(g : WeightedCsr) -> CustomizableCch { + let n = g.node_count() + let mask = (1L << NODE_BITS) - 1L + // 无向骨架邻接:每节点有序邻居表(二分去重/删除)。 + let adj : Array[Array[Int]] = [] + for _ in 0.. Unit { + let mut lo = 0 + let mut hi = a.length() + while lo < hi { + let mid = lo + (hi - lo) / 2 + if a[mid] == v { + return + } else if a[mid] < v { + lo = mid + 1 + } else { + hi = mid + } + } + a.insert(lo, v) + } + + fn adj_remove(a : Array[Int], v : Int) -> Unit { + let mut lo = 0 + let mut hi = a.length() + while lo < hi { + let mid = lo + (hi - lo) / 2 + if a[mid] == v { + let _ = a.remove(mid) + return + } else if a[mid] < v { + lo = mid + 1 + } else { + hi = mid + } + } + } + + for u in 0.. 0 { + let enc = heap.pop() + let v = (enc & mask).to_int() + if rank[v] >= 0 { + continue + } + let deg = adj[v].length() + if (enc >> NODE_BITS).to_int() != deg { + heap.push((deg.to_int64() << NODE_BITS) | v.to_int64()) + continue + } + rank[v] = next_rank + ord[next_rank] = v + next_rank = next_rank + 1 + let nbrs = adj[v] + edge_total = edge_total + nbrs.length() + for i = 0; i < nbrs.length(); i = i + 1 { + ups[v].push(nbrs[i]) + } + // 弦图补全:存活邻居两两连边,并把 v 从各邻接表中摘除。 + for i = 0; i < nbrs.length(); i = i + 1 { + let a = nbrs[i] + adj_remove(adj[a], v) + for j = i + 1; j < nbrs.length(); j = j + 1 { + let b = nbrs[j] + adj_insert(adj[a], b) + adj_insert(adj[b], a) + } + } + adj[v].clear() + } + // 上邻 CSR(ups[v] 收缩时即有序,直接铺平)。 + let fo = Array::make(n + 1, 0) + for v in 0.. Unit { + let mask = (1L << NODE_BITS) - 1L + let fw = self.fw + let gw = self.gw + let ft = self.ft + let fo = self.fo + for ei = 0; ei < fw.length(); ei = ei + 1 { + fw[ei] = INF_DIST + gw[ei] = INF_DIST + } + // 原图权注入:u→v 落在骨架边 {u,v} 的对应方向上(保留更小权)。 + for u in 0..> NODE_BITS + if self.rank[u] < self.rank[v] { + let idx = cch_bsearch(ft, fo[u], fo[u + 1], v) + if fw[idx] > w { + fw[idx] = w + } + } else { + let idx = cch_bsearch(ft, fo[v], fo[v + 1], u) + if gw[idx] > w { + gw[idx] = w + } + } + } + } + // 下三角 relax:三角 {x,u,v}(rank x < u < v),经 x 的两跳收紧 + // 骨架边 {u,v} 的两个方向。 + for oi = 0; oi < self.n; oi = oi + 1 { + let x = self.ord[oi] + let stop = fo[x + 1] + for i = fo[x]; i < stop; i = i + 1 { + let a = ft[i] + for j = i + 1; j < stop; j = j + 1 { + let b = ft[j] + let (u, eu, v, ev) = if self.rank[a] < self.rank[b] { + (a, i, b, j) + } else { + (b, j, a, i) + } + let idx = cch_bsearch(ft, fo[u], fo[u + 1], v) + // u→x→v 收紧上行 u→v;v→x→u 收紧下行 v→u。 + let up = gw[eu] + fw[ev] + if up < fw[idx] { + fw[idx] = up + } + let dn = gw[ev] + fw[eu] + if dn < gw[idx] { + gw[idx] = dn + } + } + } + } +} + +///| +/// 点对点距离查询:双向「向上」Dijkstra(前向走上行权、后向走下行 +/// 权,均含 stall-on-demand),会合取 min。不可达返回 `None`。语义 +/// 与 `dijkstra_csr` 的 s→t 距离一致。 +pub fn CustomizableCch::query_dist( + self : CustomizableCch, + start : Int, + target : Int, +) -> Int64? { + if start < 0 || start >= self.n || target < 0 || target >= self.n { + return None + } + self.gen = self.gen + 1 + let gen = self.gen + let mask = (1L << NODE_BITS) - 1L + let df = self.df + let db = self.db + let sf = self.sf + let sb = self.sb + let fo = self.fo + let ft = self.ft + let fw = self.fw + let gw = self.gw + let hf = self.hf + let hb = self.hb + hf.reset(0L) + hb.reset(0L) + df[start] = 0L + sf[start] = gen + hf.push(start.to_int64()) + db[target] = 0L + sb[target] = gen + hb.push(target.to_int64()) + let mut mu = INF_DIST + while hf.len > 0 || hb.len > 0 { + let ff = if hf.len > 0 { hf.peek() >> NODE_BITS } else { INF_DIST } + let fb = if hb.len > 0 { hb.peek() >> NODE_BITS } else { INF_DIST } + if ff >= mu && fb >= mu { + break + } + if ff <= fb { + let enc = hf.pop() + let u = (enc & mask).to_int() + let du = enc >> NODE_BITS + if du > df[u] { + continue + } + let stop = fo[u + 1] + // stall-on-demand:更高 rank 上邻 v 经下行 v→u 更短则免松弛。 + let mut stalled = false + for ei = fo[u]; ei < stop; ei = ei + 1 { + let v = ft[ei] + if sf[v] == gen && df[v] + gw[ei] < du { + stalled = true + break + } + } + if stalled { + continue + } + if sb[u] == gen && du + db[u] < mu { + mu = du + db[u] + } + for ei = fo[u]; ei < stop; ei = ei + 1 { + let v = ft[ei] + let nd = du + fw[ei] + if nd < INF_DIST && (sf[v] != gen || nd < df[v]) { + df[v] = nd + sf[v] = gen + hf.push((nd << NODE_BITS) | v.to_int64()) + } + } + } else { + let enc = hb.pop() + let u = (enc & mask).to_int() + let du = enc >> NODE_BITS + if du > db[u] { + continue + } + let stop = fo[u + 1] + let mut stalled = false + for ei = fo[u]; ei < stop; ei = ei + 1 { + let v = ft[ei] + if sb[v] == gen && db[v] + fw[ei] < du { + stalled = true + break + } + } + if stalled { + continue + } + if sf[u] == gen && du + df[u] < mu { + mu = du + df[u] + } + for ei = fo[u]; ei < stop; ei = ei + 1 { + let v = ft[ei] + let nd = du + gw[ei] + if nd < INF_DIST && (sb[v] != gen || nd < db[v]) { + db[v] = nd + sb[v] = gen + hb.push((nd << NODE_BITS) | v.to_int64()) + } + } + } + } + if mu >= INF_DIST { + None + } else { + Some(mu) + } +} diff --git a/src/directed/cch_test.mbt b/src/directed/cch_test.mbt new file mode 100644 index 0000000..2770a66 --- /dev/null +++ b/src/directed/cch_test.mbt @@ -0,0 +1,92 @@ +// cch_test.mbt —— Customizable CH 黑盒测试:距离与 Dijkstra 差分 PBT, +// 核心场景是**换权重后 customize 再对拍**(CCH 相对 CH 的价值所在)。 + +///| +test "CustomizableCch basic: chain with recustomization" { + let edges : Array[(Int, Int, Int)] = [ + (0, 1, 2), + (1, 2, 3), + (2, 3, 4), + (0, 3, 10), + ] + let g = @directed.WeightedCsr::from_edges(4, edges) + let cch = @directed.CustomizableCch::build(g) + assert_true(cch.query_dist(0, 3) is Some(9L)) + assert_true(cch.query_dist(3, 0) is None) + assert_true(cch.query_dist(2, 2) is Some(0L)) + // 换权重(同拓扑):直连边变便宜,链路变贵。 + let edges2 : Array[(Int, Int, Int)] = [ + (0, 1, 20), + (1, 2, 30), + (2, 3, 40), + (0, 3, 10), + ] + let g2 = @directed.WeightedCsr::from_edges(4, edges2) + cch.customize(g2) + assert_true(cch.query_dist(0, 3) is Some(10L)) + assert_true(cch.query_dist(0, 2) is Some(50L)) +} + +///| +test "CustomizableCch differential PBT vs dijkstra (100 iterations, 含换权重 customize 再对拍)" { + let rng = @infra_pbt.rng_new(0xCC_47AC7UL) + for iter in 0..<100 { + let n = 2 + rng.next_below(50) + let m = rng.next_below(n * 3) + let us : Array[Int] = [] + let vs : Array[Int] = [] + let edges : Array[(Int, Int, Int)] = [] + for _ in 0.. assert_eq(gc, ec.to_int64()) + (None, None) => () + _ => abort("reachability mismatch (initial customize)") + } + } + // 同拓扑换权重:只跑 customize(不重建),再与 Dijkstra 对拍。 + let edges2 : Array[(Int, Int, Int)] = [] + for i = 0; i < m; i = i + 1 { + edges2.push((us[i], vs[i], 1 + rng.next_below(500))) + } + let g2 = @directed.WeightedCsr::from_edges(n, edges2) + cch.customize(g2) + for _q in 0..<6 { + let s = rng.next_below(n) + let t = rng.next_below(n) + let expect = @directed.dijkstra_indexed_ctx(g2, ctx, s, t) + let got = cch.query_dist(s, t) + match (expect, got) { + (Some((_, ec)), Some(gc)) => assert_eq(gc, ec.to_int64()) + (None, None) => () + _ => abort("reachability mismatch (recustomize)") + } + } + } +} diff --git a/src/directed/pkg.generated.mbti b/src/directed/pkg.generated.mbti index 036fcdf..79c5756 100644 --- a/src/directed/pkg.generated.mbti +++ b/src/directed/pkg.generated.mbti @@ -132,6 +132,26 @@ pub fn ContractionHierarchy::query(Self, Int, Int) -> (Array[Int], Int)? pub fn ContractionHierarchy::rphast_query(Self, RphastTargets, Int, Array[Int64]) -> Unit pub fn ContractionHierarchy::rphast_targets(Self, Array[Int]) -> RphastTargets +pub struct CustomizableCch { + n : Int + rank : Array[Int] + ord : Array[Int] + fo : Array[Int] + ft : Array[Int] + fw : Array[Int64] + gw : Array[Int64] + df : Array[Int64] + db : Array[Int64] + sf : Array[Int] + sb : Array[Int] + mut gen : Int + hf : RadixHeap + hb : RadixHeap +} +pub fn CustomizableCch::build(WeightedCsr) -> Self +pub fn CustomizableCch::customize(Self, WeightedCsr) -> Unit +pub fn CustomizableCch::query_dist(Self, Int, Int) -> Int64? + type EncodedHeap pub struct HubLabels {