@graphty/algorithms 2.0.6 → 2.1.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.
Files changed (75) hide show
  1. package/README.md +9 -11
  2. package/dist/algorithms.js +499 -30
  3. package/dist/algorithms.js.map +1 -1
  4. package/dist/algorithms.standalone.js +500 -31
  5. package/dist/algorithms.standalone.js.map +1 -1
  6. package/dist/src/algorithms/centrality/betweenness.js +2 -2
  7. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  8. package/dist/src/algorithms/community/girvan-newman.js +1 -1
  9. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  10. package/dist/src/algorithms/matching/isomorphism.js +3 -3
  11. package/dist/src/algorithms/matching/isomorphism.js.map +1 -1
  12. package/dist/src/algorithms/mst/prim.js +1 -1
  13. package/dist/src/algorithms/mst/prim.js.map +1 -1
  14. package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -1
  15. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  16. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +1 -1
  17. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -1
  18. package/dist/src/algorithms/shortest-path/dijkstra.js +1 -1
  19. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  20. package/dist/src/algorithms/traversal/bfs-unified.js +2 -2
  21. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
  22. package/dist/src/algorithms/traversal/dfs.js +2 -2
  23. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  24. package/dist/src/clustering/spectral.js +2 -2
  25. package/dist/src/clustering/spectral.js.map +1 -1
  26. package/dist/src/index.d.ts +4 -0
  27. package/dist/src/index.d.ts.map +1 -1
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/indexed/accelerator.d.ts +14 -2
  30. package/dist/src/indexed/accelerator.d.ts.map +1 -1
  31. package/dist/src/indexed/accelerator.js +8 -0
  32. package/dist/src/indexed/accelerator.js.map +1 -1
  33. package/dist/src/indexed/components.d.ts +8 -0
  34. package/dist/src/indexed/components.d.ts.map +1 -1
  35. package/dist/src/indexed/components.js +8 -1
  36. package/dist/src/indexed/components.js.map +1 -1
  37. package/dist/src/indexed/hits.d.ts +39 -0
  38. package/dist/src/indexed/hits.d.ts.map +1 -0
  39. package/dist/src/indexed/hits.js +95 -0
  40. package/dist/src/indexed/hits.js.map +1 -0
  41. package/dist/src/indexed/index.d.ts +5 -1
  42. package/dist/src/indexed/index.d.ts.map +1 -1
  43. package/dist/src/indexed/index.js +5 -1
  44. package/dist/src/indexed/index.js.map +1 -1
  45. package/dist/src/indexed/k-core.d.ts +32 -0
  46. package/dist/src/indexed/k-core.d.ts.map +1 -0
  47. package/dist/src/indexed/k-core.js +118 -0
  48. package/dist/src/indexed/k-core.js.map +1 -0
  49. package/dist/src/indexed/katz.d.ts +38 -0
  50. package/dist/src/indexed/katz.d.ts.map +1 -0
  51. package/dist/src/indexed/katz.js +61 -0
  52. package/dist/src/indexed/katz.js.map +1 -0
  53. package/dist/src/indexed/louvain.d.ts +33 -0
  54. package/dist/src/indexed/louvain.d.ts.map +1 -0
  55. package/dist/src/indexed/louvain.js +301 -0
  56. package/dist/src/indexed/louvain.js.map +1 -0
  57. package/package.json +5 -5
  58. package/src/algorithms/centrality/betweenness.ts +2 -2
  59. package/src/algorithms/community/girvan-newman.ts +1 -1
  60. package/src/algorithms/matching/isomorphism.ts +3 -3
  61. package/src/algorithms/mst/prim.ts +1 -1
  62. package/src/algorithms/shortest-path/bellman-ford.ts +3 -3
  63. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +1 -1
  64. package/src/algorithms/shortest-path/dijkstra.ts +1 -1
  65. package/src/algorithms/traversal/bfs-unified.ts +2 -2
  66. package/src/algorithms/traversal/dfs.ts +2 -2
  67. package/src/clustering/spectral.ts +2 -2
  68. package/src/index.ts +10 -0
  69. package/src/indexed/accelerator.ts +26 -2
  70. package/src/indexed/components.ts +8 -1
  71. package/src/indexed/hits.ts +126 -0
  72. package/src/indexed/index.ts +5 -7
  73. package/src/indexed/k-core.ts +138 -0
  74. package/src/indexed/katz.ts +88 -0
  75. package/src/indexed/louvain.ts +345 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"katz.js","sourceRoot":"","sources":["../../../src/indexed/katz.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type GraphSnapshot } from "@graphty/graph-format";
2
+ import { type LabelResult } from "./components.js";
3
+ /** Options of the index-based Louvain, matching the legacy `louvain`. @public */
4
+ export interface LouvainOptions {
5
+ /** Resolution gamma: above 1 favours smaller communities; default 1. */
6
+ readonly resolution?: number | undefined;
7
+ /** Cap on aggregation levels, and on node visits per node within a level; default 100. */
8
+ readonly maxIterations?: number | undefined;
9
+ /** Stop when a level improves modularity by less than this; default 1e-6. */
10
+ readonly tolerance?: number | undefined;
11
+ }
12
+ /** Result of the index-based Louvain: a partition plus the modularity it reaches. @public */
13
+ export interface LouvainResult extends LabelResult {
14
+ /** Modularity of the returned partition, at the requested resolution. */
15
+ readonly modularity: number;
16
+ /** Aggregation levels that improved the partition. */
17
+ readonly iterations: number;
18
+ }
19
+ /**
20
+ * Louvain community detection over a snapshot: local moving to the best modularity gain, then
21
+ * collapse each community into a node and repeat, until a level stops paying for itself.
22
+ *
23
+ * The legacy `louvain` switches implementation at 50 nodes (`useOptimized`) and its small-graph
24
+ * path has no aggregation phase at all, so this port is not move-for-move identical to either
25
+ * branch of it -- what it guarantees is a partition whose modularity is at least as high, measured
26
+ * by the same formula.
27
+ * @param s - An undirected snapshot
28
+ * @param o - Algorithm options
29
+ * @returns The partition, its modularity, and the number of levels that improved it
30
+ * @public
31
+ */
32
+ export declare function louvain(s: GraphSnapshot, o?: LouvainOptions): LouvainResult;
33
+ //# sourceMappingURL=louvain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"louvain.d.ts","sourceRoot":"","sources":["../../../src/indexed/louvain.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,uBAAuB,CAAC;AAE9E,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,iBAAiB,CAAC;AAE/D,iFAAiF;AACjF,MAAM,WAAW,cAAc;IAC3B,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,6EAA6E;IAC7E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC3C;AAED,6FAA6F;AAC7F,MAAM,WAAW,aAAc,SAAQ,WAAW;IAC9C,yEAAyE;IACzE,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC/B;AAyQD;;;;;;;;;;;;GAYG;AACH,wBAAgB,OAAO,CAAC,CAAC,EAAE,aAAa,EAAE,CAAC,GAAE,cAAmB,GAAG,aAAa,CA8C/E"}
@@ -0,0 +1,301 @@
1
+ import { renumberPartition } from "@graphty/graph-format";
2
+ import { withGroups } from "./components.js";
3
+ /**
4
+ * Level 0: the snapshot's own arcs, with the self-loop arcs moved into `loop`.
5
+ * @param s - An undirected snapshot
6
+ * @returns The first level
7
+ */
8
+ function buildLevel0(s) {
9
+ const n = s.nodeCount;
10
+ const rowPtr = new Uint32Array(n + 1);
11
+ for (let u = 0; u < n; u++) {
12
+ let kept = 0;
13
+ const end = s.rowPtr[u + 1];
14
+ for (let a = s.rowPtr[u]; a < end; a++) {
15
+ if (s.colIdx[a] !== u) {
16
+ kept++;
17
+ }
18
+ }
19
+ rowPtr[u + 1] = rowPtr[u] + kept;
20
+ }
21
+ const arcs = rowPtr[n];
22
+ const colIdx = new Uint32Array(arcs);
23
+ const w = new Float64Array(arcs);
24
+ const loop = new Float64Array(n);
25
+ const deg = new Float64Array(n);
26
+ let k = 0;
27
+ for (let u = 0; u < n; u++) {
28
+ let rowWeight = 0;
29
+ const end = s.rowPtr[u + 1];
30
+ for (let a = s.rowPtr[u]; a < end; a++) {
31
+ const v = s.colIdx[a];
32
+ const weight = s.weights === null ? 1 : s.weights[a];
33
+ if (v === u) {
34
+ loop[u] += weight;
35
+ }
36
+ else {
37
+ colIdx[k] = v;
38
+ w[k] = weight;
39
+ k++;
40
+ rowWeight += weight;
41
+ }
42
+ }
43
+ deg[u] = rowWeight + 2 * loop[u];
44
+ }
45
+ return { n, rowPtr, colIdx, w, loop, deg };
46
+ }
47
+ /**
48
+ * Modularity of a partition of one level: `sum_c in_c / 2m - gamma * (tot_c / 2m)^2`, with `in_c`
49
+ * counting each internal edge twice. This is the same quantity the legacy `calculateModularity`
50
+ * computes over a `Graph`, written per arc.
51
+ * @param level - The level the partition is over
52
+ * @param comm - Community of every node of that level
53
+ * @param count - Number of communities, so `comm` is within `[0, count)`
54
+ * @param resolution - Resolution gamma
55
+ * @param m2 - Twice the total edge weight
56
+ * @returns The modularity
57
+ */
58
+ function modularityOf(level, comm, count, resolution, m2) {
59
+ const inside = new Float64Array(count);
60
+ const tot = new Float64Array(count);
61
+ for (let u = 0; u < level.n; u++) {
62
+ const c = comm[u];
63
+ tot[c] += level.deg[u];
64
+ inside[c] += 2 * level.loop[u];
65
+ const end = level.rowPtr[u + 1];
66
+ for (let a = level.rowPtr[u]; a < end; a++) {
67
+ if (comm[level.colIdx[a]] === c) {
68
+ inside[c] += level.w[a];
69
+ }
70
+ }
71
+ }
72
+ let q = 0;
73
+ for (let c = 0; c < count; c++) {
74
+ const share = tot[c] / m2;
75
+ q += inside[c] / m2 - resolution * share * share;
76
+ }
77
+ return q;
78
+ }
79
+ /**
80
+ * Phase one: move each node to the neighbouring community with the largest modularity gain, until
81
+ * no move is left to make.
82
+ *
83
+ * Nodes wait in a queue rather than in repeated sweeps over all of them, which is what makes this
84
+ * affordable on a graph with no community structure: there the last moves are worth a millionth of
85
+ * a point each and go on for dozens of passes, and a pass costs every node and every arc whether or
86
+ * not anything can move. A node enters the queue when a neighbour leaves its community, so the tail
87
+ * of the run costs the handful of nodes that can still move. Same fixed point, and it is the rule
88
+ * networkx's `louvain_communities` follows.
89
+ * @param level - The level to optimise
90
+ * @param comm - Output: community of every node, overwritten with the singleton partition first
91
+ * @param resolution - Resolution gamma
92
+ * @param m2 - Twice the total edge weight
93
+ * @param maxVisitsPerNode - Cap on node visits, as a multiple of the node count
94
+ * @returns Whether any node moved
95
+ */
96
+ function localMove(level, comm, resolution, m2, maxVisitsPerNode) {
97
+ const { n } = level;
98
+ const tot = new Float64Array(n);
99
+ for (let u = 0; u < n; u++) {
100
+ comm[u] = u;
101
+ tot[u] = level.deg[u];
102
+ }
103
+ const acc = new Float64Array(n);
104
+ // A visit stamp rather than a clear pass: `mark` grows monotonically over the whole run, so a
105
+ // value left in acc by an earlier visit is never mistaken for this one's.
106
+ const stamp = new Int32Array(n).fill(-1);
107
+ const touched = new Uint32Array(n);
108
+ // A ring of n + 1 slots: `queued` keeps a node out of the queue twice, so n entries is the most
109
+ // that can be waiting.
110
+ const capacity = n + 1;
111
+ const queue = new Uint32Array(capacity);
112
+ const queued = new Uint8Array(n);
113
+ let head = 0;
114
+ let tail = n;
115
+ for (let u = 0; u < n; u++) {
116
+ queue[u] = u;
117
+ queued[u] = 1;
118
+ }
119
+ let mark = 0;
120
+ let visits = 0;
121
+ const maxVisits = maxVisitsPerNode * n;
122
+ let movedAny = false;
123
+ while (head !== tail && visits < maxVisits) {
124
+ const u = queue[head];
125
+ head = head + 1 === capacity ? 0 : head + 1;
126
+ queued[u] = 0;
127
+ visits++;
128
+ const cu = comm[u];
129
+ const ku = level.deg[u];
130
+ tot[cu] -= ku;
131
+ mark++;
132
+ let cnt = 0;
133
+ const end = level.rowPtr[u + 1];
134
+ for (let a = level.rowPtr[u]; a < end; a++) {
135
+ const c = comm[level.colIdx[a]];
136
+ if (stamp[c] !== mark) {
137
+ stamp[c] = mark;
138
+ acc[c] = 0;
139
+ touched[cnt++] = c;
140
+ }
141
+ acc[c] += level.w[a];
142
+ }
143
+ let best = cu;
144
+ let bestGain = (stamp[cu] === mark ? acc[cu] : 0) - (resolution * ku * tot[cu]) / m2;
145
+ for (let i = 0; i < cnt; i++) {
146
+ const c = touched[i];
147
+ if (c === cu) {
148
+ continue;
149
+ }
150
+ const gain = acc[c] - (resolution * ku * tot[c]) / m2;
151
+ if (gain > bestGain) {
152
+ bestGain = gain;
153
+ best = c;
154
+ }
155
+ }
156
+ tot[best] += ku;
157
+ if (best !== cu) {
158
+ comm[u] = best;
159
+ movedAny = true;
160
+ // Only a neighbour outside the community u just joined can gain from u's move.
161
+ for (let a = level.rowPtr[u]; a < end; a++) {
162
+ const v = level.colIdx[a];
163
+ if (queued[v] === 0 && comm[v] !== best) {
164
+ queued[v] = 1;
165
+ queue[tail] = v;
166
+ tail = tail + 1 === capacity ? 0 : tail + 1;
167
+ }
168
+ }
169
+ }
170
+ }
171
+ return movedAny;
172
+ }
173
+ /**
174
+ * Phase two: every community becomes one node, the edges between two communities merge into one
175
+ * arc, and the edges inside a community become that node's self-loop. Rows are NOT sorted by
176
+ * neighbour index; nothing downstream of here needs them to be.
177
+ * @param level - The level to collapse
178
+ * @param comm - Dense community of every node of that level
179
+ * @param count - Number of communities
180
+ * @returns The next level, with `count` nodes
181
+ */
182
+ function aggregate(level, comm, count) {
183
+ const start = new Uint32Array(count + 1);
184
+ for (let u = 0; u < level.n; u++) {
185
+ start[comm[u] + 1]++;
186
+ }
187
+ for (let c = 0; c < count; c++) {
188
+ start[c + 1] += start[c];
189
+ }
190
+ const members = new Uint32Array(level.n);
191
+ const fill = start.slice(0, count);
192
+ for (let u = 0; u < level.n; u++) {
193
+ members[fill[comm[u]]++] = u;
194
+ }
195
+ const rowPtr = new Uint32Array(count + 1);
196
+ const colIdx = new Uint32Array(level.colIdx.length);
197
+ const w = new Float64Array(level.colIdx.length);
198
+ const loop = new Float64Array(count);
199
+ const deg = new Float64Array(count);
200
+ const acc = new Float64Array(count);
201
+ const stamp = new Int32Array(count).fill(-1);
202
+ const touched = new Uint32Array(count);
203
+ let k = 0;
204
+ for (let c = 0; c < count; c++) {
205
+ rowPtr[c] = k;
206
+ let internalArcs = 0;
207
+ let cnt = 0;
208
+ for (let i = start[c]; i < start[c + 1]; i++) {
209
+ const u = members[i];
210
+ loop[c] += level.loop[u];
211
+ const end = level.rowPtr[u + 1];
212
+ for (let a = level.rowPtr[u]; a < end; a++) {
213
+ const other = comm[level.colIdx[a]];
214
+ if (other === c) {
215
+ internalArcs += level.w[a];
216
+ continue;
217
+ }
218
+ if (stamp[other] !== c) {
219
+ stamp[other] = c;
220
+ acc[other] = 0;
221
+ touched[cnt++] = other;
222
+ }
223
+ acc[other] += level.w[a];
224
+ }
225
+ }
226
+ // An edge between two members shows up as two arcs, one from each end.
227
+ loop[c] += internalArcs / 2;
228
+ let rowWeight = 0;
229
+ for (let i = 0; i < cnt; i++) {
230
+ const other = touched[i];
231
+ colIdx[k] = other;
232
+ w[k] = acc[other];
233
+ rowWeight += acc[other];
234
+ k++;
235
+ }
236
+ deg[c] = rowWeight + 2 * loop[c];
237
+ }
238
+ rowPtr[count] = k;
239
+ return { n: count, rowPtr, colIdx: colIdx.subarray(0, k), w: w.subarray(0, k), loop, deg };
240
+ }
241
+ /**
242
+ * Louvain community detection over a snapshot: local moving to the best modularity gain, then
243
+ * collapse each community into a node and repeat, until a level stops paying for itself.
244
+ *
245
+ * The legacy `louvain` switches implementation at 50 nodes (`useOptimized`) and its small-graph
246
+ * path has no aggregation phase at all, so this port is not move-for-move identical to either
247
+ * branch of it -- what it guarantees is a partition whose modularity is at least as high, measured
248
+ * by the same formula.
249
+ * @param s - An undirected snapshot
250
+ * @param o - Algorithm options
251
+ * @returns The partition, its modularity, and the number of levels that improved it
252
+ * @public
253
+ */
254
+ export function louvain(s, o = {}) {
255
+ if (s.directed) {
256
+ throw new Error("Louvain requires an undirected graph. Pass s.toUndirected().snapshot.");
257
+ }
258
+ const resolution = o.resolution ?? 1;
259
+ const maxIterations = o.maxIterations ?? 100;
260
+ const tolerance = o.tolerance ?? 1e-6;
261
+ const n0 = s.nodeCount;
262
+ const labels = new Uint32Array(n0);
263
+ for (let u = 0; u < n0; u++) {
264
+ labels[u] = u;
265
+ }
266
+ let level = buildLevel0(s);
267
+ let m2 = 0;
268
+ for (let u = 0; u < n0; u++) {
269
+ m2 += level.deg[u];
270
+ }
271
+ if (m2 === 0) {
272
+ // No edge weight to redistribute: every node is its own community and Q is 0 by definition,
273
+ // which is what the legacy `calculateModularity` also answers.
274
+ return { ...withGroups(labels, n0), modularity: 0, iterations: 0 };
275
+ }
276
+ let q = modularityOf(level, labels, n0, resolution, m2);
277
+ let iterations = 0;
278
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
279
+ const comm = new Uint32Array(level.n);
280
+ if (!localMove(level, comm, resolution, m2, maxIterations)) {
281
+ break;
282
+ }
283
+ const dense = renumberPartition(comm);
284
+ const { count } = dense;
285
+ const packed = dense.labels;
286
+ const newQ = modularityOf(level, packed, count, resolution, m2);
287
+ for (let u = 0; u < n0; u++) {
288
+ labels[u] = packed[labels[u]];
289
+ }
290
+ iterations = iteration + 1;
291
+ const gain = newQ - q;
292
+ q = newQ;
293
+ if (gain < tolerance || count === level.n) {
294
+ break;
295
+ }
296
+ level = aggregate(level, packed, count);
297
+ }
298
+ const final = renumberPartition(labels);
299
+ return { ...withGroups(final.labels, final.count), modularity: q, iterations };
300
+ }
301
+ //# sourceMappingURL=louvain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"louvain.js","sourceRoot":"","sources":["../../../src/indexed/louvain.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@graphty/algorithms",
3
- "version": "2.0.6",
3
+ "version": "2.1.0",
4
4
  "description": "Graph algorithms library for browser environments implemented in TypeScript",
