graphwise 1.7.0 → 1.8.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.
Files changed (130) hide show
  1. package/README.md +81 -30
  2. package/dist/adjacency-map-B6wPtmaq.cjs +234 -0
  3. package/dist/adjacency-map-B6wPtmaq.cjs.map +1 -0
  4. package/dist/adjacency-map-D-Ul7V1r.js +229 -0
  5. package/dist/adjacency-map-D-Ul7V1r.js.map +1 -0
  6. package/dist/async/index.cjs +16 -0
  7. package/dist/async/index.js +3 -0
  8. package/dist/expansion/dfs-priority.d.ts +11 -0
  9. package/dist/expansion/dfs-priority.d.ts.map +1 -1
  10. package/dist/expansion/dome.d.ts +20 -0
  11. package/dist/expansion/dome.d.ts.map +1 -1
  12. package/dist/expansion/edge.d.ts +18 -0
  13. package/dist/expansion/edge.d.ts.map +1 -1
  14. package/dist/expansion/flux.d.ts +16 -0
  15. package/dist/expansion/flux.d.ts.map +1 -1
  16. package/dist/expansion/frontier-balanced.d.ts +11 -0
  17. package/dist/expansion/frontier-balanced.d.ts.map +1 -1
  18. package/dist/expansion/fuse.d.ts +16 -0
  19. package/dist/expansion/fuse.d.ts.map +1 -1
  20. package/dist/expansion/hae.d.ts +16 -0
  21. package/dist/expansion/hae.d.ts.map +1 -1
  22. package/dist/expansion/index.cjs +43 -0
  23. package/dist/expansion/index.js +2 -0
  24. package/dist/expansion/lace.d.ts +16 -0
  25. package/dist/expansion/lace.d.ts.map +1 -1
  26. package/dist/expansion/maze.d.ts +17 -0
  27. package/dist/expansion/maze.d.ts.map +1 -1
  28. package/dist/expansion/pipe.d.ts +16 -0
  29. package/dist/expansion/pipe.d.ts.map +1 -1
  30. package/dist/expansion/random-priority.d.ts +18 -0
  31. package/dist/expansion/random-priority.d.ts.map +1 -1
  32. package/dist/expansion/reach.d.ts +17 -0
  33. package/dist/expansion/reach.d.ts.map +1 -1
  34. package/dist/expansion/sage.d.ts +15 -0
  35. package/dist/expansion/sage.d.ts.map +1 -1
  36. package/dist/expansion/sift.d.ts +16 -0
  37. package/dist/expansion/sift.d.ts.map +1 -1
  38. package/dist/expansion/standard-bfs.d.ts +11 -0
  39. package/dist/expansion/standard-bfs.d.ts.map +1 -1
  40. package/dist/expansion/tide.d.ts +16 -0
  41. package/dist/expansion/tide.d.ts.map +1 -1
  42. package/dist/expansion/warp.d.ts +16 -0
  43. package/dist/expansion/warp.d.ts.map +1 -1
  44. package/dist/expansion-FkmEYlrQ.cjs +1949 -0
  45. package/dist/expansion-FkmEYlrQ.cjs.map +1 -0
  46. package/dist/expansion-sldRognt.js +1704 -0
  47. package/dist/expansion-sldRognt.js.map +1 -0
  48. package/dist/extraction/index.cjs +630 -0
  49. package/dist/extraction/index.cjs.map +1 -0
  50. package/dist/extraction/index.js +621 -0
  51. package/dist/extraction/index.js.map +1 -0
  52. package/dist/graph/index.cjs +2 -229
  53. package/dist/graph/index.js +1 -228
  54. package/dist/index/index.cjs +131 -3406
  55. package/dist/index/index.js +14 -3334
  56. package/dist/index.d.ts +1 -0
  57. package/dist/index.d.ts.map +1 -1
  58. package/dist/jaccard-Bmd1IEFO.cjs +50 -0
  59. package/dist/jaccard-Bmd1IEFO.cjs.map +1 -0
  60. package/dist/jaccard-Yddrtt5D.js +39 -0
  61. package/dist/jaccard-Yddrtt5D.js.map +1 -0
  62. package/dist/{kmeans-BIgSyGKu.cjs → kmeans-D3yX5QFs.cjs} +1 -1
  63. package/dist/{kmeans-BIgSyGKu.cjs.map → kmeans-D3yX5QFs.cjs.map} +1 -1
  64. package/dist/{kmeans-87ExSUNZ.js → kmeans-DVCe61Me.js} +1 -1
  65. package/dist/{kmeans-87ExSUNZ.js.map → kmeans-DVCe61Me.js.map} +1 -1
  66. package/dist/ops-4nmI-pwk.cjs +277 -0
  67. package/dist/ops-4nmI-pwk.cjs.map +1 -0
  68. package/dist/ops-Zsu4ecEG.js +212 -0
  69. package/dist/ops-Zsu4ecEG.js.map +1 -0
  70. package/dist/priority-queue-ChVLoG6T.cjs +148 -0
  71. package/dist/priority-queue-ChVLoG6T.cjs.map +1 -0
  72. package/dist/priority-queue-DqCuFTR8.js +143 -0
  73. package/dist/priority-queue-DqCuFTR8.js.map +1 -0
  74. package/dist/ranking/index.cjs +43 -0
  75. package/dist/ranking/index.js +4 -0
  76. package/dist/ranking/mi/adamic-adar.d.ts +8 -0
  77. package/dist/ranking/mi/adamic-adar.d.ts.map +1 -1
  78. package/dist/ranking/mi/adaptive.d.ts +8 -0
  79. package/dist/ranking/mi/adaptive.d.ts.map +1 -1
  80. package/dist/ranking/mi/cosine.d.ts +7 -0
  81. package/dist/ranking/mi/cosine.d.ts.map +1 -1
  82. package/dist/ranking/mi/etch.d.ts +8 -0
  83. package/dist/ranking/mi/etch.d.ts.map +1 -1
  84. package/dist/ranking/mi/hub-promoted.d.ts +7 -0
  85. package/dist/ranking/mi/hub-promoted.d.ts.map +1 -1
  86. package/dist/ranking/mi/index.cjs +581 -0
  87. package/dist/ranking/mi/index.cjs.map +1 -0
  88. package/dist/ranking/mi/index.js +555 -0
  89. package/dist/ranking/mi/index.js.map +1 -0
  90. package/dist/ranking/mi/jaccard.d.ts +7 -0
  91. package/dist/ranking/mi/jaccard.d.ts.map +1 -1
  92. package/dist/ranking/mi/notch.d.ts +8 -0
  93. package/dist/ranking/mi/notch.d.ts.map +1 -1
  94. package/dist/ranking/mi/overlap-coefficient.d.ts +7 -0
  95. package/dist/ranking/mi/overlap-coefficient.d.ts.map +1 -1
  96. package/dist/ranking/mi/resource-allocation.d.ts +8 -0
  97. package/dist/ranking/mi/resource-allocation.d.ts.map +1 -1
  98. package/dist/ranking/mi/scale.d.ts +7 -0
  99. package/dist/ranking/mi/scale.d.ts.map +1 -1
  100. package/dist/ranking/mi/skew.d.ts +7 -0
  101. package/dist/ranking/mi/skew.d.ts.map +1 -1
  102. package/dist/ranking/mi/sorensen.d.ts +7 -0
  103. package/dist/ranking/mi/sorensen.d.ts.map +1 -1
  104. package/dist/ranking/mi/span.d.ts +8 -0
  105. package/dist/ranking/mi/span.d.ts.map +1 -1
  106. package/dist/ranking/mi/types.d.ts +12 -0
  107. package/dist/ranking/mi/types.d.ts.map +1 -1
  108. package/dist/ranking/parse.d.ts +24 -1
  109. package/dist/ranking/parse.d.ts.map +1 -1
  110. package/dist/ranking-mUm9rV-C.js +1016 -0
  111. package/dist/ranking-mUm9rV-C.js.map +1 -0
  112. package/dist/ranking-riRrEVAR.cjs +1093 -0
  113. package/dist/ranking-riRrEVAR.cjs.map +1 -0
  114. package/dist/seeds/index.cjs +1 -1
  115. package/dist/seeds/index.js +1 -1
  116. package/dist/structures/index.cjs +2 -143
  117. package/dist/structures/index.js +1 -142
  118. package/dist/utils/index.cjs +1 -1
  119. package/dist/utils/index.js +1 -1
  120. package/dist/utils-CcIrKAEb.js +22 -0
  121. package/dist/utils-CcIrKAEb.js.map +1 -0
  122. package/dist/utils-CpyzmzIF.cjs +33 -0
  123. package/dist/utils-CpyzmzIF.cjs.map +1 -0
  124. package/package.json +6 -1
  125. package/dist/graph/index.cjs.map +0 -1
  126. package/dist/graph/index.js.map +0 -1
  127. package/dist/index/index.cjs.map +0 -1
  128. package/dist/index/index.js.map +0 -1
  129. package/dist/structures/index.cjs.map +0 -1
  130. package/dist/structures/index.js.map +0 -1
