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 +87 -25
- package/docs/index.html +186 -36
- package/index.mjs +39 -0
- package/package.json +2 -1
- package/src/baseCase.mjs +56 -32
- package/src/blockList.mjs +35 -21
- package/src/bmssp.mjs +157 -84
- package/src/constantDegree.mjs +136 -0
- package/src/findPivots.mjs +39 -30
- package/src/heap.mjs +25 -11
- package/src/tieBreak.mjs +174 -0
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
|
|
21
|
-
|
|
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 β
|
|
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** (
|
|
41
|
-
>
|
|
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),
|
|
44
|
-
> ([#163](https://github.com/Sirivasv/bmssp-js/issues/163)
|
|
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
|
|
111
|
-
npm run lint
|
|
112
|
-
npm run bench
|
|
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
|
|
116
|
-
|
|
117
|
-
(
|
|
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
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
|
135
|
-
| `1.2.0` | Performance & ergonomics β exact Lemma 3.3 asymptotics,
|
|
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>
|
|
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
|
-
|
|
25
|
-
|
|
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
|
-
|
|
33
|
-
font-size: 1.
|
|
34
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
99
|
+
margin: 0 0 2rem;
|
|
63
100
|
font-size: 0.95rem;
|
|
64
|
-
color: #
|
|
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
|
-
<
|
|
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 Hub</a>
|
|
137
|
+
</p>
|
|
138
|
+
</header>
|
|
139
|
+
|
|
140
|
+
<h2>Install</h2>
|
|
141
|
+
<pre><code>npm install bmssp</code></pre>
|
|
71
142
|
<p>
|
|
72
|
-
|
|
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
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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<nodeId, distance></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<nodeId, distance></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
|
-
|
|
228
|
+
|
|
229
|
+
<h2>Correctness & 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 = 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
|
|
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.
|
|
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 .",
|