bmssp 1.0.1 β†’ 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,8 +17,8 @@ first to beat Dijkstra's O(m + nΒ·log n) bound on sparse directed graphs.
17
17
 
18
18
  **The algorithm is functional end-to-end as of `1.0.0`** πŸŽ‰ β€” `calculateShortestPaths()`
19
19
  computes distances via the paper's method (FindPivots β†’ block list β†’ recursive BMSSP with
20
- the bounded base case), validated node-by-node against a Dijkstra oracle, including on a
21
- real 2-million-node road network. The project was built piece by piece against the paper,
20
+ the bounded base case), validated node-by-node against a Dijkstra oracle on thousands of
21
+ seeded graphs β€” up to 2 million nodes. The project was built piece by piece against the paper,
22
22
  with every building block shipped, tested, and released individually:
23
23
 
24
24
  | Building block (paper) | Where | Status |
@@ -30,18 +30,34 @@ with every building block shipped, tested, and released individually:
30
30
  | `BaseCase(B, S)` β€” Algorithm 2, bounded mini-Dijkstra ([#40](https://github.com/Sirivasv/bmssp-js/issues/40)) | `src/baseCase.mjs` | βœ… done |
31
31
  | `FindPivots(B, S)` β€” Algorithm 1, frontier shrinking ([#44](https://github.com/Sirivasv/bmssp-js/issues/44)) | `src/findPivots.mjs` | βœ… done |
32
32
  | `BMSSP(l, B, S)` β€” Algorithm 3, the main recursion ([#43](https://github.com/Sirivasv/bmssp-js/issues/43)) | `src/bmssp.mjs` | βœ… done β€” **1.0.0** |
33
+ | Deterministic tie-breaking β€” Assumption 2.1 realized ([#163](https://github.com/Sirivasv/bmssp-js/issues/163)) | `src/tieBreak.mjs` | βœ… done |
34
+ | Shortest-path reconstruction ([#169](https://github.com/Sirivasv/bmssp-js/issues/169)) | `BMSSP.reconstructPath()` | βœ… done |
35
+ | Constructor input validation ([#165](https://github.com/Sirivasv/bmssp-js/issues/165)) | `BMSSP` constructor | βœ… done |
36
+ | Opt-in constant-degree transform, in/out-degree ≀ 2 ([#164](https://github.com/Sirivasv/bmssp-js/issues/164)) | `constantDegreeTransform()` | βœ… done |
37
+ | BMSSP-vs-Dijkstra head-to-head in the benchmark harness ([#170](https://github.com/Sirivasv/bmssp-js/issues/170)) | `npm run bench` / `npm run bench:counts` | βœ… done |
38
+ | Performance-cliff investigation; quadratic `BatchPrepend` fixed ([#182](https://github.com/Sirivasv/bmssp-js/issues/182)) | `src/blockList.mjs` | βœ… done β€” **1.1.1** |
33
39
 
34
40
  > **Honest note:** the paper's win is asymptotic, and this repo optimizes for correctness
35
41
  > and readability, not raw speed. Measured head-to-head (algorithm time only, graph
36
- > loading excluded β€” see [benchmarks/HEAD-TO-HEAD.md](benchmarks/HEAD-TO-HEAD.md)):
42
+ > loading excluded β€” rerun it yourself with `npm run bench`; methodology and the deep
43
+ > 1.0.0 record in [benchmarks/HEAD-TO-HEAD.md](benchmarks/HEAD-TO-HEAD.md)):
37
44
  > Dijkstra still wins on wall-clock at every practical size, though the gap narrows as
38
45
  > sparse graphs grow (2.5Γ— at 50k nodes β†’ 1.57Γ— at 2M). But in the paper's own metric β€”
39
46
  > **comparisons between path lengths** β€” BMSSP does **fewer comparisons than Dijkstra
40
- > from about n = 1M on sparse graphs** (0.91Γ— at n = 2M), and the advantage grows with
41
- > size: the "sorting barrier" is measurably broken; what remains is JS constant factors.
47
+ > from about n = 1M on sparse graphs** (`npm run bench:counts` measures the ratio falling
48
+ > 1.20Γ— at 50k β†’ 1.03Γ— at 200k β†’ 0.98Γ— at 1M; 0.91Γ— at 2M in the record), and the
49
+ > advantage grows with size: the "sorting barrier" is measurably broken; what remains is
50
+ > JS constant factors. The two performance cliffs found in the 1.0.0 measurements were
51
+ > run down in [#182](https://github.com/Sirivasv/bmssp-js/issues/182): the star-graph
52
+ > blowup was a quadratic in `BatchPrepend`'s bookkeeping β€” fixed in `1.1.1` (500k-node
53
+ > star: 61 s β†’ ~3 s) β€” and the recursion-level step is an inherent, measured ~+24% per
54
+ > extra level (see the addendum in
55
+ > [benchmarks/HEAD-TO-HEAD.md](benchmarks/HEAD-TO-HEAD.md)).
42
56
  > Where inputs violate the paper's distinct-path-lengths assumption (e.g. zero-weight
43
- > edges), documented tie guards keep the results correct
44
- > ([#163](https://github.com/Sirivasv/bmssp-js/issues/163) tracks a principled tie-break).
57
+ > edges), a principled deterministic tie-break
58
+ > ([#163](https://github.com/Sirivasv/bmssp-js/issues/163)) realizes that assumption in
59
+ > code: paths are ranked by composite `(length, hops, id)` keys, so distances, predecessor
60
+ > pointers and completed sets are identical no matter how the edge list is ordered.
45
61
 
46
62
  ## How it works (in two ideas)
47
63
 
@@ -68,7 +84,8 @@ npm install bmssp
68
84
  ## Usage
69
85
 
70
86
  The package is ESM-only (`.mjs`). Graphs are arrays of `[from, to, weight]` edges with
71
- numeric node IDs and non-negative weights:
87
+ finite numeric node IDs and finite, non-negative weights. The constructor rejects malformed
88
+ edges with an error that identifies their array index; an empty edge array remains valid:
72
89
 
73
90
  ```javascript
74
91
  import { BMSSP } from "bmssp";
@@ -81,10 +98,41 @@ const graph = new BMSSP([
81
98
 
82
99
  graph.calculateShortestPaths(0);
83
100
  console.log(graph.shortestPaths); // Map(3) { 0 => 0, 1 => 50, 2 => 25 }
101
+ console.log(graph.reconstructPath(1)); // [0, 1]
84
102
  ```
85
103
 
104
+ `reconstructPath(target)` uses the latest `calculateShortestPaths()` run. It returns `[]`
105
+ when the target is unreachable (or before any run) and throws for a node outside the graph.
106
+
86
107
  A reference `dijkstra` implementation is also exported. See the `examples/` directory for
87
- more.
108
+ more, or the [public API reference](https://sirivasv.github.io/bmssp-js/) for the complete
109
+ documented surface.
110
+
111
+ ### Optional: constant-degree transform
112
+
113
+ The paper's bound assumes every vertex has in-degree and out-degree ≀ 2. That preprocessing
114
+ is **not** required for correctness here β€” BMSSP is validated on arbitrary graphs β€” but it is
115
+ available opt-in via `constantDegreeTransform`, which rewrites any graph into that shape by
116
+ splitting each vertex into a zero-weight cycle of "port" copies. The rewrite is
117
+ distance-preserving, so you run on the transformed graph and fold the result back onto your
118
+ original node IDs:
119
+
120
+ ```javascript
121
+ import { BMSSP, constantDegreeTransform } from "bmssp";
122
+
123
+ const t = constantDegreeTransform([
124
+ [0, 1, 50],
125
+ [0, 2, 25],
126
+ [1, 2, 75],
127
+ ]);
128
+
129
+ const g = new BMSSP(t.edges); // every node now has in/out-degree ≀ 2
130
+ g.calculateShortestPaths(t.sourceCopy(0)); // start from a copy of original node 0
131
+ console.log(t.collapse(g.shortestPaths)); // Map(3) { 0 => 0, 1 => 50, 2 => 25 }
132
+ ```
133
+
134
+ `collapse` maps the transformed graph's distances back to the original node IDs; `sourceCopy`
135
+ picks a valid start copy for an original node.
88
136
 
89
137
  ### Using the Docker image
90
138
 
@@ -107,37 +155,51 @@ Other image versions are on [Docker Hub](https://hub.docker.com/r/sirivasv/bmssp
107
155
 
108
156
  ```bash
109
157
  npm install
110
- npm test # Jest suite, incl. BMSSP-vs-Dijkstra equivalence on a real road network
111
- npm run lint # Prettier + ESLint
112
- npm run bench # seeded micro-benchmarks (see benchmarks/README.md)
158
+ npm test # Jest suite β€” every graph seeded, every failure reproducible (~7 s)
159
+ npm run lint # Prettier + ESLint
160
+ npm run bench # BMSSP-vs-Dijkstra head-to-head per graph shape, outputs verified
161
+ npm run bench:counts # …plus comparison-count tables (the paper's own cost metric)
113
162
  ```
114
163
 
115
- The measured BMSSP-vs-Dijkstra head-to-head β€” wall-clock and comparison counts, with
116
- methodology β€” lives in [benchmarks/HEAD-TO-HEAD.md](benchmarks/HEAD-TO-HEAD.md)
117
- ([#170](https://github.com/Sirivasv/bmssp-js/issues/170) tracks harness integration).
164
+ The benchmark harness runs the measured BMSSP-vs-Dijkstra head-to-head on every
165
+ `npm run bench` ([#170](https://github.com/Sirivasv/bmssp-js/issues/170)); methodology,
166
+ the deep 1.0.0 record (up to n = 4M) and the "when to use which" guidance live in
167
+ [benchmarks/HEAD-TO-HEAD.md](benchmarks/HEAD-TO-HEAD.md) and
168
+ [benchmarks/README.md](benchmarks/README.md).
118
169
 
119
170
  The test suite's core contract: for every node, BMSSP's distances must equal the Dijkstra
120
- oracle's β€” including on `test/roadNet-CA.txt`, a real road network from SNAP. A seeded
121
- property/fuzz suite (`test/fuzz.test.mjs`) hammers that contract across eight graph shapes
122
- (sparse/dense random, grids, chains, stars, DAGs, disconnected forests, multigraphs),
123
- extreme weight regimes (all-zero, mixed zero/huge, exact floats), and direct multi-source
124
- bounded calls checked against per-source oracles. It runs in `npm test` by default; crank
125
- the volume with the `FUZZ_ROUNDS` multiplier (e.g. `FUZZ_ROUNDS=25 npm test -- test/fuzz.test.mjs`
126
- checks several thousand graphs in a few seconds). Failures always report the seed that
127
- produced them.
171
+ oracle's. A seeded property/fuzz suite (`test/fuzz.test.mjs`) hammers that contract across
172
+ eight graph shapes (sparse/dense random, grids, chains, stars, DAGs, disconnected forests,
173
+ multigraphs), extreme weight regimes (all-zero, mixed zero/huge, exact floats), direct
174
+ multi-source bounded calls checked against per-source oracles, and seeded scale runs up to
175
+ 150k nodes. It runs in `npm test` by default; crank the volume with the `FUZZ_ROUNDS`
176
+ multiplier (e.g. `FUZZ_ROUNDS=25 npm test -- test/fuzz.test.mjs` checks several thousand
177
+ graphs in a few seconds), and set `FUZZ_XL=1` for an additional 2-million-node round
178
+ (~30 s). Failures always report the seed that produced them.
128
179
 
129
180
  ## Roadmap
130
181
 
131
182
  | Milestone | Theme | Status |
132
183
  | --- | --- | --- |
133
184
  | [`1.0.0`](https://github.com/Sirivasv/bmssp-js/milestones) | First end-to-end functional BMSSP (issues #40–#45) | βœ… done |
134
- | `1.1.0` | Correctness hardening β€” fuzz tests, edge cases, tie-breaking, input validation | πŸ”¨ current focus |
135
- | `1.2.0` | Performance & ergonomics β€” exact Lemma 3.3 asymptotics, path reconstruction, BMSSP-vs-Dijkstra benchmarks | planned |
185
+ | `1.1.0` | Correctness hardening β€” fuzz tests, edge cases, tie-breaking, input validation, constant-degree transform, API docs | βœ… done |
186
+ | `1.2.0` | Performance & ergonomics β€” exact Lemma 3.3 asymptotics, BMSSP-vs-Dijkstra benchmarks, performance-cliff investigation | πŸ”¨ current focus |
136
187
  | `2.0.0` | API generalization β€” public multi-source/bounded entry point, flexible inputs | planned |
137
188
 
189
+ ## Contributing (humans and AI agents welcome)
190
+
138
191
  Contributions are welcome β€” see [CONTRIBUTING.md](CONTRIBUTING.md) and the
139
192
  [`help wanted` / `good first issue` labels](https://github.com/Sirivasv/bmssp-js/issues).
140
193
 
194
+ This repo is built to be **agent-friendly, model-agnostic**: everything a coding assistant
195
+ needs to contribute is checked in, so you can point **any AI running any model** at the
196
+ project and it can start working **without ever reading the source paper**. Have your agent
197
+ read [`.claude/CLAUDE.md`](.claude/CLAUDE.md) first β€” it lays out the full working lifecycle
198
+ as literal, follow-along checklists β€” backed by a self-contained knowledge base under
199
+ [`.claude/knowledge/`](.claude/knowledge/) (a verified transcription of the paper, the
200
+ current codebase map, the roadmap, and a glossary). Prefer to work by hand? The same
201
+ knowledge base reads as plain documentation.
202
+
141
203
  ## Other implementations on GitHub
142
204
 
143
205
  <https://github.com/search?q=bmssp&type=repositories>
package/docs/index.html CHANGED
@@ -2,8 +2,9 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
- <title>Tsinghua SSSP - BMSSP - Landing Page</title>
5
+ <title>bmssp β€” BMSSP shortest paths for JavaScript</title>
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description" content="JavaScript implementation of BMSSP, the O(m log^(2/3) n) single-source shortest-paths algorithm β€” public API documentation.">
7
8
  <style>
8
9
  body {
9
10
  margin: 0;
@@ -14,35 +15,63 @@
14
15
  display: flex;
15
16
  flex-direction: column;
16
17
  align-items: center;
17
- justify-content: center;
18
18
  }
19
19
  .container {
20
20
  background: #fff;
21
21
  border-radius: 18px;
22
22
  box-shadow: 0 4px 24px rgba(0,0,0,0.08);
23
23
  padding: 2.5rem 2rem;
24
- max-width: 420px;
25
- text-align: center;
24
+ margin: 2rem 1rem;
25
+ max-width: 760px;
26
+ width: calc(100% - 2rem);
27
+ box-sizing: border-box;
26
28
  }
29
+ header { text-align: center; }
27
30
  h1 {
28
31
  font-size: 2.2rem;
29
32
  margin-bottom: 0.5rem;
30
33
  color: #3a0ca3;
31
34
  }
32
- p {
33
- font-size: 1.1rem;
34
- margin-bottom: 1.5rem;
35
+ h2 {
36
+ font-size: 1.4rem;
37
+ color: #3a0ca3;
38
+ border-bottom: 2px solid #e0e7ff;
39
+ padding-bottom: 0.3rem;
40
+ margin-top: 2.2rem;
41
+ }
42
+ h3 {
43
+ font-size: 1.05rem;
44
+ color: #22223b;
45
+ margin-bottom: 0.3rem;
46
+ }
47
+ p, li {
48
+ font-size: 1rem;
49
+ line-height: 1.55;
35
50
  color: #4a4e69;
36
51
  }
52
+ .tagline { font-size: 1.1rem; }
53
+ a { color: #4361ee; }
54
+ code {
55
+ font-family: 'Cascadia Code', Consolas, Menlo, monospace;
56
+ font-size: 0.92em;
57
+ background: #eef1ff;
58
+ border-radius: 4px;
59
+ padding: 0.08em 0.35em;
60
+ }
61
+ pre {
62
+ background: #22223b;
63
+ color: #f0fdfa;
64
+ border-radius: 10px;
65
+ padding: 1rem 1.2rem;
66
+ overflow-x: auto;
67
+ }
68
+ pre code { background: none; color: inherit; padding: 0; }
37
69
  .graphics {
38
- margin: 1.5rem 0;
70
+ margin: 1.2rem 0 0.4rem;
39
71
  display: flex;
40
72
  justify-content: center;
41
73
  }
42
- .node-graph {
43
- width: 120px;
44
- height: 120px;
45
- }
74
+ .node-graph { width: 110px; height: 110px; }
46
75
  .btn {
47
76
  display: inline-block;
48
77
  padding: 0.7em 1.5em;
@@ -54,43 +83,164 @@
54
83
  text-decoration: none;
55
84
  transition: background 0.2s;
56
85
  cursor: pointer;
86
+ margin: 0.3rem 0.35rem;
57
87
  }
58
- .btn:hover {
59
- background: #3a0ca3;
88
+ .btn:hover { background: #3a0ca3; }
89
+ .btn.secondary { background: #4cc9f0; color: #22223b; }
90
+ .btn.secondary:hover { background: #3ab8de; }
91
+ .note {
92
+ background: #f0fdfa;
93
+ border-left: 4px solid #4cc9f0;
94
+ border-radius: 0 8px 8px 0;
95
+ padding: 0.7rem 1rem;
96
+ margin: 1rem 0;
60
97
  }
61
98
  footer {
62
- margin-top: 2rem;
99
+ margin: 0 0 2rem;
63
100
  font-size: 0.95rem;
64
- color: #adb5bd;
101
+ color: #6c757d;
102
+ text-align: center;
65
103
  }
104
+ footer a { color: #6c757d; }
66
105
  </style>
67
106
  </head>
68
107
  <body>
69
108
  <div class="container">
70
- <h1>Tsinghua SSSP - BMSSP JS</h1>
109
+ <header>
110
+ <h1>bmssp</h1>
111
+ <p class="tagline">
112
+ A JavaScript (ESM) implementation of <strong>BMSSP</strong> β€”
113
+ <strong>B</strong>ounded <strong>M</strong>ulti-<strong>S</strong>ource
114
+ <strong>S</strong>hortest <strong>P</strong>aths β€” the deterministic
115
+ O(mΒ·log<sup>2/3</sup> n) single-source shortest-paths algorithm from
116
+ <a href="https://dl.acm.org/doi/10.1145/3717823.3718179" target="_blank" rel="noopener">
117
+ β€œBreaking the Sorting Barrier for Directed Single-Source Shortest Paths”</a>
118
+ (Duan, Mao, Mao, Shu, Yin β€” 2025), the first to beat Dijkstra on sparse directed graphs.
119
+ </p>
120
+ <div class="graphics">
121
+ <!-- SVG: stylized graph with nodes and edges -->
122
+ <svg class="node-graph" viewBox="0 0 120 120" role="img" aria-label="Stylized graph">
123
+ <line x1="60" y1="20" x2="30" y2="60" stroke="#adb5bd" stroke-width="3"/>
124
+ <line x1="60" y1="20" x2="90" y2="60" stroke="#adb5bd" stroke-width="3"/>
125
+ <line x1="30" y1="60" x2="60" y2="100" stroke="#adb5bd" stroke-width="3"/>
126
+ <line x1="90" y1="60" x2="60" y2="100" stroke="#adb5bd" stroke-width="3"/>
127
+ <circle cx="60" cy="20" r="12" fill="#4361ee" />
128
+ <circle cx="30" cy="60" r="10" fill="#4cc9f0" />
129
+ <circle cx="90" cy="60" r="10" fill="#4cc9f0" />
130
+ <circle cx="60" cy="100" r="12" fill="#3a0ca3" />
131
+ </svg>
132
+ </div>
133
+ <p>
134
+ <a class="btn" href="https://github.com/Sirivasv/bmssp-js" target="_blank" rel="noopener">GitHub</a>
135
+ <a class="btn secondary" href="https://www.npmjs.com/package/bmssp" target="_blank" rel="noopener">npm</a>
136
+ <a class="btn secondary" href="https://hub.docker.com/r/sirivasv/bmssp-js" target="_blank" rel="noopener">Docker&nbsp;Hub</a>
137
+ </p>
138
+ </header>
139
+
140
+ <h2>Install</h2>
141
+ <pre><code>npm install bmssp</code></pre>
71
142
  <p>
72
- Implementation of the Single Source Shortest Path (SSSP) algorithm - Bounded Multiple Source Shortest Paths (BMSSP), originally published by Tsinghua University's research.
143
+ The package is ESM-only. Graphs are arrays of <code>[from, to, weight]</code>
144
+ edges with finite numeric node IDs and finite, non-negative weights.
73
145
  </p>
146
+
147
+ <h2>Quick start</h2>
148
+ <pre><code>import { BMSSP } from "bmssp";
149
+
150
+ const graph = new BMSSP([
151
+ [0, 1, 50],
152
+ [1, 2, 75],
153
+ [0, 2, 25],
154
+ ]);
155
+
156
+ graph.calculateShortestPaths(0);
157
+ graph.shortestPaths; // Map(3) { 0 => 0, 1 => 50, 2 => 25 }
158
+ graph.reconstructPath(1); // [0, 1]</code></pre>
159
+
160
+ <h2>Public API</h2>
161
+ <p>The package has exactly three exports: <code>BMSSP</code>, <code>dijkstra</code>,
162
+ and <code>constantDegreeTransform</code>.</p>
163
+
164
+ <h3><code>new BMSSP(graph)</code></h3>
74
165
  <p>
75
- Read the original paper:
76
- <a href="https://dl.acm.org/doi/10.1145/3717823.3718179" target="_blank">
77
- Tsinghua SSSP Paper
78
- </a>
166
+ Validates and stores the edge list. Every entry must be an exact three-element
167
+ <code>[from, to, weight]</code> array; node IDs must be finite numbers and weights
168
+ finite and non-negative. Malformed input throws an <code>Error</code> identifying
169
+ the offending edge index. An empty edge array is valid (the graph then has no
170
+ nodes, so any start node is rejected later).
79
171
  </p>
80
- <div class="graphics">
81
- <!-- SVG: stylized graph with nodes and edges -->
82
- <svg class="node-graph" viewBox="0 0 120 120">
83
- <circle cx="60" cy="20" r="12" fill="#4361ee" />
84
- <circle cx="30" cy="60" r="10" fill="#4cc9f0" />
85
- <circle cx="90" cy="60" r="10" fill="#4cc9f0" />
86
- <circle cx="60" cy="100" r="12" fill="#3a0ca3" />
87
- <line x1="60" y1="20" x2="30" y2="60" stroke="#adb5bd" stroke-width="3"/>
88
- <line x1="60" y1="20" x2="90" y2="60" stroke="#adb5bd" stroke-width="3"/>
89
- <line x1="30" y1="60" x2="60" y2="100" stroke="#adb5bd" stroke-width="3"/>
90
- <line x1="90" y1="60" x2="60" y2="100" stroke="#adb5bd" stroke-width="3"/>
91
- </svg>
172
+
173
+ <h3><code>graph.calculateShortestPaths(source)</code></h3>
174
+ <p>
175
+ Computes shortest distances from <code>source</code> via the paper's method
176
+ (FindPivots β†’ block list β†’ recursive BMSSP with the bounded base case β€” not by
177
+ calling Dijkstra). Throws if <code>source</code> is not a node of the graph.
178
+ Results land in <code>graph.shortestPaths</code>, a
179
+ <code>Map&lt;nodeId, distance&gt;</code> holding a value for every node β€”
180
+ <code>Infinity</code> where unreachable. Distances and predecessor pointers are
181
+ canonical: deterministic regardless of edge-list order, even with ties and
182
+ zero-weight edges. Calling it again with a different source resets state and
183
+ recomputes.
184
+ </p>
185
+
186
+ <h3><code>graph.reconstructPath(target)</code></h3>
187
+ <p>
188
+ Returns the canonical shortest path from the most recent source to
189
+ <code>target</code> as an array of node IDs (<code>[source, …, target]</code>).
190
+ Returns <code>[]</code> when <code>target</code> is unreachable or no run has
191
+ completed yet; throws for a node that is not in the graph.
192
+ </p>
193
+
194
+ <h3><code>dijkstra(graph, nodeIDs, source)</code></h3>
195
+ <p>
196
+ The reference Dijkstra implementation used as the test suite's ground-truth
197
+ oracle, exported for comparison and independent checking. Takes the raw edge
198
+ array, a <code>Set</code> of node IDs, and a source; returns a
199
+ <code>Map&lt;nodeId, distance&gt;</code> (<code>Infinity</code> if unreachable).
200
+ </p>
201
+
202
+ <h3><code>constantDegreeTransform(graph)</code></h3>
203
+ <p>
204
+ Opt-in preprocessing that rewrites any graph so every vertex has in-degree and
205
+ out-degree ≀ 2 (the paper's preliminary assumption) by splitting each vertex into
206
+ a zero-weight cycle of β€œport” copies. Distance-preserving and never required for
207
+ correctness β€” BMSSP is validated on arbitrary graphs. Returns
208
+ <code>{ edges, copiesOf, originalOf, sourceCopy, collapse }</code>:
209
+ </p>
210
+ <pre><code>import { BMSSP, constantDegreeTransform } from "bmssp";
211
+
212
+ const t = constantDegreeTransform([
213
+ [0, 1, 50],
214
+ [0, 2, 25],
215
+ [1, 2, 75],
216
+ ]);
217
+
218
+ const g = new BMSSP(t.edges); // in/out-degree ≀ 2 everywhere
219
+ g.calculateShortestPaths(t.sourceCopy(0)); // start from a copy of node 0
220
+ t.collapse(g.shortestPaths); // Map(3) { 0 => 0, 1 => 50, 2 => 25 }</code></pre>
221
+
222
+ <div class="note">
223
+ Algorithm internals β€” the Lemma 3.3 block list, the indexed min-heap,
224
+ <code>BaseCase</code>, <code>FindPivots</code>, and the tie-break helpers β€” are
225
+ deliberately <em>not</em> exported. They are implementation details, documented
226
+ in-source for contributors.
92
227
  </div>
93
- <a class="btn" href="https://github.com/sirivasv/tsinghua-sssp" target="_blank">View on GitHub</a>
228
+
229
+ <h2>Correctness &amp; performance</h2>
230
+ <p>
231
+ Every release is validated node-by-node against the Dijkstra oracle on thousands
232
+ of seeded graphs (up to 2 million nodes). The paper's win is asymptotic: measured
233
+ head-to-head, Dijkstra still wins wall-clock at practical sizes, but in the
234
+ paper's own metric β€” comparisons between path lengths β€” this implementation does
235
+ fewer comparisons than Dijkstra from about n&nbsp;=&nbsp;1M on sparse graphs. See
236
+ <a href="https://github.com/Sirivasv/bmssp-js/blob/main/benchmarks/HEAD-TO-HEAD.md"
237
+ target="_blank" rel="noopener">benchmarks/HEAD-TO-HEAD.md</a> for the data and
238
+ methodology.
239
+ </p>
94
240
  </div>
241
+ <footer>
242
+ <a href="https://github.com/Sirivasv/bmssp-js" target="_blank" rel="noopener">bmssp-js</a>
243
+ Β· MPL-2.0 Β· Saul Ivan Rivas Vega
244
+ </footer>
95
245
  </body>
96
- </html></svg>
246
+ </html>
package/index.mjs CHANGED
@@ -1,2 +1,41 @@
1
+ // Public API of the `bmssp` package (ESM-only). Exactly three exports:
2
+ // the BMSSP class, the reference `dijkstra`, and the opt-in
3
+ // `constantDegreeTransform`. Algorithm-internal modules (the block list,
4
+ // the indexed heap, BaseCase, FindPivots, the tie-break helpers) are
5
+ // deliberately NOT re-exported β€” they are implementation details of the
6
+ // recursion, not supported public API.
7
+
8
+ /**
9
+ * BMSSP β€” the paper's Algorithm 3 behind a small class API.
10
+ *
11
+ * `new BMSSP(graph)` takes an array of `[from, to, weight]` edges with
12
+ * finite numeric node IDs and finite, non-negative weights (an empty array
13
+ * is valid; malformed edges throw with the offending index).
14
+ * `calculateShortestPaths(source)` computes canonical distances into the
15
+ * `shortestPaths` Map (`Infinity` for unreachable nodes), and
16
+ * `reconstructPath(target)` returns the canonical shortest path as an
17
+ * array of node IDs. Full JSDoc lives on the class in `src/bmssp.mjs`.
18
+ */
1
19
  export { BMSSP } from "./src/bmssp.mjs";
20
+
21
+ /**
22
+ * Reference Dijkstra implementation β€” the oracle the BMSSP test suite is
23
+ * validated against, exported for comparison and independent checking.
24
+ *
25
+ * `dijkstra(graph, nodeIDs, source)` returns a `Map` from node ID to
26
+ * shortest distance (`Infinity` if unreachable). See `src/dijkstra.mjs`.
27
+ */
2
28
  export { dijkstra } from "./src/dijkstra.mjs";
29
+
30
+ /**
31
+ * Opt-in constant-degree transform β€” rewrites any graph so every vertex
32
+ * has in-degree and out-degree <= 2 (the paper's preliminary assumption)
33
+ * by splitting each vertex into a zero-weight cycle of port copies. The
34
+ * rewrite is distance-preserving and never required for correctness.
35
+ *
36
+ * `constantDegreeTransform(graph)` returns
37
+ * `{ edges, copiesOf, originalOf, sourceCopy, collapse }`; run BMSSP on
38
+ * `edges` from `sourceCopy(source)` and `collapse()` the resulting
39
+ * distances back onto original node IDs. See `src/constantDegree.mjs`.
40
+ */
41
+ export { constantDegreeTransform } from "./src/constantDegree.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bmssp",
3
- "version": "1.0.1",
3
+ "version": "1.1.1",
4
4
  "description": "Javascript package implementation of the bmssp algorithm.",
5
5
  "main": "index.mjs",
6
6
  "keywords": [
@@ -35,6 +35,7 @@
35
35
  "scripts": {
36
36
  "test": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" jest --coverage",
37
37
  "bench": "node benchmarks/run.mjs",
38
+ "bench:counts": "node benchmarks/run.mjs --counts",
38
39
  "lint": "npm run prettier && npm run eslint",
39
40
  "format": "npm run prettier:fix && npm run eslint:fix",
40
41
  "eslint": "eslint --max-warnings=0 .",