@@ -0,0 +1,1093 @@
1
+ const require_jaccard = require("./jaccard-Bmd1IEFO.cjs");
2
+ //#region src/ranking/parse.ts
3
+ /**
4
+ * Rank paths using PARSE (Path-Aware Ranking via Salience Estimation).
5
+ *
6
+ * Computes geometric mean of edge MI scores for each path,
7
+ * then sorts by salience (highest first).
8
+ *
9
+ * @param graph - Source graph
10
+ * @param paths - Paths to rank
11
+ * @param config - Configuration options
12
+ * @returns Ranked paths with statistics
13
+ */
14
+ function parse(graph, paths, config) {
15
+ const startTime = performance.now();
16
+ const { mi = require_jaccard.jaccard, epsilon = 1e-10 } = config ?? {};
17
+ const rankedPaths = [];
18
+ for (const path of paths) {
19
+ const salience = computePathSalience(graph, path, mi, epsilon);
20
+ rankedPaths.push({
21
+ ...path,
22
+ salience
23
+ });
24
+ }
25
+ rankedPaths.sort((a, b) => b.salience - a.salience);
26
+ const endTime = performance.now();
27
+ const saliences = rankedPaths.map((p) => p.salience);
28
+ const meanSalience = saliences.length > 0 ? saliences.reduce((a, b) => a + b, 0) / saliences.length : 0;
29
+ const sortedSaliences = [...saliences].sort((a, b) => a - b);
30
+ const mid = Math.floor(sortedSaliences.length / 2);
31
+ const medianSalience = sortedSaliences.length > 0 ? sortedSaliences.length % 2 !== 0 ? sortedSaliences[mid] ?? 0 : ((sortedSaliences[mid - 1] ?? 0) + (sortedSaliences[mid] ?? 0)) / 2 : 0;
32
+ const maxSalience = sortedSaliences.length > 0 ? sortedSaliences[sortedSaliences.length - 1] ?? 0 : 0;
33
+ const minSalience = sortedSaliences.length > 0 ? sortedSaliences[0] ?? 0 : 0;
34
+ return {
35
+ paths: rankedPaths,
36
+ stats: {
37
+ pathsRanked: rankedPaths.length,
38
+ meanSalience,
39
+ medianSalience,
40
+ maxSalience,
41
+ minSalience,
42
+ durationMs: endTime - startTime
43
+ }
44
+ };
45
+ }
46
+ /**
47
+ * Rank paths using async PARSE (Path-Aware Ranking via Salience Estimation).
48
+ *
49
+ * Async variant suitable for use with remote or lazy graph data sources.
50
+ * Computes geometric mean of edge MI scores for each path using Promise.all
51
+ * for parallelism, then sorts by salience (highest first).
52
+ *
53
+ * @param graph - Async source graph
54
+ * @param paths - Paths to rank
55
+ * @param config - Configuration options
56
+ * @returns Ranked paths with statistics
57
+ */
58
+ async function parseAsync(graph, paths, config) {
59
+ const startTime = performance.now();
60
+ const { mi = require_jaccard.jaccardAsync, epsilon = 1e-10 } = config ?? {};
61
+ const rankedPaths = [];
62
+ for (const path of paths) {
63
+ const salience = await computePathSalienceAsync(graph, path, mi, epsilon);
64
+ rankedPaths.push({
65
+ ...path,
66
+ salience
67
+ });
68
+ }
69
+ rankedPaths.sort((a, b) => b.salience - a.salience);
70
+ const endTime = performance.now();
71
+ const saliences = rankedPaths.map((p) => p.salience);
72
+ const meanSalience = saliences.length > 0 ? saliences.reduce((a, b) => a + b, 0) / saliences.length : 0;
73
+ const sortedSaliences = [...saliences].sort((a, b) => a - b);
74
+ const mid = Math.floor(sortedSaliences.length / 2);
75
+ const medianSalience = sortedSaliences.length > 0 ? sortedSaliences.length % 2 !== 0 ? sortedSaliences[mid] ?? 0 : ((sortedSaliences[mid - 1] ?? 0) + (sortedSaliences[mid] ?? 0)) / 2 : 0;
76
+ const maxSalience = sortedSaliences.length > 0 ? sortedSaliences[sortedSaliences.length - 1] ?? 0 : 0;
77
+ const minSalience = sortedSaliences.length > 0 ? sortedSaliences[0] ?? 0 : 0;
78
+ return {
79
+ paths: rankedPaths,
80
+ stats: {
81
+ pathsRanked: rankedPaths.length,
82
+ meanSalience,
83
+ medianSalience,
84
+ maxSalience,
85
+ minSalience,
86
+ durationMs: endTime - startTime
87
+ }
88
+ };
89
+ }
90
+ /**
91
+ * Compute salience for a single path asynchronously.
92
+ *
93
+ * Uses geometric mean of edge MI scores for length-unbiased ranking.
94
+ * Edge MI values are computed in parallel via Promise.all.
95
+ */
96
+ async function computePathSalienceAsync(graph, path, mi, epsilon) {
97
+ const nodes = path.nodes;
98
+ if (nodes.length < 2) return epsilon;
99
+ const edgeMIs = await Promise.all(nodes.slice(0, -1).map((source, i) => {
100
+ const target = nodes[i + 1];
101
+ if (target !== void 0) return mi(graph, source, target);
102
+ return Promise.resolve(epsilon);
103
+ }));
104
+ let productMi = 1;
105
+ let edgeCount = 0;
106
+ for (const edgeMi of edgeMIs) {
107
+ productMi *= Math.max(epsilon, edgeMi);
108
+ edgeCount++;
109
+ }
110
+ if (edgeCount === 0) return epsilon;
111
+ const salience = Math.pow(productMi, 1 / edgeCount);
112
+ return Math.max(epsilon, Math.min(1, salience));
113
+ }
114
+ /**
115
+ * Compute salience for a single path.
116
+ *
117
+ * Uses geometric mean of edge MI scores for length-unbiased ranking.
118
+ */
119
+ function computePathSalience(graph, path, mi, epsilon) {
120
+ const nodes = path.nodes;
121
+ if (nodes.length < 2) return epsilon;
122
+ let productMi = 1;
123
+ let edgeCount = 0;
124
+ for (let i = 0; i < nodes.length - 1; i++) {
125
+ const source = nodes[i];
126
+ const target = nodes[i + 1];
127
+ if (source !== void 0 && target !== void 0) {
128
+ const edgeMi = mi(graph, source, target);
129
+ productMi *= Math.max(epsilon, edgeMi);
130
+ edgeCount++;
131
+ }
132
+ }
133
+ if (edgeCount === 0) return epsilon;
134
+ const salience = Math.pow(productMi, 1 / edgeCount);
135
+ return Math.max(epsilon, Math.min(1, salience));
136
+ }
137
+ //#endregion
138
+ //#region src/ranking/baselines/utils.ts
139
+ /**
140
+ * Normalise a set of scored paths and return them sorted highest-first.
141
+ *
142
+ * All scores are normalised relative to the maximum observed score.
143
+ * When `includeScores` is false, raw (un-normalised) scores are preserved.
144
+ * Handles degenerate cases: empty input and all-zero scores.
145
+ *
146
+ * @param paths - Original paths in input order
147
+ * @param scored - Paths paired with their computed scores
148
+ * @param method - Method name to embed in the result
149
+ * @param includeScores - When true, normalise scores to [0, 1]; when false, keep raw scores
150
+ * @returns BaselineResult with ranked paths
151
+ */
152
+ function normaliseAndRank(paths, scored, method, includeScores) {
153
+ if (scored.length === 0) return {
154
+ paths: [],
155
+ method
156
+ };
157
+ const maxScore = Math.max(...scored.map((s) => s.score));
158
+ if (maxScore === 0) return {
159
+ paths: paths.map((path) => ({
160
+ ...path,
161
+ score: 0
162
+ })),
163
+ method
164
+ };
165
+ return {
166
+ paths: scored.map(({ path, score }) => ({
167
+ ...path,
168
+ score: includeScores ? score / maxScore : score
169
+ })).sort((a, b) => b.score - a.score),
170
+ method
171
+ };
172
+ }
173
+ //#endregion
174
+ //#region src/ranking/baselines/shortest.ts
175
+ /**
176
+ * Rank paths by length (shortest first).
177
+ *
178
+ * Score = 1 / path_length, normalised to [0, 1].
179
+ *
180
+ * @param _graph - Source graph (unused for length ranking)
181
+ * @param paths - Paths to rank
182
+ * @param config - Configuration options
183
+ * @returns Ranked paths (shortest first)
184
+ */
185
+ function shortest(_graph, paths, config) {
186
+ const { includeScores = true } = config ?? {};
187
+ if (paths.length === 0) return {
188
+ paths: [],
189
+ method: "shortest"
190
+ };
191
+ return normaliseAndRank(paths, paths.map((path) => ({
192
+ path,
193
+ score: 1 / path.nodes.length
194
+ })), "shortest", includeScores);
195
+ }
196
+ //#endregion
197
+ //#region src/ranking/baselines/degree-sum.ts
198
+ /**
199
+ * Rank paths by sum of node degrees.
200
+ *
201
+ * @param graph - Source graph
202
+ * @param paths - Paths to rank
203
+ * @param config - Configuration options
204
+ * @returns Ranked paths (highest degree-sum first)
205
+ */
206
+ function degreeSum(graph, paths, config) {
207
+ const { includeScores = true } = config ?? {};
208
+ if (paths.length === 0) return {
209
+ paths: [],
210
+ method: "degree-sum"
211
+ };
212
+ return normaliseAndRank(paths, paths.map((path) => {
213
+ let degreeSum = 0;
214
+ for (const nodeId of path.nodes) degreeSum += graph.degree(nodeId);
215
+ return {
216
+ path,
217
+ score: degreeSum
218
+ };
219
+ }), "degree-sum", includeScores);
220
+ }
221
+ //#endregion
222
+ //#region src/ranking/baselines/widest-path.ts
223
+ /**
224
+ * Rank paths by widest bottleneck (minimum edge similarity).
225
+ *
226
+ * @param graph - Source graph
227
+ * @param paths - Paths to rank
228
+ * @param config - Configuration options
229
+ * @returns Ranked paths (highest bottleneck first)
230
+ */
231
+ function widestPath(graph, paths, config) {
232
+ const { includeScores = true } = config ?? {};
233
+ if (paths.length === 0) return {
234
+ paths: [],
235
+ method: "widest-path"
236
+ };
237
+ return normaliseAndRank(paths, paths.map((path) => {
238
+ if (path.nodes.length < 2) return {
239
+ path,
240
+ score: 1
241
+ };
242
+ let minSimilarity = Number.POSITIVE_INFINITY;
243
+ for (let i = 0; i < path.nodes.length - 1; i++) {
244
+ const source = path.nodes[i];
245
+ const target = path.nodes[i + 1];
246
+ if (source === void 0 || target === void 0) continue;
247
+ const edgeSimilarity = require_jaccard.jaccard(graph, source, target);
248
+ minSimilarity = Math.min(minSimilarity, edgeSimilarity);
249
+ }
250
+ return {
251
+ path,
252
+ score: minSimilarity === Number.POSITIVE_INFINITY ? 1 : minSimilarity
253
+ };
254
+ }), "widest-path", includeScores);
255
+ }
256
+ //#endregion
257
+ //#region src/ranking/baselines/jaccard-arithmetic.ts
258
+ /**
259
+ * Rank paths by arithmetic mean of edge Jaccard similarities.
260
+ *
261
+ * @param graph - Source graph
262
+ * @param paths - Paths to rank
263
+ * @param config - Configuration options
264
+ * @returns Ranked paths (highest arithmetic mean first)
265
+ */
266
+ function jaccardArithmetic(graph, paths, config) {
267
+ const { includeScores = true } = config ?? {};
268
+ if (paths.length === 0) return {
269
+ paths: [],
270
+ method: "jaccard-arithmetic"
271
+ };
272
+ return normaliseAndRank(paths, paths.map((path) => {
273
+ if (path.nodes.length < 2) return {
274
+ path,
275
+ score: 1
276
+ };
277
+ let similaritySum = 0;
278
+ let edgeCount = 0;
279
+ for (let i = 0; i < path.nodes.length - 1; i++) {
280
+ const source = path.nodes[i];
281
+ const target = path.nodes[i + 1];
282
+ if (source === void 0 || target === void 0) continue;
283
+ const edgeSimilarity = require_jaccard.jaccard(graph, source, target);
284
+ similaritySum += edgeSimilarity;
285
+ edgeCount++;
286
+ }
287
+ return {
288
+ path,
289
+ score: edgeCount > 0 ? similaritySum / edgeCount : 1
290
+ };
291
+ }), "jaccard-arithmetic", includeScores);
292
+ }
293
+ //#endregion
294
+ //#region src/ranking/baselines/pagerank.ts
295
+ /**
296
+ * Compute PageRank centrality for all nodes using power iteration.
297
+ *
298
+ * @param graph - Source graph
299
+ * @param damping - Damping factor (default 0.85)
300
+ * @param tolerance - Convergence tolerance (default 1e-6)
301
+ * @param maxIterations - Maximum iterations (default 100)
302
+ * @returns Map of node ID to PageRank value
303
+ */
304
+ function computePageRank(graph, damping = .85, tolerance = 1e-6, maxIterations = 100) {
305
+ const nodes = Array.from(graph.nodeIds());
306
+ const n = nodes.length;
307
+ if (n === 0) return /* @__PURE__ */ new Map();
308
+ const ranks = /* @__PURE__ */ new Map();
309
+ const newRanks = /* @__PURE__ */ new Map();
310
+ for (const nodeId of nodes) {
311
+ ranks.set(nodeId, 1 / n);
312
+ newRanks.set(nodeId, 0);
313
+ }
314
+ let isCurrentRanks = true;
315
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
316
+ let maxChange = 0;
317
+ const currMap = isCurrentRanks ? ranks : newRanks;
318
+ const nextMap = isCurrentRanks ? newRanks : ranks;
319
+ for (const nodeId of nodes) {
320
+ let incomingSum = 0;
321
+ for (const incomingId of graph.neighbours(nodeId, "in")) {
322
+ const incomingRank = currMap.get(incomingId) ?? 0;
323
+ const outDegree = graph.degree(incomingId);
324
+ if (outDegree > 0) incomingSum += incomingRank / outDegree;
325
+ }
326
+ const newRank = (1 - damping) / n + damping * incomingSum;
327
+ nextMap.set(nodeId, newRank);
328
+ const oldRank = currMap.get(nodeId) ?? 0;
329
+ maxChange = Math.max(maxChange, Math.abs(newRank - oldRank));
330
+ }
331
+ if (maxChange < tolerance) break;
332
+ isCurrentRanks = !isCurrentRanks;
333
+ currMap.clear();
334
+ }
335
+ return isCurrentRanks ? ranks : newRanks;
336
+ }
337
+ /**
338
+ * Rank paths by sum of PageRank scores.
339
+ *
340
+ * @param graph - Source graph
341
+ * @param paths - Paths to rank
342
+ * @param config - Configuration options
343
+ * @returns Ranked paths (highest PageRank sum first)
344
+ */
345
+ function pagerank(graph, paths, config) {
346
+ const { includeScores = true } = config ?? {};
347
+ if (paths.length === 0) return {
348
+ paths: [],
349
+ method: "pagerank"
350
+ };
351
+ const ranks = computePageRank(graph);
352
+ return normaliseAndRank(paths, paths.map((path) => {
353
+ let prSum = 0;
354
+ for (const nodeId of path.nodes) prSum += ranks.get(nodeId) ?? 0;
355
+ return {
356
+ path,
357
+ score: prSum
358
+ };
359
+ }), "pagerank", includeScores);
360
+ }
361
+ //#endregion
362
+ //#region src/ranking/baselines/betweenness.ts
363
+ /**
364
+ * Compute betweenness centrality for all nodes using Brandes algorithm.
365
+ *
366
+ * @param graph - Source graph
367
+ * @returns Map of node ID to betweenness value
368
+ */
369
+ function computeBetweenness(graph) {
370
+ const nodes = Array.from(graph.nodeIds());
371
+ const betweenness = /* @__PURE__ */ new Map();
372
+ for (const nodeId of nodes) betweenness.set(nodeId, 0);
373
+ for (const source of nodes) {
374
+ const predecessors = /* @__PURE__ */ new Map();
375
+ const distance = /* @__PURE__ */ new Map();
376
+ const sigma = /* @__PURE__ */ new Map();
377
+ const queue = [];
378
+ for (const nodeId of nodes) {
379
+ predecessors.set(nodeId, []);
380
+ distance.set(nodeId, -1);
381
+ sigma.set(nodeId, 0);
382
+ }
383
+ distance.set(source, 0);
384
+ sigma.set(source, 1);
385
+ queue.push(source);
386
+ for (const v of queue) {
387
+ const vDist = distance.get(v) ?? -1;
388
+ const neighbours = graph.neighbours(v);
389
+ for (const w of neighbours) {
390
+ const wDist = distance.get(w) ?? -1;
391
+ if (wDist < 0) {
392
+ distance.set(w, vDist + 1);
393
+ queue.push(w);
394
+ }
395
+ if (wDist === vDist + 1) {
396
+ const wSigma = sigma.get(w) ?? 0;
397
+ const vSigma = sigma.get(v) ?? 0;
398
+ sigma.set(w, wSigma + vSigma);
399
+ const wPred = predecessors.get(w) ?? [];
400
+ wPred.push(v);
401
+ predecessors.set(w, wPred);
402
+ }
403
+ }
404
+ }
405
+ const delta = /* @__PURE__ */ new Map();
406
+ for (const nodeId of nodes) delta.set(nodeId, 0);
407
+ const sorted = [...nodes].sort((a, b) => {
408
+ const aD = distance.get(a) ?? -1;
409
+ return (distance.get(b) ?? -1) - aD;
410
+ });
411
+ for (const w of sorted) {
412
+ if (w === source) continue;
413
+ const wDelta = delta.get(w) ?? 0;
414
+ const wSigma = sigma.get(w) ?? 0;
415
+ const wPred = predecessors.get(w) ?? [];
416
+ for (const v of wPred) {
417
+ const vSigma = sigma.get(v) ?? 0;
418
+ const vDelta = delta.get(v) ?? 0;
419
+ if (wSigma > 0) delta.set(v, vDelta + vSigma / wSigma * (1 + wDelta));
420
+ }
421
+ if (w !== source) {
422
+ const current = betweenness.get(w) ?? 0;
423
+ betweenness.set(w, current + wDelta);
424
+ }
425
+ }
426
+ }
427
+ return betweenness;
428
+ }
429
+ /**
430
+ * Rank paths by sum of betweenness scores.
431
+ *
432
+ * @param graph - Source graph
433
+ * @param paths - Paths to rank
434
+ * @param config - Configuration options
435
+ * @returns Ranked paths (highest betweenness sum first)
436
+ */
437
+ function betweenness(graph, paths, config) {
438
+ const { includeScores = true } = config ?? {};
439
+ if (paths.length === 0) return {
440
+ paths: [],
441
+ method: "betweenness"
442
+ };
443
+ const bcMap = computeBetweenness(graph);
444
+ return normaliseAndRank(paths, paths.map((path) => {
445
+ let bcSum = 0;
446
+ for (const nodeId of path.nodes) bcSum += bcMap.get(nodeId) ?? 0;
447
+ return {
448
+ path,
449
+ score: bcSum
450
+ };
451
+ }), "betweenness", includeScores);
452
+ }
453
+ //#endregion
454
+ //#region src/ranking/baselines/katz.ts
455
+ /**
456
+ * Compute truncated Katz centrality between two nodes.
457
+ *
458
+ * Uses iterative matrix-vector products to avoid full matrix powers.
459
+ * score(s,t) = sum_{k=1}^{K} beta^k * walks_k(s,t)
460
+ *
461
+ * @param graph - Source graph
462
+ * @param source - Source node ID
463
+ * @param target - Target node ID
464
+ * @param k - Truncation depth (default 5)
465
+ * @param beta - Attenuation factor (default 0.005)
466
+ * @returns Katz score
467
+ */
468
+ function computeKatz(graph, source, target, k = 5, beta = .005) {
469
+ const nodes = Array.from(graph.nodeIds());
470
+ const nodeToIdx = /* @__PURE__ */ new Map();
471
+ nodes.forEach((nodeId, idx) => {
472
+ nodeToIdx.set(nodeId, idx);
473
+ });
474
+ const n = nodes.length;
475
+ if (n === 0) return 0;
476
+ const sourceIdx = nodeToIdx.get(source);
477
+ const targetIdx = nodeToIdx.get(target);
478
+ if (sourceIdx === void 0 || targetIdx === void 0) return 0;
479
+ let walks = new Float64Array(n);
480
+ walks[targetIdx] = 1;
481
+ let katzScore = 0;
482
+ for (let depth = 1; depth <= k; depth++) {
483
+ const walksNext = new Float64Array(n);
484
+ for (const sourceNode of nodes) {
485
+ const srcIdx = nodeToIdx.get(sourceNode);
486
+ if (srcIdx === void 0) continue;
487
+ const neighbours = graph.neighbours(sourceNode);
488
+ for (const neighbourId of neighbours) {
489
+ const nIdx = nodeToIdx.get(neighbourId);
490
+ if (nIdx === void 0) continue;
491
+ walksNext[srcIdx] = (walksNext[srcIdx] ?? 0) + (walks[nIdx] ?? 0);
492
+ }
493
+ }
494
+ const walkCount = walksNext[sourceIdx] ?? 0;
495
+ katzScore += Math.pow(beta, depth) * walkCount;
496
+ walks = walksNext;
497
+ }
498
+ return katzScore;
499
+ }
500
+ /**
501
+ * Rank paths by Katz centrality between endpoints.
502
+ *
503
+ * @param graph - Source graph
504
+ * @param paths - Paths to rank
505
+ * @param config - Configuration options
506
+ * @returns Ranked paths (highest Katz score first)
507
+ */
508
+ function katz(graph, paths, config) {
509
+ const { includeScores = true } = config ?? {};
510
+ if (paths.length === 0) return {
511
+ paths: [],
512
+ method: "katz"
513
+ };
514
+ return normaliseAndRank(paths, paths.map((path) => {
515
+ const source = path.nodes[0];
516
+ const target = path.nodes[path.nodes.length - 1];
517
+ if (source === void 0 || target === void 0) return {
518
+ path,
519
+ score: 0
520
+ };
521
+ return {
522
+ path,
523
+ score: computeKatz(graph, source, target)
524
+ };
525
+ }), "katz", includeScores);
526
+ }
527
+ //#endregion
528
+ //#region src/ranking/baselines/communicability.ts
529
+ /**
530
+ * Compute truncated communicability between two nodes.
531
+ *
532
+ * Uses Taylor series expansion: (e^A)_{s,t} ≈ sum_{k=0}^{K} A^k_{s,t} / k!
533
+ *
534
+ * @param graph - Source graph
535
+ * @param source - Source node ID
536
+ * @param target - Target node ID
537
+ * @param k - Truncation depth (default 15)
538
+ * @returns Communicability score
539
+ */
540
+ function computeCommunicability(graph, source, target, k = 15) {
541
+ const nodes = Array.from(graph.nodeIds());
542
+ const nodeToIdx = /* @__PURE__ */ new Map();
543
+ nodes.forEach((nodeId, idx) => {
544
+ nodeToIdx.set(nodeId, idx);
545
+ });
546
+ const n = nodes.length;
547
+ if (n === 0) return 0;
548
+ const sourceIdx = nodeToIdx.get(source);
549
+ const targetIdx = nodeToIdx.get(target);
550
+ if (sourceIdx === void 0 || targetIdx === void 0) return 0;
551
+ let walks = new Float64Array(n);
552
+ walks[targetIdx] = 1;
553
+ let commScore = walks[sourceIdx] ?? 0;
554
+ let factorial = 1;
555
+ for (let depth = 1; depth <= k; depth++) {
556
+ const walksNext = new Float64Array(n);
557
+ for (const fromNode of nodes) {
558
+ const fromIdx = nodeToIdx.get(fromNode);
559
+ if (fromIdx === void 0) continue;
560
+ const neighbours = graph.neighbours(fromNode);
561
+ for (const toNodeId of neighbours) {
562
+ const toIdx = nodeToIdx.get(toNodeId);
563
+ if (toIdx === void 0) continue;
564
+ walksNext[fromIdx] = (walksNext[fromIdx] ?? 0) + (walks[toIdx] ?? 0);
565
+ }
566
+ }
567
+ factorial *= depth;
568
+ commScore += (walksNext[sourceIdx] ?? 0) / factorial;
569
+ walks = walksNext;
570
+ }
571
+ return commScore;
572
+ }
573
+ /**
574
+ * Rank paths by communicability between endpoints.
575
+ *
576
+ * @param graph - Source graph
577
+ * @param paths - Paths to rank
578
+ * @param config - Configuration options
579
+ * @returns Ranked paths (highest communicability first)
580
+ */
581
+ function communicability(graph, paths, config) {
582
+ const { includeScores = true } = config ?? {};
583
+ if (paths.length === 0) return {
584
+ paths: [],
585
+ method: "communicability"
586
+ };
587
+ return normaliseAndRank(paths, paths.map((path) => {
588
+ const source = path.nodes[0];
589
+ const target = path.nodes[path.nodes.length - 1];
590
+ if (source === void 0 || target === void 0) return {
591
+ path,
592
+ score: 0
593
+ };
594
+ return {
595
+ path,
596
+ score: computeCommunicability(graph, source, target)
597
+ };
598
+ }), "communicability", includeScores);
599
+ }
600
+ //#endregion
601
+ //#region src/ranking/baselines/resistance-distance.ts
602
+ /**
603
+ * Compute effective resistance between two nodes via Laplacian pseudoinverse.
604
+ *
605
+ * Resistance = L^+_{s,s} + L^+_{t,t} - 2*L^+_{s,t}
606
+ * where L^+ is the pseudoinverse of the Laplacian matrix.
607
+ *
608
+ * @param graph - Source graph
609
+ * @param source - Source node ID
610
+ * @param target - Target node ID
611
+ * @returns Effective resistance
612
+ */
613
+ function computeResistance(graph, source, target) {
614
+ const nodes = Array.from(graph.nodeIds());
615
+ const nodeToIdx = /* @__PURE__ */ new Map();
616
+ nodes.forEach((nodeId, idx) => {
617
+ nodeToIdx.set(nodeId, idx);
618
+ });
619
+ const n = nodes.length;
620
+ if (n === 0 || n > 5e3) throw new Error(`Cannot compute resistance distance: graph too large (${String(n)} nodes). Maximum 5000.`);
621
+ const sourceIdx = nodeToIdx.get(source);
622
+ const targetIdx = nodeToIdx.get(target);
623
+ if (sourceIdx === void 0 || targetIdx === void 0) return 0;
624
+ const L = Array.from({ length: n }, () => Array.from({ length: n }, () => 0));
625
+ for (let i = 0; i < n; i++) {
626
+ const nodeId = nodes[i];
627
+ if (nodeId === void 0) continue;
628
+ const degree = graph.degree(nodeId);
629
+ const row = L[i];
630
+ if (row !== void 0) row[i] = degree;
631
+ const neighbours = graph.neighbours(nodeId);
632
+ for (const neighbourId of neighbours) {
633
+ const j = nodeToIdx.get(neighbourId);
634
+ if (j !== void 0 && row !== void 0) row[j] = -1;
635
+ }
636
+ }
637
+ const Lpinv = pinv(L);
638
+ const resistance = (Lpinv[sourceIdx]?.[sourceIdx] ?? 0) + (Lpinv[targetIdx]?.[targetIdx] ?? 0) - 2 * (Lpinv[sourceIdx]?.[targetIdx] ?? 0);
639
+ return Math.max(resistance, 1e-10);
640
+ }
641
+ /**
642
+ * Compute Moore-Penrose pseudoinverse of a matrix.
643
+ * Simplified implementation for small dense matrices.
644
+ *
645
+ * @param A - Square matrix
646
+ * @returns Pseudoinverse A^+
647
+ */
648
+ function pinv(A) {
649
+ const n = A.length;
650
+ if (n === 0) return [];
651
+ const M = A.map((row) => [...row]);
652
+ const epsilon = 1e-10;
653
+ for (let i = 0; i < n; i++) {
654
+ const row = M[i];
655
+ if (row !== void 0) row[i] = (row[i] ?? 0) + epsilon;
656
+ }
657
+ return gaussianInverse(M);
658
+ }
659
+ /**
660
+ * Compute matrix inverse using Gaussian elimination with partial pivoting.
661
+ *
662
+ * @param A - Matrix to invert
663
+ * @returns Inverted matrix
664
+ */
665
+ function gaussianInverse(A) {
666
+ const n = A.length;
667
+ const aug = A.map((row, i) => {
668
+ const identity = Array.from({ length: n }, (_, j) => i === j ? 1 : 0);
669
+ return [...row, ...identity];
670
+ });
671
+ for (let col = 0; col < n; col++) {
672
+ let maxRow = col;
673
+ for (let row = col + 1; row < n; row++) {
674
+ const currentRow = aug[row];
675
+ const maxRowRef = aug[maxRow];
676
+ if (currentRow !== void 0 && maxRowRef !== void 0 && Math.abs(currentRow[col] ?? 0) > Math.abs(maxRowRef[col] ?? 0)) maxRow = row;
677
+ }
678
+ const currentCol = aug[col];
679
+ const maxRowAug = aug[maxRow];
680
+ if (currentCol !== void 0 && maxRowAug !== void 0) {
681
+ aug[col] = maxRowAug;
682
+ aug[maxRow] = currentCol;
683
+ }
684
+ const pivotRow = aug[col];
685
+ const pivot = pivotRow?.[col];
686
+ if (pivot === void 0 || Math.abs(pivot) < 1e-12) continue;
687
+ if (pivotRow !== void 0) for (let j = col; j < 2 * n; j++) pivotRow[j] = (pivotRow[j] ?? 0) / pivot;
688
+ for (let row = 0; row < n; row++) {
689
+ if (row === col) continue;
690
+ const eliminationRow = aug[row];
691
+ const factor = eliminationRow?.[col] ?? 0;
692
+ if (eliminationRow !== void 0 && pivotRow !== void 0) for (let j = col; j < 2 * n; j++) eliminationRow[j] = (eliminationRow[j] ?? 0) - factor * (pivotRow[j] ?? 0);
693
+ }
694
+ }
695
+ const Ainv = [];
696
+ for (let i = 0; i < n; i++) Ainv[i] = (aug[i]?.slice(n) ?? []).map((v) => v);
697
+ return Ainv;
698
+ }
699
+ /**
700
+ * Rank paths by reciprocal of resistance distance between endpoints.
701
+ *
702
+ * @param graph - Source graph
703
+ * @param paths - Paths to rank
704
+ * @param config - Configuration options
705
+ * @returns Ranked paths (highest conductance first)
706
+ */
707
+ function resistanceDistance(graph, paths, config) {
708
+ const { includeScores = true } = config ?? {};
709
+ if (paths.length === 0) return {
710
+ paths: [],
711
+ method: "resistance-distance"
712
+ };
713
+ const nodeCount = Array.from(graph.nodeIds()).length;
714
+ if (nodeCount > 5e3) throw new Error(`Cannot rank paths: graph too large (${String(nodeCount)} nodes). Resistance distance requires O(n^3) computation; maximum 5000 nodes.`);
715
+ return normaliseAndRank(paths, paths.map((path) => {
716
+ const source = path.nodes[0];
717
+ const target = path.nodes[path.nodes.length - 1];
718
+ if (source === void 0 || target === void 0) return {
719
+ path,
720
+ score: 0
721
+ };
722
+ return {
723
+ path,
724
+ score: 1 / computeResistance(graph, source, target)
725
+ };
726
+ }), "resistance-distance", includeScores);
727
+ }
728
+ //#endregion
729
+ //#region src/ranking/baselines/random-ranking.ts
730
+ /**
731
+ * Deterministic seeded random number generator.
732
+ * Uses FNV-1a-like hash for input → [0, 1] output.
733
+ *
734
+ * @param input - String to hash
735
+ * @param seed - Random seed for reproducibility
736
+ * @returns Deterministic random value in [0, 1]
737
+ */
738
+ function seededRandom(input, seed = 0) {
739
+ let h = seed;
740
+ for (let i = 0; i < input.length; i++) {
741
+ h = Math.imul(h ^ input.charCodeAt(i), 2654435769);
742
+ h ^= h >>> 16;
743
+ }
744
+ return (h >>> 0) / 4294967295;
745
+ }
746
+ /**
747
+ * Rank paths randomly (null hypothesis baseline).
748
+ *
749
+ * @param _graph - Source graph (unused)
750
+ * @param paths - Paths to rank
751
+ * @param config - Configuration options
752
+ * @returns Ranked paths (randomly ordered)
753
+ */
754
+ function randomRanking(_graph, paths, config) {
755
+ const { includeScores = true, seed = 0 } = config ?? {};
756
+ if (paths.length === 0) return {
757
+ paths: [],
758
+ method: "random"
759
+ };
760
+ return normaliseAndRank(paths, paths.map((path) => {
761
+ return {
762
+ path,
763
+ score: seededRandom(path.nodes.join(","), seed)
764
+ };
765
+ }), "random", includeScores);
766
+ }
767
+ //#endregion
768
+ //#region src/ranking/baselines/hitting-time.ts
769
+ /**
770
+ * Seeded deterministic random number generator (LCG).
771
+ * Suitable for reproducible random walk simulation.
772
+ */
773
+ var SeededRNG = class {
774
+ state;
775
+ constructor(seed) {
776
+ this.state = seed;
777
+ }
778
+ /**
779
+ * Generate next pseudorandom value in [0, 1).
780
+ */
781
+ next() {
782
+ this.state = this.state * 1103515245 + 12345 & 2147483647;
783
+ return this.state / 2147483647;
784
+ }
785
+ };
786
+ /**
787
+ * Compute hitting time via Monte Carlo random walk simulation.
788
+ *
789
+ * @param graph - Source graph
790
+ * @param source - Source node ID
791
+ * @param target - Target node ID
792
+ * @param walks - Number of walks to simulate
793
+ * @param maxSteps - Maximum steps per walk
794
+ * @param rng - Seeded RNG instance
795
+ * @returns Average hitting time across walks
796
+ */
797
+ function computeHittingTimeApproximate(graph, source, target, walks, maxSteps, rng) {
798
+ if (source === target) return 0;
799
+ let totalSteps = 0;
800
+ let successfulWalks = 0;
801
+ for (let w = 0; w < walks; w++) {
802
+ let current = source;
803
+ let steps = 0;
804
+ while (current !== target && steps < maxSteps) {
805
+ const neighbours = Array.from(graph.neighbours(current));
806
+ if (neighbours.length === 0) break;
807
+ const nextNode = neighbours[Math.floor(rng.next() * neighbours.length)];
808
+ if (nextNode === void 0) break;
809
+ current = nextNode;
810
+ steps++;
811
+ }
812
+ if (current === target) {
813
+ totalSteps += steps;
814
+ successfulWalks++;
815
+ }
816
+ }
817
+ if (successfulWalks > 0) return totalSteps / successfulWalks;
818
+ return maxSteps;
819
+ }
820
+ /**
821
+ * Compute hitting time via exact fundamental matrix method.
822
+ *
823
+ * For small graphs, computes exact expected hitting times using
824
+ * the fundamental matrix of the random walk.
825
+ *
826
+ * @param graph - Source graph
827
+ * @param source - Source node ID
828
+ * @param target - Target node ID
829
+ * @returns Exact hitting time (or approximation if convergence fails)
830
+ */
831
+ function computeHittingTimeExact(graph, source, target) {
832
+ if (source === target) return 0;
833
+ const nodes = Array.from(graph.nodeIds());
834
+ const nodeToIdx = /* @__PURE__ */ new Map();
835
+ nodes.forEach((nodeId, idx) => {
836
+ nodeToIdx.set(nodeId, idx);
837
+ });
838
+ const n = nodes.length;
839
+ const sourceIdx = nodeToIdx.get(source);
840
+ const targetIdx = nodeToIdx.get(target);
841
+ if (sourceIdx === void 0 || targetIdx === void 0) return 0;
842
+ const P = [];
843
+ for (let i = 0; i < n; i++) {
844
+ const row = [];
845
+ for (let j = 0; j < n; j++) row[j] = 0;
846
+ P[i] = row;
847
+ }
848
+ for (const nodeId of nodes) {
849
+ const idx = nodeToIdx.get(nodeId);
850
+ if (idx === void 0) continue;
851
+ const pRow = P[idx];
852
+ if (pRow === void 0) continue;
853
+ if (idx === targetIdx) pRow[idx] = 1;
854
+ else {
855
+ const neighbours = Array.from(graph.neighbours(nodeId));
856
+ const degree = neighbours.length;
857
+ if (degree > 0) for (const neighbourId of neighbours) {
858
+ const nIdx = nodeToIdx.get(neighbourId);
859
+ if (nIdx !== void 0) pRow[nIdx] = 1 / degree;
860
+ }
861
+ }
862
+ }
863
+ const transientIndices = [];
864
+ for (let i = 0; i < n; i++) if (i !== targetIdx) transientIndices.push(i);
865
+ const m = transientIndices.length;
866
+ const Q = [];
867
+ for (let i = 0; i < m; i++) {
868
+ const row = [];
869
+ for (let j = 0; j < m; j++) row[j] = 0;
870
+ Q[i] = row;
871
+ }
872
+ for (let i = 0; i < m; i++) {
873
+ const qRow = Q[i];
874
+ if (qRow === void 0) continue;
875
+ const origI = transientIndices[i];
876
+ if (origI === void 0) continue;
877
+ const pRow = P[origI];
878
+ if (pRow === void 0) continue;
879
+ for (let j = 0; j < m; j++) {
880
+ const origJ = transientIndices[j];
881
+ if (origJ === void 0) continue;
882
+ qRow[j] = pRow[origJ] ?? 0;
883
+ }
884
+ }
885
+ const IMQ = [];
886
+ for (let i = 0; i < m; i++) {
887
+ const row = [];
888
+ for (let j = 0; j < m; j++) row[j] = i === j ? 1 : 0;
889
+ IMQ[i] = row;
890
+ }
891
+ for (let i = 0; i < m; i++) {
892
+ const imqRow = IMQ[i];
893
+ if (imqRow === void 0) continue;
894
+ const qRow = Q[i];
895
+ for (let j = 0; j < m; j++) {
896
+ const qVal = qRow?.[j] ?? 0;
897
+ imqRow[j] = (i === j ? 1 : 0) - qVal;
898
+ }
899
+ }
900
+ const N = invertMatrix(IMQ);
901
+ if (N === null) return 1;
902
+ const sourceTransientIdx = transientIndices.indexOf(sourceIdx);
903
+ if (sourceTransientIdx < 0) return 0;
904
+ let hittingTime = 0;
905
+ const row = N[sourceTransientIdx];
906
+ if (row !== void 0) for (const val of row) hittingTime += val;
907
+ return hittingTime;
908
+ }
909
+ /**
910
+ * Invert a square matrix using Gaussian elimination with partial pivoting.
911
+ *
912
+ * @param matrix - Input matrix (n × n)
913
+ * @returns Inverted matrix, or null if singular
914
+ */
915
+ function invertMatrix(matrix) {
916
+ const n = matrix.length;
917
+ const aug = [];
918
+ for (let i = 0; i < n; i++) {
919
+ const row = [];
920
+ const matRow = matrix[i];
921
+ for (let j = 0; j < n; j++) row[j] = matRow?.[j] ?? 0;
922
+ for (let j = 0; j < n; j++) row[n + j] = i === j ? 1 : 0;
923
+ aug[i] = row;
924
+ }
925
+ for (let col = 0; col < n; col++) {
926
+ let pivotRow = col;
927
+ const pivotCol = aug[pivotRow];
928
+ if (pivotCol === void 0) return null;
929
+ for (let row = col + 1; row < n; row++) {
930
+ const currRowVal = aug[row]?.[col] ?? 0;
931
+ const pivotRowVal = pivotCol[col] ?? 0;
932
+ if (Math.abs(currRowVal) > Math.abs(pivotRowVal)) pivotRow = row;
933
+ }
934
+ const augPivot = aug[pivotRow];
935
+ if (augPivot === void 0 || Math.abs(augPivot[col] ?? 0) < 1e-10) return null;
936
+ [aug[col], aug[pivotRow]] = [aug[pivotRow] ?? [], aug[col] ?? []];
937
+ const scaledPivotRow = aug[col];
938
+ if (scaledPivotRow === void 0) return null;
939
+ const pivot = scaledPivotRow[col] ?? 1;
940
+ for (let j = 0; j < 2 * n; j++) scaledPivotRow[j] = (scaledPivotRow[j] ?? 0) / pivot;
941
+ for (let row = col + 1; row < n; row++) {
942
+ const currRow = aug[row];
943
+ if (currRow === void 0) continue;
944
+ const factor = currRow[col] ?? 0;
945
+ for (let j = 0; j < 2 * n; j++) currRow[j] = (currRow[j] ?? 0) - factor * (scaledPivotRow[j] ?? 0);
946
+ }
947
+ }
948
+ for (let col = n - 1; col > 0; col--) {
949
+ const colRow = aug[col];
950
+ if (colRow === void 0) return null;
951
+ for (let row = col - 1; row >= 0; row--) {
952
+ const currRow = aug[row];
953
+ if (currRow === void 0) continue;
954
+ const factor = currRow[col] ?? 0;
955
+ for (let j = 0; j < 2 * n; j++) currRow[j] = (currRow[j] ?? 0) - factor * (colRow[j] ?? 0);
956
+ }
957
+ }
958
+ const inv = [];
959
+ for (let i = 0; i < n; i++) {
960
+ const row = [];
961
+ for (let j = 0; j < n; j++) row[j] = 0;
962
+ inv[i] = row;
963
+ }
964
+ for (let i = 0; i < n; i++) {
965
+ const invRow = inv[i];
966
+ if (invRow === void 0) continue;
967
+ const augRow = aug[i];
968
+ if (augRow === void 0) continue;
969
+ for (let j = 0; j < n; j++) invRow[j] = augRow[n + j] ?? 0;
970
+ }
971
+ return inv;
972
+ }
973
+ /**
974
+ * Rank paths by inverse hitting time between endpoints.
975
+ *
976
+ * @param graph - Source graph
977
+ * @param paths - Paths to rank
978
+ * @param config - Configuration options
979
+ * @returns Ranked paths (highest inverse hitting time first)
980
+ */
981
+ function hittingTime(graph, paths, config) {
982
+ const { includeScores = true, mode = "auto", walks = 1e3, maxSteps = 1e4, seed = 42 } = config ?? {};
983
+ if (paths.length === 0) return {
984
+ paths: [],
985
+ method: "hitting-time"
986
+ };
987
+ const nodeCount = Array.from(graph.nodeIds()).length;
988
+ const actualMode = mode === "auto" ? nodeCount < 100 ? "exact" : "approximate" : mode;
989
+ const rng = new SeededRNG(seed);
990
+ const scored = paths.map((path) => {
991
+ const source = path.nodes[0];
992
+ const target = path.nodes[path.nodes.length - 1];
993
+ if (source === void 0 || target === void 0) return {
994
+ path,
995
+ score: 0
996
+ };
997
+ const ht = actualMode === "exact" ? computeHittingTimeExact(graph, source, target) : computeHittingTimeApproximate(graph, source, target, walks, maxSteps, rng);
998
+ return {
999
+ path,
1000
+ score: ht > 0 ? 1 / ht : 0
1001
+ };
1002
+ });
1003
+ const maxScore = Math.max(...scored.map((s) => s.score));
1004
+ if (!Number.isFinite(maxScore)) return {
1005
+ paths: paths.map((path) => ({
1006
+ ...path,
1007
+ score: 0
1008
+ })),
1009
+ method: "hitting-time"
1010
+ };
1011
+ return normaliseAndRank(paths, scored, "hitting-time", includeScores);
1012
+ }
1013
+ //#endregion
1014
+ Object.defineProperty(exports, "betweenness", {
1015
+ enumerable: true,
1016
+ get: function() {
1017
+ return betweenness;
1018
+ }
1019
+ });
1020
+ Object.defineProperty(exports, "communicability", {
1021
+ enumerable: true,
1022
+ get: function() {
1023
+ return communicability;
1024
+ }
1025
+ });
1026
+ Object.defineProperty(exports, "degreeSum", {
1027
+ enumerable: true,
1028
+ get: function() {
1029
+ return degreeSum;
1030
+ }
1031
+ });
1032
+ Object.defineProperty(exports, "hittingTime", {
1033
+ enumerable: true,
1034
+ get: function() {
1035
+ return hittingTime;
1036
+ }
1037
+ });
1038
+ Object.defineProperty(exports, "jaccardArithmetic", {
1039
+ enumerable: true,
1040
+ get: function() {
1041
+ return jaccardArithmetic;
1042
+ }
1043
+ });
1044
+ Object.defineProperty(exports, "katz", {
1045
+ enumerable: true,
1046
+ get: function() {
1047
+ return katz;
1048
+ }
1049
+ });
1050
+ Object.defineProperty(exports, "pagerank", {
1051
+ enumerable: true,
1052
+ get: function() {
1053
+ return pagerank;
1054
+ }
1055
+ });
1056
+ Object.defineProperty(exports, "parse", {
1057
+ enumerable: true,
1058
+ get: function() {
1059
+ return parse;
1060
+ }
1061
+ });
1062
+ Object.defineProperty(exports, "parseAsync", {
1063
+ enumerable: true,
1064
+ get: function() {
1065
+ return parseAsync;
1066
+ }
1067
+ });
1068
+ Object.defineProperty(exports, "randomRanking", {
1069
+ enumerable: true,
1070
+ get: function() {
1071
+ return randomRanking;
1072
+ }
1073
+ });
1074
+ Object.defineProperty(exports, "resistanceDistance", {
1075
+ enumerable: true,
1076
+ get: function() {
1077
+ return resistanceDistance;
1078
+ }
1079
+ });
1080
+ Object.defineProperty(exports, "shortest", {
1081
+ enumerable: true,
1082
+ get: function() {
1083
+ return shortest;
1084
+ }
1085
+ });
1086
+ Object.defineProperty(exports, "widestPath", {
1087
+ enumerable: true,
1088
+ get: function() {
1089
+ return widestPath;
1090
+ }
1091
+ });
1092
+
1093
+ //# sourceMappingURL=ranking-riRrEVAR.cjs.map