5
5
  "author": "Adam Powers <apowers@ato.ms>",
6
6
  "main": "dist/algorithms.js",
@@ -68,13 +68,14 @@
68
68
  "@storybook/html-vite": "^9.0.11",
69
69
  "@storybook/test": "^8.6.14",
70
70
  "@types/benchmark": "^2.1.5",
71
+ "@vitest/browser-playwright": "4.1.11",
71
72
  "benchmark": "^2.1.4",
72
73
  "chromatic": "^11.0.0",
73
74
  "eslint": "^9.29.0",
74
75
  "eslint-plugin-simple-import-sort": "^12.1.1",
75
76
  "gh-pages": "^6.3.0",
76
77
  "globals": "^15.12.0",
77
- "happy-dom": "^18.0.1",
78
+ "happy-dom": "^20.8.9",
78
79
  "knip": "^5.61.3",
79
80
  "playwright": "^1.53.0",
80
81
  "semantic-release": "^25.0.2",
@@ -88,11 +89,11 @@
88
89
  "typescript-eslint": "^8.34.1",
89
90
  "vite": "^7.0.5",
90
91
  "vitepress": "^1.6.3",
91
- "vitest": "^3.2.4"
92
+ "vitest": "4.1.11"
92
93
  },
93
94
  "dependencies": {
94
95
  "typedfastbitset": "^0.6.1",
95
- "@graphty/graph-format": "^1.0.7"
96
+ "@graphty/graph-format": "^1.1.0"
96
97
  },
