bmssp 1.1.1 → 2.0.0
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 +113 -22
- package/docs/index.html +82 -15
- package/index.mjs +19 -9
- package/package.json +1 -1
- package/src/baseCase.mjs +41 -33
- package/src/blockList.mjs +133 -68
- package/src/bmssp.mjs +370 -66
- package/src/boundIndex.mjs +243 -0
- package/src/findPivots.mjs +40 -26
- package/src/graph.mjs +174 -0
- package/src/select.mjs +144 -0
- package/src/tieBreak.mjs +111 -29
package/src/tieBreak.mjs
CHANGED
|
@@ -38,9 +38,11 @@
|
|
|
38
38
|
* source is a hop-0 root) and "no predecessor" (which never loses a tie).
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
|
-
// Sentinel predecessor for sources: compares below every real
|
|
42
|
-
// source's own label never loses an equal-(length, hops) comparison.
|
|
43
|
-
|
|
41
|
+
// Sentinel predecessor for sources: compares below every real vertex index,
|
|
42
|
+
// so a source's own label never loses an equal-(length, hops) comparison.
|
|
43
|
+
// Since #205 the engine works on dense vertex indices (>= 0), so -1 is a
|
|
44
|
+
// valid sentinel AND fits the Int32Array holding the predecessors.
|
|
45
|
+
const NO_PRED = -1;
|
|
44
46
|
|
|
45
47
|
// Running count of compareKeys calls — the paper's cost metric ("comparisons
|
|
46
48
|
// between path lengths"). Every BMSSP-side comparison funnels through
|
|
@@ -78,6 +80,26 @@ function compareKeys(a, b) {
|
|
|
78
80
|
return 0;
|
|
79
81
|
}
|
|
80
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Lexicographic comparison of an UNPACKED key (length, hops, id) against a
|
|
85
|
+
* packed one — identical order to compareKeys without materializing the
|
|
86
|
+
* left-hand array (#168: the hot relax/routing loops call this instead of
|
|
87
|
+
* allocating a throwaway key per test). Counts as one comparison.
|
|
88
|
+
* @param {number} length - Left key's length component
|
|
89
|
+
* @param {number} hops - Left key's hops component
|
|
90
|
+
* @param {*} id - Left key's id component
|
|
91
|
+
* @param {[number, number, *]} key - Right key, packed
|
|
92
|
+
* @returns {number} Negative when (length, hops, id) < key, positive when
|
|
93
|
+
* greater, 0 when equal
|
|
94
|
+
*/
|
|
95
|
+
function compareKeyParts(length, hops, id, key) {
|
|
96
|
+
comparisonCount += 1;
|
|
97
|
+
if (length !== key[0]) return length < key[0] ? -1 : 1;
|
|
98
|
+
if (hops !== key[1]) return hops < key[1] ? -1 : 1;
|
|
99
|
+
if (id !== key[2]) return id < key[2] ? -1 : 1;
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
81
103
|
/**
|
|
82
104
|
* Normalize a bound to a composite key. A scalar B becomes the infimum of
|
|
83
105
|
* all keys of length B, preserving the strict "distance < B" contract;
|
|
@@ -90,7 +112,9 @@ function toBound(B) {
|
|
|
90
112
|
}
|
|
91
113
|
|
|
92
114
|
/**
|
|
93
|
-
* Bundle (or create) the tie-break label maps that accompany d
|
|
115
|
+
* Bundle (or create) the tie-break label maps that accompany d̂ at the PUBLIC
|
|
116
|
+
* boundary (the BMSSP class mirrors the engine's arrays into these Maps,
|
|
117
|
+
* keyed by original node ids).
|
|
94
118
|
* @param {Map<*, number>} [hops] - Canonical path edge counts
|
|
95
119
|
* @param {Map<*, *>} [preds] - Canonical predecessor of each vertex
|
|
96
120
|
* @returns {{ hops: Map<*, number>, preds: Map<*, *> }}
|
|
@@ -99,6 +123,31 @@ function makeTies(hops = new Map(), preds = new Map()) {
|
|
|
99
123
|
return { hops, preds };
|
|
100
124
|
}
|
|
101
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Allocate the engine's label state for n vertices (#205): d̂, hops and
|
|
128
|
+
* canonical predecessors as typed arrays over dense vertex indices.
|
|
129
|
+
* Unlabeled vertices read as distance Infinity, hop 0, no predecessor —
|
|
130
|
+
* the same defaults the pre-#205 Maps gave via `?? Infinity` / `?? 0`.
|
|
131
|
+
* @param {number} n - Vertex count
|
|
132
|
+
* @returns {{ dist: Float64Array, hops: Uint32Array, preds: Int32Array }}
|
|
133
|
+
*/
|
|
134
|
+
function makeLabels(n) {
|
|
135
|
+
const dist = new Float64Array(n).fill(Infinity);
|
|
136
|
+
const hops = new Uint32Array(n);
|
|
137
|
+
const preds = new Int32Array(n).fill(NO_PRED);
|
|
138
|
+
return { dist, hops, preds };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The frontier-ordering key of vertex index v under the engine labels.
|
|
143
|
+
* @param {number} v - Dense vertex index
|
|
144
|
+
* @param {{ dist: Float64Array, hops: Uint32Array }} labels
|
|
145
|
+
* @returns {[number, number, number]} [dist[v], hops[v], v]
|
|
146
|
+
*/
|
|
147
|
+
function labelKey(v, labels) {
|
|
148
|
+
return [labels.dist[v], labels.hops[v], v];
|
|
149
|
+
}
|
|
150
|
+
|
|
102
151
|
/**
|
|
103
152
|
* The frontier-ordering key of a vertex under its current labels.
|
|
104
153
|
* @param {*} v - Vertex id
|
|
@@ -130,45 +179,78 @@ function orderKey(v, dHat, ties) {
|
|
|
130
179
|
* tied alternative parents; callers filter already-completed vertices to
|
|
131
180
|
* keep the re-enqueue finite, exactly like the paper.
|
|
132
181
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
182
|
+
* Allocation-free since #168: the result is one of the three RELAX_* codes,
|
|
183
|
+
* and a caller that needs v's (possibly updated) order key materializes it
|
|
184
|
+
* with orderKey(v, dHat, ties) — only on the rare paths that enqueue v,
|
|
185
|
+
* instead of on every attempt.
|
|
186
|
+
*
|
|
187
|
+
* Since #205 the labels live in typed arrays over dense vertex indices
|
|
188
|
+
* (see makeLabels); u and v are indices, and index order equals original-id
|
|
189
|
+
* order (the BMSSP class assigns indices by sorted id), so the canonical
|
|
190
|
+
* choices are identical to the pre-#205 Map engine's.
|
|
191
|
+
*
|
|
192
|
+
* @param {number} u - Edge tail index (its labels are read)
|
|
193
|
+
* @param {number} v - Edge head index (its labels may be updated)
|
|
135
194
|
* @param {number} weight - Edge weight, >= 0
|
|
136
|
-
* @param {
|
|
137
|
-
*
|
|
195
|
+
* @param {{ dist: Float64Array, hops: Uint32Array, preds: Int32Array }} labels
|
|
196
|
+
* - Engine label state, updated in place
|
|
138
197
|
* @param {[number, number, *]} [bound] - Optional gate: skip (no d̂ update)
|
|
139
198
|
* unless the resulting order key would be strictly below this bound
|
|
140
|
-
* @returns {
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
199
|
+
* @returns {number} RELAX_IMPROVED when the labels were updated,
|
|
200
|
+
* RELAX_EQUAL when the candidate exactly matches v's canonical label
|
|
201
|
+
* (the re-enqueue signal), RELAX_LOST when the candidate loses or the
|
|
202
|
+
* bound gates it (labels untouched)
|
|
144
203
|
*/
|
|
145
|
-
function relaxEdge(u, v, weight,
|
|
146
|
-
const length =
|
|
147
|
-
const hopCount =
|
|
148
|
-
if (bound !== undefined &&
|
|
149
|
-
return
|
|
204
|
+
function relaxEdge(u, v, weight, labels, bound) {
|
|
205
|
+
const length = labels.dist[u] + weight;
|
|
206
|
+
const hopCount = labels.hops[u] + 1;
|
|
207
|
+
if (bound !== undefined && compareKeyParts(length, hopCount, v, bound) >= 0) {
|
|
208
|
+
return RELAX_LOST;
|
|
209
|
+
}
|
|
210
|
+
// Candidate path key [length, hopCount, u] vs. v's current path key
|
|
211
|
+
// [dist[v], hops[v], preds[v]] — compareKeys inlined on the unpacked
|
|
212
|
+
// components (one counted comparison, no arrays)
|
|
213
|
+
comparisonCount += 1;
|
|
214
|
+
let cmp;
|
|
215
|
+
const currentLength = labels.dist[v];
|
|
216
|
+
if (length !== currentLength) {
|
|
217
|
+
cmp = length < currentLength ? -1 : 1;
|
|
218
|
+
} else {
|
|
219
|
+
const currentHops = labels.hops[v];
|
|
220
|
+
if (hopCount !== currentHops) {
|
|
221
|
+
cmp = hopCount < currentHops ? -1 : 1;
|
|
222
|
+
} else {
|
|
223
|
+
const currentPred = labels.preds[v];
|
|
224
|
+
cmp = u !== currentPred ? (u < currentPred ? -1 : 1) : 0;
|
|
225
|
+
}
|
|
150
226
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
];
|
|
156
|
-
|
|
157
|
-
if (cmp > 0) return null;
|
|
158
|
-
if (cmp === 0) return { key: [length, hopCount, v], improved: false };
|
|
159
|
-
dHat.set(v, length);
|
|
160
|
-
ties.hops.set(v, hopCount);
|
|
161
|
-
ties.preds.set(v, u);
|
|
162
|
-
return { key: [length, hopCount, v], improved: true };
|
|
227
|
+
if (cmp > 0) return RELAX_LOST;
|
|
228
|
+
if (cmp === 0) return RELAX_EQUAL;
|
|
229
|
+
labels.dist[v] = length;
|
|
230
|
+
labels.hops[v] = hopCount;
|
|
231
|
+
labels.preds[v] = u;
|
|
232
|
+
return RELAX_IMPROVED;
|
|
163
233
|
}
|
|
164
234
|
|
|
235
|
+
// relaxEdge result codes (#168): distinct, and only RELAX_LOST is falsy-like
|
|
236
|
+
// in comparisons — callers must compare against the constants, not truthiness
|
|
237
|
+
const RELAX_LOST = -1;
|
|
238
|
+
const RELAX_EQUAL = 0;
|
|
239
|
+
const RELAX_IMPROVED = 1;
|
|
240
|
+
|
|
165
241
|
export {
|
|
166
242
|
compareKeys,
|
|
243
|
+
compareKeyParts,
|
|
167
244
|
toBound,
|
|
168
245
|
makeTies,
|
|
246
|
+
makeLabels,
|
|
169
247
|
orderKey,
|
|
248
|
+
labelKey,
|
|
170
249
|
relaxEdge,
|
|
171
250
|
NO_PRED,
|
|
251
|
+
RELAX_LOST,
|
|
252
|
+
RELAX_EQUAL,
|
|
253
|
+
RELAX_IMPROVED,
|
|
172
254
|
resetComparisonCount,
|
|
173
255
|
getComparisonCount,
|
|
174
256
|
};
|