{"id":30273170,"url":"https://github.com/opensvm/bmssp-benchmark-game","last_synced_at":"2025-08-19T09:02:05.686Z","repository":{"id":309837189,"uuid":"1037125491","full_name":"openSVM/bmssp-benchmark-game","owner":"openSVM","description":"bounded multi-source shortest paths benches in Rust, C, C++, Nim, Crystal, Kotlin (JAR), Elixir (.exs), Erlang (.erl).","archived":false,"fork":false,"pushed_at":"2025-08-14T02:56:54.000Z","size":5095,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-14T04:33:03.852Z","etag":null,"topics":["benchmark","benchmark-game","bmssp","bounded-multi-source-shortest-paths","c","cpp","crystal","elixir","erlang","graph-algorithms","graphs","graphs-theory","kotlin","nim","rust"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/openSVM.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-13T05:29:31.000Z","updated_at":"2025-08-14T02:56:57.000Z","dependencies_parsed_at":"2025-08-14T04:43:12.773Z","dependency_job_id":null,"html_url":"https://github.com/openSVM/bmssp-benchmark-game","commit_stats":null,"previous_names":["opensvm/bmssp-benchmark-game"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/openSVM/bmssp-benchmark-game","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openSVM%2Fbmssp-benchmark-game","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openSVM%2Fbmssp-benchmark-game/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openSVM%2Fbmssp-benchmark-game/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openSVM%2Fbmssp-benchmark-game/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/openSVM","download_url":"https://codeload.github.com/openSVM/bmssp-benchmark-game/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/openSVM%2Fbmssp-benchmark-game/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":270678574,"owners_count":24626919,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-16T02:00:11.002Z","response_time":91,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["benchmark","benchmark-game","bmssp","bounded-multi-source-shortest-paths","c","cpp","crystal","elixir","erlang","graph-algorithms","graphs","graphs-theory","kotlin","nim","rust"],"created_at":"2025-08-16T06:40:32.604Z","updated_at":"2025-08-16T06:40:35.283Z","avatar_url":"https://github.com/openSVM.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bmssp benchmark game\n\nImplementation of the algorithm in multiple languages to compare performance in a standard GitHub Actions environment.\n\nLanguages currently wired in this repo: Rust, C, C++, Nim, Crystal, Kotlin (JAR), Elixir (.exs), Erlang (.erl).\n\n## Benchmark snapshot\n\n| impl          | lang   | graph | n    | m    | k | B  | seed | threads | time_ns | popped | edges_scanned | heap_pushes | B_prime | mem_bytes |\n|---------------|--------|-------|------|------|---|----|------|---------|---------|--------|---------------|-------------|---------|-----------|\n| rust-bmssp    | Rust   | grid  | 2500 | 9800 | 4 | 50 | 1    | 1       | 741251  | 868    | 3423          | 1047        | 50      | 241824    |\n| c-bmssp       | C      | grid  | 2500 | 9800 | 4 | 50 | 1    | 1       | 99065   | 1289   | 5119          | 1565        | 50      | 176800    |\n| cpp-bmssp     | C++    | grid  | 2500 | 9800 | 4 | 50 | 1    | 1       | 117480  | 1064   | 4224          | 1261        | 50      | 176800    |\n| kotlin-bmssp  | Kotlin | grid  | 2500 | 9800 | 4 | 50 | 1    | 1       | 5308820 | 1102   | 4386          | 1309        | 50      | 196800    |\n| elixir-bmssp  | Elixir | grid  | 2500 | 9800 | 4 | 50 | 1    | 1       | 5410039 | 870    | 3447          | 1047        | 50      | 196800    |\n| erlang-bmssp  | Erlang | grid  | 2500 | 9800 | 4 | 50 | 1    | 1       | 1155739 | 691    | 2701          | 818         | 50      | 196800    |\n\nRust implementation of **bounded multi-source shortest paths** (multi-source Dijkstra cut off at `B`).\n\n## Run\n\n```bash\ncargo test\ncargo bench -p bmssp\npython3 bench/runner.py --release --out results\n\n# fast iteration\npython3 bench/runner.py --quick --out results-dev --timeout-seconds 20 --jobs 2\n\n# 1000x larger graphs (heavy):\npython3 bench/runner.py --params bench/params_1000x.yaml --release --jobs 4 --timeout-seconds 600 --out results\n```\n\n### One-time setup scripts\n\n- Linux/macOS:\n  - Run `scripts/install_deps.sh --yes` to auto-install Rust, Python deps, build tools, Crystal/shards, Nim.\n  - Use `--check-only` to only report missing items.\n- Windows:\n  - Open PowerShell (as Administrator recommended) and run `scripts/Install-Dependencies.ps1 -Yes`.\n  - Add `-CheckOnly` to only report.\n\n## Complexity\n\nLet `U = { v | d(v) \u003c B }` and `E(U)` be edges scanned from `U`.\n\n- Time: `O((|E(U)| + |U|) log |U|)` with binary heap; worst-case `O((m+n) log n)`.\n- Space: `Θ(n + m)` graph + `Θ(n)` distances/flags + heap bounded by frontier size.\n\nUse `Graph::memory_estimate_bytes()` to get a byte estimate at runtime.\n\n\nHere’s the no-BS, self-contained write-up you asked for. It includes the theory, proofs at the right granularity, complexity, comparisons against the usual suspects, and **charts** (model-based, not empirical—use them to reason about trends, not absolutes).\n\n---\n\n# Bounded Multi-Source Shortest Paths (BMSSP)\n\n## What problem it solves\n\nGiven a directed graph $G=(V,E)$ with non-negative weights $w:E\\to \\mathbb{R}_{\\ge 0}$, a **set of sources** $S\\subseteq V$ with initial offsets $d_0(s)$ (usually 0), and a **distance bound** $B$, compute:\n\n* For every vertex $v$ with true shortest-path distance $d(v) \u003c B$, the exact distance $d(v)$.\n* The **explored set** $U=\\{v\\in V\\mid d(v)\u003cB\\}$.\n* The **tight boundary** $B' = \\min\\{ \\hat d(x)\\mid x \\notin U\\}$, i.e., the smallest tentative label never popped (next frontier).\n\nThis is **Dijkstra from multiple sources that halts when the next tentative key would be $\\ge B$**.\n\n---\n\n## Algorithm (binary-heap version)\n\nSame invariant as Dijkstra: a node is popped exactly once with its final shortest distance. The only change is the early-exit cut.\n\n**Initialize**\n\n* $\\forall v\\in V:\\ \\text{dist}[v] \\leftarrow +\\infty$\n* For each source $s\\in S$: if $d_0(s) \u003c B$ set $\\text{dist}[s]\\leftarrow d_0(s)$ and push $(d_0(s),s)$ into a min-PQ.\n* $B' \\leftarrow B$\n\n**Main loop**\n\n* Pop $(d,u)$. If $d \\ne \\text{dist}[u]$ continue (stale).\n* If $d \\ge B$, set $B'\\gets d$ and **stop**.\n* For each edge $(u,v,w)$:\n  $\\text{nd}=d+w$.\n\n  * If $\\text{nd} \u003c \\text{dist}[v]$ and $\\text{nd} \u003c B$: relax and push.\n  * Else if $\\text{nd} \\ge B$: update $B' \\leftarrow \\min(B', \\text{nd})$ (tracks the smallest over-bound key observed).\n\nReturn $(U, B')$ where $U$ are the popped vertices.\n\n\u003e Multi-source is trivial: seed multiple entries. All standard Dijkstra proofs still apply.\n\n---\n\n## Correctness (sketch you can actually use)\n\n**Invariant** (Dijkstra): When a node $u$ is popped, $\\text{dist}[u]=d(u)$.\nProof carries over because (1) weights $\\ge 0$, (2) heap order guarantees we never pop a label larger than any alternate path to that node, (3) early exit only **reduces** the set of popped nodes; it never allows popping out of order.\n\n**Boundary lemma.** Let $U$ be nodes popped before exit. Then\n$B' = \\min\\{\\hat d(x)\\mid x\\in V\\setminus U\\}$ — the smallest tentative distance still in the heap (or discovered and $\\ge B$).\nSo $B' \\ge B$ and is the next tight phase boundary if you continue search.\n\n---\n\n## Complexity\n\nLet $U=\\{v\\mid d(v)\u003cB\\}$ and $E(U)=\\{(u,v)\\in E\\mid u\\in U\\}$.\n\n* **Time (binary heap):**\n\n  $$\n  T = \\mathcal{O}\\big((|E(U)| + |U|)\\log |U|\\big)\n  $$\n\n  In the worst case ($B$ large), $|U|=n$, $|E(U)|=m$ → standard $\\mathcal{O}((m+n)\\log n)$.\n\n* **Space:** Graph storage $\\Theta(n+m)$ + working arrays $\\Theta(n)$ + heap up to $\\Theta(|U|)$.\n  Asymptotically $\\Theta(n+m)$.\n\n* **Multi-source impact:** Doesn’t change big-O. Practically lowers the radius needed to reach a given $f = |U|/n$; you explore **more shallow balls from more centers** instead of a deep ball from one center.\n\n---\n\n## Where BMSSP wins / loses\n\n**Wins**\n\n* You only care about a **radius** (range search, k-hop neighborhoods, geo/proximity queries, limited-distance labeling).\n* **Repeated queries** that increase $B$ gradually; reuse $B'$ to chunk work in phases.\n* Graphs where $f=|U|/n \\ll 1$ for your typical $B$. Cost drops roughly like $f \\log (fn)$.\n\n**Loses**\n\n* $B$ approaches graph diameter ⇒ you’re doing full SSSP; there’s no magic: you pay Dijkstra.\n* Integer weights with **small range**: specialized queues (Dial/radix) beat binary heaps.\n* Heavy parallel iron: **Δ-stepping** or GPU SSSP can dominate on large, sparse graphs with good partitioning.\n\n---\n\n## Comparison with existing SSSP families\n\n| Method                         | Weights                  |                          Time (typical) |             Memory | Pros                                 | Cons                                                             |       |   |    |               |                                                   |                               |\n| ------------------------------ | ------------------------ | --------------------------------------: | -----------------: | ------------------------------------ | ---------------------------------------------------------------- | ----- | - | -- | ------------- | ------------------------------------------------- | ----------------------------- |\n| **BMSSP (this)** + binary heap | non-negative             | $\\mathcal{O}((m+n)\\log n)$ | $\\Theta(n+m)$ | Exact within bound; simple; great when $f \\ll 1$. | If $f\\to1$, same as Dijkstra. |\n| Dijkstra (binary heap)         | non-negative             |              $\\mathcal{O}((m+n)\\log n)$ |      $\\Theta(n+m)$ | Baseline, robust.                    | Slower on integer weights; no early stop unless you add a bound. |       |   |    |               |                                                   |                               |\n| Dijkstra (Fibonacci)           | non-negative             |     $\\mathcal{O}(m + n\\log n)$ (theory) |      big constants | Better asymptotics.                  | Not worth it in practice.                                        |       |   |    |               |                                                   |                               |\n| **Dial / bucket queues**       | integer weights $0..C$   |                   $\\mathcal{O}(m + nC)$ |    $\\Theta(n+m+C)$ | Blazing if $C$ small; predictable.   | Explodes with large $C$ or big $B$ (buckets).                    |       |   |    |               |                                                   |                               |\n| Radix / 0-1 BFS                | small integer            |                             near-linear |      $\\Theta(n+m)$ | Ideal for tiny ranges (0-1 BFS).     | Narrow use-case.                                                 |       |   |    |               |                                                   |                               |\n| Δ-stepping (parallel)          | non-negative             | $\\approx$ near-linear per level on PRAM | buckets + frontier | Parallel-friendly; fast in practice. | Needs tuning (δ); irregular work.                                |       |   |    |               |                                                   |                               |\n| Bellman-Ford                   | any                      |                       $\\mathcal{O}(nm)$ |        $\\Theta(n)$ | Negative edges (no cycles).          | Too slow; not comparable for your case.                          |       |   |    |               |                                                   |                               |\n| A\\*                            | non-negative + heuristic |  like Dijkstra on set actually explored |      like Dijkstra | If heuristic is good, dominates.     | Needs problem-specific heuristics.                               |       |   |    |               |                                                   |                               |\n\n**Bottom line:** If you need exact distances **up to a bound** and you’re not on tiny integer weights, BMSSP (bounded Dijkstra) is the simplest, most dependable hammer. If weights are small integers, buckets win. If you’re on many-core/GPU, consider Δ-stepping or GPU SSSP.\n\n---\n\n## Charts (model-based trends)\n\nThese plots use simple cost models (they **are not** runtime measurements). They illustrate how the math scales with explored fraction $f$, bound $B$, and weight range. Use your own `cargo bench` to pin real numbers.\n\n1. **Relative time vs explored fraction**\n   Bounded Dijkstra cost shrinks roughly like $f \\log (fn)$ vs full Dijkstra at $f=1$.\n\n![Relative time vs fraction](sandbox:/mnt/data/bmssp_rel_time_vs_fraction.png)\n\n2. **Bounded Dijkstra vs Dial (integer weights)**\n   When the weight range $C$ is small, Dial can beat binary heaps; as $B$ grows or $C$ is large, buckets become a tax.\n\n![Relative time vs bound](sandbox:/mnt/data/bmssp_rel_time_vs_bound.png)\n\n3. **Extra memory (excluding adjacency)**\n   All Dijkstra variants pay $O(n)$. Bucketed queues add $O(B)$ (or $O(C)$) worth of buckets.\n\n![Memory overhead](sandbox:/mnt/data/bmssp_memory_overhead.png)\n\n\u003e Want empirical plots? Run the provided Rust bench across varying $B$, $k=|S|$, $n,m$, and (optionally) a bucketed queue variant. I can script `cargo bench` + CSV + gnuplot for you.\n\n### Crystal implementation\n\nCrystal port lives in `impls/crystal` and follows the same CLI and JSON contract.\n\nBuild locally (if Crystal is installed):\n\n```bash\ncd impls/crystal\nshards build --release\n./bin/bmssp_cr --graph grid --rows 20 --cols 20 --k 8 --B 50 --seed 1 --trials 2 --json\n```\n\nThe bench runner will auto-detect Crystal (`crystal` + `shards` in PATH) and include its results.\n\n---\n\n## Implementation notes that actually matter\n\n* Keep `dist` as `u64`. Use `saturating_add` to avoid overflow during relaxations.\n* Don’t attempt “decrease-key”; just push duplicates and skip stale pops. It’s faster with Rust’s `BinaryHeap`.\n* Track `B'` while relaxing edges that would cross the bound—this is your next-phase threshold.\n* Multi-source is just seeding many `(node, offset)` entries. If you repeat with larger $B$, reuse `dist` as a warm-start and continue; it preserves optimality because labels are monotone.\n* Memory: adjacency dominates. The rest is a few `Vec`s of size $n$ and the heap/frontier.\n\n---\n\n## Complexity re-stated (so you don’t scroll back)\n\n* **Time:** $\\boxed{ \\mathcal{O}\\big((|E(U)|+|U|)\\log |U|\\big) }$\n  Worst case: $\\mathcal{O}((m+n)\\log n)$.\n\n* **Space:** $\\boxed{ \\Theta(n+m) }$ total; working state $\\Theta(n)$ + heap $O(|U|)$.\n\nIf you want byte-accurate numbers, the Rust crate includes a `memory_estimate_bytes()` helper; for real allocations, measure with `heaptrack`/`massif` or Linux cgroups.\n\n---\n\n## When to prefer other queues\n\n* **Small integer weights** (e.g., 0–255): Dial/radix will beat binary heaps. BMSSP still applies—just stop at $B$—but use buckets.\n* **Parallel hardware**: Δ-stepping buckets **plus** bound $B$ works well; you get level-synchronous waves clipped at $B$.\n* **Heuristic-rich domains**: A\\* with an admissible heuristic can explore **far less than $U$** for the same $B$.\n\n---\n\n## How to produce *real* comparison charts (bench recipe)\n\n* Use the Rust crate I gave you (`cargo bench -p bmssp`) and vary:\n\n  * $n,m$ (grid, ER random, power-law)\n  * number of sources $k$\n  * bound $B$\n* Implement a second queue:\n\n  * Dial (if your weights are bounded ints)\n  * or a bucketed Δ-stepping variant (single-thread first)\n* Capture: push counts, relax counts, popped vertices, wall-clock (release), and peak RSS.\n* Plot **time vs |U|**, **time vs B**, **pushes per edge**; the trends will match the charts above, with real constants.\n\n---\n\n## TL;DR\n\n* If $B$ is small relative to typical distances, **BMSSP saves you real work**, exactly as the $f\\log(fn)$ model predicts.\n* If weights are tiny integers, **use buckets** (Dial) and still bound by $B$.\n* Otherwise, bounded Dijkstra with a binary heap is the **clean default**—simple, exact, and fast enough.\n\nIf you want this rewritten around the **recursive/pivoted BMSSP** from your screenshot (the one with `FindPivots`, batching, etc.), send the full section of the paper and I’ll extend the Rust crate to that variant and give you *measured* charts next.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopensvm%2Fbmssp-benchmark-game","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fopensvm%2Fbmssp-benchmark-game","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fopensvm%2Fbmssp-benchmark-game/lists"}