97
98
  "peerDependencies": {
98
99
  "@graphty/graph-format": "^1.0.0"
@@ -135,7 +136,6 @@
135
136
  "examples:run": "node examples/run-all-examples.js",
136
137
  "examples:html": "npm run build:bundle && vite --port ${PORT:?start it through servherd, which sets PORT} --strictPort",
137
138
  "serve": "npm run build:bundle && vite --port ${PORT:?start it through servherd, which sets PORT} --strictPort",
138
- "commit": "cz",
139
139
  "ready:commit": "npm run build && npm run lint && npm run test:all",
140
140
  "watch": "tsc --watch",
141
141
  "docs:api": "typedoc && node ../tools/sanitize-api-docs.js docs/api/generated",
@@ -102,7 +102,7 @@ function accumulateBetweenness(
102
102
  // Process nodes in reverse BFS order
103
103
  for (let i = stack.length - 1; i >= 0; i--) {
104
104
  const w = stack[i];
105
- if (!w) {
105
+ if (w === undefined) {
106
106
  continue;
107
107
  }
108
108
 
@@ -162,7 +162,7 @@ function accumulateEdgeBetweenness(
162
162
  // Process nodes in reverse BFS order
163
163
  for (let i = stack.length - 1; i >= 0; i--) {
164
164
  const w = stack[i];
165
- if (!w) {
165
+ if (w === undefined) {
166
166
  continue;
167
167
  }
168
168
 
@@ -226,7 +226,7 @@ function findAllShortestPaths(
226
226
  let queueIndex = 0;
227
227
  while (queueIndex < queue.length) {
228
228
  const current = queue[queueIndex++];
229
- if (!current) {
229
+ if (current === undefined) {
230
230
  continue;
231
231
  }
232
232
 
@@ -163,7 +163,7 @@ function getCandidatePairs(
163
163
  }
164
164
 
165
165
  // If not found, pick from in1
166
- if (!node1) {
166
+ if (node1 === null) {
167
167
  for (const [n] of state.in1) {
168
168
  if (!state.core1.has(n)) {
169
169
  node1 = n;
@@ -173,7 +173,7 @@ function getCandidatePairs(
173
173
  }
174
174
 
175
175
  // If still not found, pick any unmapped node
176
- if (!node1) {
176
+ if (node1 === null) {
177
177
  for (const n of nodes1) {
178
178
  if (!state.core1.has(n)) {
179
179
  node1 = n;
@@ -182,7 +182,7 @@ function getCandidatePairs(
182
182
  }
183
183
  }
184
184
 
185
- if (!node1) {
185
+ if (node1 === null) {
186
186
  return pairs;
187
187
  }
188
188
 
@@ -26,7 +26,7 @@ export function primMST(graph: Graph, startNode?: NodeId): MSTResult {
26
26
  }
27
27
 
28
28
  const start = startNode ?? nodes[0]?.id;
29
- if (!start) {
29
+ if (start === undefined) {
30
30
  return {
31
31
  edges: [],
32
32
  totalWeight: 0,
@@ -109,7 +109,7 @@ export function bellmanFord(graph: Graph, source: NodeId, options: BellmanFordOp
109
109
  for (const arc of arcs) {
110
110
  const u = arc.from;
111
111
  const v = arc.to;
112
- const {weight} = arc;
112
+ const { weight } = arc;
113
113
 
114
114
  const distanceU = distances.get(u);
115
115
  const distanceV = distances.get(v);
@@ -123,7 +123,7 @@ export function bellmanFord(graph: Graph, source: NodeId, options: BellmanFordOp
123
123
  updated = true;
124
124
 
125
125
  // Early termination if target reached
126
- if (options.target && v === options.target) {
126
+ if (options.target !== undefined && v === options.target) {
127
127
  break;
128
128
  }
129
129
  }
@@ -143,7 +143,7 @@ export function bellmanFord(graph: Graph, source: NodeId, options: BellmanFordOp
143
143
  for (const arc of arcs) {
144
144
  const u = arc.from;
145
145
  const v = arc.to;
146
- const {weight} = arc;
146
+ const { weight } = arc;
147
147
 
148
148
  const distanceU = distances.get(u);
149
149
  const distanceV = distances.get(v);
@@ -200,7 +200,7 @@ export class BidirectionalDijkstra {
200
200
  }
201
201
 
202
202
  private reconstructPath(): NodeId[] {
203
- if (!this.meetingNode) {
203
+ if (this.meetingNode === null) {
204
204
  return [];
205
205
  }
206
206
 
@@ -55,7 +55,7 @@ export function dijkstra(graph: Graph, source: NodeId, options: DijkstraOptions
55
55
  visited.add(currentNode);
56
56
 
57
57
  // Early termination if target reached
58
- if (options.target && currentNode === options.target) {
58
+ if (options.target !== undefined && currentNode === options.target) {
59
59
  break;
60
60
  }
61
61
 
@@ -89,7 +89,7 @@ function breadthFirstSearchStandard(graph: Graph, startNode: NodeId, options: Tr
89
89
  }
90
90
 
91
91
  // Early termination if target found
92
- if (options.targetNode && current.node === options.targetNode) {
92
+ if (options.targetNode !== undefined && current.node === options.targetNode) {
93
93
  break;
94
94
  }
95
95
 
@@ -409,7 +409,7 @@ export function isBipartite(graph: Graph): boolean {
409
409
 
410
410
  while (queue.length > 0) {
411
411
  const current = queue.shift();
412
- if (!current) {
412
+ if (current === undefined) {
413
413
  break;
414
414
  }
415
415
 
@@ -86,7 +86,7 @@ function dfsIterative(
86
86
  }
87
87
 
88
88
  // Early termination if target found
89
- if (options.targetNode && current.node === options.targetNode) {
89
+ if (options.targetNode !== undefined && current.node === options.targetNode) {
90
90
  break;
91
91
  }
92
92
 
@@ -135,7 +135,7 @@ function dfsRecursive(
135
135
  }
136
136
 
137
137
  // Early termination if target found
138
- if (options.targetNode && node === options.targetNode) {
138
+ if (options.targetNode !== undefined && node === options.targetNode) {
139
139
  return;
140
140
  }
141
141
  }
@@ -102,7 +102,7 @@ export function spectralClustering(graph: Graph, options: SpectralClusteringOpti
102
102
  for (let i = 0; i < nodeIds.length; i++) {
103
103
  const clusterId = kmeans.assignments[i] ?? 0;
104
104
  const nodeId = nodeIds[i];
105
- if (!nodeId) {
105
+ if (nodeId === undefined) {
106
106
  continue;
107
107
  }
108
108
 
@@ -151,7 +151,7 @@ function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
151
151
 
152
152
  for (let i = 0; i < n; i++) {
153
153
  const nodeId = nodeIds[i];
154
- if (!nodeId) {
154
+ if (nodeId === undefined) {
155
155
  continue;
156
156
  }
157
157
 
package/src/index.ts CHANGED
@@ -85,6 +85,16 @@ export type { BfsOptions, BfsResult } from "./indexed/bfs.js";
85
85
  export type { CommonNeighborsOptions } from "./indexed/common-neighbors.js";
86
86
  export type { LabelResult } from "./indexed/components.js";
87
87
  export type { SsspOptions, SsspResult } from "./indexed/dijkstra.js";
88
+ // The `Indexed` prefix, as on the PageRank pair below: the flat names HITSOptions / HITSResult,
89
+ // KatzCentralityOptions and LouvainOptions already belong to the legacy functions above, and two
90
+ // option types one capital letter apart in the same barrel is a trap, not a convenience.
91
+ export type { HitsOptions as IndexedHitsOptions, HitsResult as IndexedHitsResult } from "./indexed/hits.js";
92
+ export type { CorenessResult } from "./indexed/k-core.js";
93
+ export type { KatzOptions as IndexedKatzOptions, KatzResult as IndexedKatzResult } from "./indexed/katz.js";
94
+ export type {
95
+ LouvainOptions as IndexedLouvainOptions,
96
+ LouvainResult as IndexedLouvainResult,
97
+ } from "./indexed/louvain.js";
88
98
  export type { MstOptions, MstResult } from "./indexed/mst.js";
89
99
  // Aliased: the flat names are taken twice over (types/index.ts:96 and centrality/pagerank.ts:15).
90
100
  export type {
@@ -15,7 +15,10 @@ import type { F32, F64, GraphSnapshot, NumericVector, U32 } from "@graphty/graph
15
15
 
16
16
  import type { BfsOptions } from "./bfs.js";
17
17
  import { type SsspOptions, type SsspResult, walkPredArcs, walkPredEdges } from "./dijkstra.js";
18
+ import type { HitsOptions } from "./hits.js";
18
19
  import * as indexed from "./index.js";
20
+ import type { KatzOptions } from "./katz.js";
21
+ import type { LouvainOptions } from "./louvain.js";
19
22
  import type { MstOptions } from "./mst.js";
20
23
  import type { PageRankOptions } from "./pagerank.js";
21
24
 
@@ -157,8 +160,13 @@ export interface BetweennessAcceleratorOptions {
157
160
  * accelerator when it has the method and runs the CPU port otherwise, wrapped in `Promise.resolve`
158
161
  * so both paths are async and graphty-element's `async run()` adapters treat them alike.
159
162
  *
160
- * The list GROWS with the A2 ports -- each port PR adds its method. Today it carries the six whose
161
- * ports exist (plan departure DEP-8A-E).
163
+ * The list GROWS with the A2 ports -- each port PR adds its method (plan departure DEP-8A-E).
164
+ *
165
+ * The four newest methods take their port's own option type, which is WIDER than the
166
+ * `HitsOptionsLike` the accelerator side still declares: an accelerator therefore never sees Katz's
167
+ * `alpha` / `beta` or Louvain's `resolution`, and one that is handed them would answer a different
168
+ * question than the CPU port. Narrowing `AlgorithmAccelerator` is a change to the interface the GPU
169
+ * package implements, so it belongs to the pull request that lands a GPU Louvain or Katz.
162
170
  * @public
163
171
  */
164
172
  export interface AcceleratedAlgorithms {
@@ -169,6 +177,10 @@ export interface AcceleratedAlgorithms {
169
177
  connectedComponents(s: GraphSnapshot): Promise<LabelResultLike>;
170
178
  weaklyConnectedComponents(s: GraphSnapshot): Promise<LabelResultLike>;
171
179
  minimumSpanningTree(s: GraphSnapshot, options?: MstOptions): Promise<MstResultLike>;
180
+ kCoreDecomposition(s: GraphSnapshot): Promise<CorenessResultLike>;
181
+ katzCentrality(s: GraphSnapshot, options?: KatzOptions): Promise<ScoresResultLike>;
182
+ hits(s: GraphSnapshot, options?: HitsOptions): Promise<HitsResultLike>;
183
+ louvain(s: GraphSnapshot, options?: LouvainOptions): Promise<CommunityResultLike>;
172
184
  }
173
185
 
174
186
  /**
@@ -221,5 +233,17 @@ export function accelerated(acc: AlgorithmAccelerator | null | undefined): Accel
221
233
  acc?.minimumSpanningTree !== undefined
222
234
  ? acc.minimumSpanningTree(s, options)
223
235
  : Promise.resolve(indexed.kruskalMST(s, options)),
236
+ kCoreDecomposition: (s) =>
237
+ acc?.kCoreDecomposition !== undefined
238
+ ? acc.kCoreDecomposition(s)
239
+ : Promise.resolve(indexed.kCoreDecomposition(s)),
240
+ katzCentrality: (s, options) =>
241
+ acc?.katzCentrality !== undefined
242
+ ? acc.katzCentrality(s, options)
243
+ : Promise.resolve(indexed.katzCentrality(s, options)),
244
+ hits: (s, options) =>
245
+ acc?.hits !== undefined ? acc.hits(s, options) : Promise.resolve(indexed.hits(s, options)),
246
+ louvain: (s, options) =>
247
+ acc?.louvain !== undefined ? acc.louvain(s, options) : Promise.resolve(indexed.louvain(s, options)),
224
248
  };
225
249
  }