feat(directed): Customizable CH(CCH,Dibbelt 2014)——权重换绑 13–19× 于 CH 重建

- 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>
This commit is contained in:
Suquster 2026-07-06 13:26:28 +00:00
parent 0eda5603da
commit 060fd43f41
13 changed files with 733 additions and 10 deletions

View File

@ -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 µs14304×**PHAST 一到全 SSSP
@ -235,6 +235,7 @@ CH **1625×**RPHAST 目标子集限定再提 **6.99.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 实测 1625× | [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.99.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 实测换权 1319× | [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源码 + 单元测试已合入主干

View File

@ -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 µs14304×**PHAST 一到全 SSSP
@ -181,6 +181,7 @@ CH **1625×**RPHAST 目标子集限定再提 **6.99.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 实测 1625× | Knopp, Sanders, Schultes, Schulz & Wagner 2007 |
| 37 | 🔥 RPHAST目标子集限定 | `src/directed/rphast.mbt` | ✅ OSM 实测 6.99.8× | Delling, Goldberg, Nowatzyk & Werneck 2011 |
| 38 | 🔥 Customizable CHCCH | `src/directed/cch.mbt` | ✅ OSM 实测换权 1319× | Dibbelt, Strasser & Wagner 2014 |
> 🔥 = **Rust `pathfinding` crate 未实现的独家算法**
> 🧪 experimental = 源码与测试已存在,但 API / 性能证据尚未冻结

View File

@ -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..<qcount {
queries.push((qrng.next() % n, qrng.next() % n))
}
// CH 全量重建耗时(换权时 CH 的必付成本,对比项)。
let mut ch_opt : @dir.ContractionHierarchy? = None
let ch_pre_us = elapsed(fn() {
ch_opt = Some(@dir.ContractionHierarchy::build(g))
})
let ch = ch_opt.unwrap()
// CCH 度量无关构建(含首次 customize
let mut cch_opt : @dir.CustomizableCch? = None
let cch_build_us = elapsed(fn() {
cch_opt = Some(@dir.CustomizableCch::build(g))
})
let cch = cch_opt.unwrap()
// 换权成本customization 单独计时(中位数)。
let cust_samples = collect_samples(warmup, samples, fn() { cch.customize(g) })
let cust_med = csr_bench_median(cust_samples)
// 代价全量对拍护栏(原权重)。
let cf = @dir.SearchCtx::new(n)
let cb = @dir.SearchCtx::new(n)
let mut parity_ok = true
for q in queries {
let (s, t) = q
let a = @dir.dijkstra_bidirectional_ctx(g, rg, cf, cb, s, t)
match (a, cch.query_dist(s, t), ch.query(s, t)) {
(Some((_, x)), Some(y), Some((_, z))) =>
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..<n {
let stop = g.offsets[u + 1]
for ei = g.offsets[u]; ei < stop; ei = ei + 1 {
let pe = g.packed[ei]
let v = (pe & mask).to_int()
let w = (pe >> 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")
}
}
}

View File

@ -0,0 +1,39 @@
# Customizable CHCCH真实 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边 54151CCH
- 代价对拍一致(原权重): 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边 406591CCH
- 代价对拍一致(原权重): 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.95.9×**(北京 780 µs vs 132 µs——basic customization
的骨架无 witness 剪枝、比 CH 密,这是 CCH 论文已知的取舍;相对双向
Dijkstra 仍有 4.47.9× 加速。若查询占主导且权重稳定应选 CH/HL。
- 正确性48 组查询与双向 Dijkstra 全量对拍一致(原权重与 ×3+7 扰动换权后
各一轮),另有 `src/directed/cch_test.mbt` 100 迭代差分 PBT含换权
recustomize 再对拍)与厦门网 8 组守卫测试。

View File

@ -129,7 +129,7 @@ Official contest page: <https://www.moonbitlang.cn/2026-scc>
- 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

View File

@ -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 |
---

View File

@ -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 pathfindingMoonBit 原生、多后端、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)

View File

@ -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 / JPSRust pathfinding 均未提供,且附真实 OSM 路网实测证据(北京 CH 46×、HL 14304×
> 5. 8 种前沿路网算法CH / ALT / HL / PHAST / RPHAST / m2m / CCH / JPSRust pathfinding 均未提供,且附真实 OSM 路网实测证据(北京 CH 46×、HL 14304×、CCH 换权 13.4×
> 6. 跨语言等价工作负载对比基础设施(`bench_rust/` + 逐位一致随机源 + 黄金交叉校验)把“和 Rust 比”变成可复现命令而非口号。
>
> 也就是说,我的差异化不是“语言换皮”,而是把 MoonBit 的多后端、可执行文档和未来证明链路组合成一个可交付库。

View File

@ -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 2014Customizable 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.95.9×(论文已知取舍,无 witness 剪枝骨架更密),相对双向
Dijkstra 仍 4.47.9×。
---
## 5. 取舍与已知边界(答辩 Q&A 素材)

View File

@ -20,7 +20,7 @@ NetworkX而 MoonBit 在本项目之前没有对应基础设施。
而是真实 OSM 路网上可测量的数量级加速;
3. **可信度**:每个声明都有可复现证据,每个优化都有差分测试守卫。
最终交付:**30 种经典算法 + 7 种前沿路网算法**,三后端
最终交付:**30 种经典算法 + 8 种前沿路网算法**,三后端
native / wasm-gc / js2158 项测试全绿,真实北京驾车路网上
距离查询从双向 Dijkstra 的 6.3 ms 压到 Hub Labeling 的
**0.44 µs14304×**。
@ -135,7 +135,7 @@ PHASTDelling 2011把一到全查询拆成向上 Dijkstra + rank 降序
的用例标注并排除。方法学声明、机器信息与两套工具链版本全部写进
报告工件(`benches/results/latest-rust-comparison.{md,json}`)。
差异化不在"语言换皮",而在 Rust `pathfinding` 未提供的 7
差异化不在"语言换皮",而在 Rust `pathfinding` 未提供的 8
前沿路网算法,以及 MoonBit 的多后端一键部署(同一份算法代码
驱动 native 基准与浏览器 playground

375
src/directed/cch.mbt Normal file
View File

@ -0,0 +1,375 @@
// cch.mbt —— Customizable Contraction HierarchiesDibbelt, 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]
/// 上邻 CSRfo[u]..fo[u+1] 为低端点 u 的高 rank 邻居(按编号有序)。
fo : Array[Int]
ft : Array[Int]
/// 上行方向权 u→vcustomize 写入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..<n {
adj.push([])
}
fn adj_insert(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 {
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..<n {
let stop = g.offsets[u + 1]
for ei = g.offsets[u]; ei < stop; ei = ei + 1 {
let v = (g.packed[ei] & mask).to_int()
if v != u {
adj_insert(adj[u], v)
adj_insert(adj[v], u)
}
}
}
// 最小度贪心懒更新pop 后度数变了就按当前度重新入队)。
let rank = Array::make(n, -1)
let ord = Array::make(n, 0)
let heap = RadixHeap::new()
heap.reset(0L)
for v in 0..<n {
heap.push((adj[v].length().to_int64() << NODE_BITS) | v.to_int64())
}
// 收缩时定稿的上邻表v 收缩瞬间的存活邻居即其全部高 rank 邻居)。
let ups : Array[Array[Int]] = []
for _ in 0..<n {
ups.push([])
}
let mut next_rank = 0
let mut edge_total = 0
while heap.len > 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()
}
// 上邻 CSRups[v] 收缩时即有序,直接铺平)。
let fo = Array::make(n + 1, 0)
for v in 0..<n {
fo[v + 1] = fo[v] + ups[v].length()
}
let ft = Array::make(edge_total, 0)
for v in 0..<n {
let base = fo[v]
let uv = ups[v]
for i = 0; i < uv.length(); i = i + 1 {
ft[base + i] = uv[i]
}
}
let cch = {
n,
rank,
ord,
fo,
ft,
fw: Array::make(edge_total, INF_DIST),
gw: Array::make(edge_total, INF_DIST),
df: Array::make(n, 0L),
db: Array::make(n, 0L),
sf: Array::make(n, 0),
sb: Array::make(n, 0),
gen: 0,
hf: RadixHeap::new(),
hb: RadixHeap::new(),
}
cch.customize(g)
cch
}
///|
/// 换绑权重:把 g 的边权写进骨架(拓扑必须与 build 时同边集),再做
/// basic customization——按 rank 升序对每个节点的上邻对做下三角
/// relax恢复上行图的最短路保持性。O(骨架三角数),无堆无搜索。
pub fn CustomizableCch::customize(
self : CustomizableCch,
g : WeightedCsr,
) -> 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..<self.n {
let stop = g.offsets[u + 1]
for ei = g.offsets[u]; ei < stop; ei = ei + 1 {
let pe = g.packed[ei]
let v = (pe & mask).to_int()
if v == u {
continue
}
let w = pe >> 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→vv→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)
}
}

92
src/directed/cch_test.mbt Normal file
View File

@ -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..<m {
let u = rng.next_below(n)
let mut v = rng.next_below(n)
if v == u {
v = (u + 1) % n
}
us.push(u)
vs.push(v)
edges.push(
(
u,
v,
if iter % 3 == 2 {
1 + rng.next_below(60) + 2000
} else {
1 + rng.next_below(60)
},
),
)
}
let g = @directed.WeightedCsr::from_edges(n, edges)
let cch = @directed.CustomizableCch::build(g)
let ctx = @directed.SearchCtx::new(n)
for _q in 0..<6 {
let s = rng.next_below(n)
let t = rng.next_below(n)
let expect = @directed.dijkstra_indexed_ctx(g, 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 (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)")
}
}
}
}

View File

@ -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 {