@graphty/algorithms 1.0.1 → 1.2.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 +1259 -61
- package/dist/algorithms.d.ts +2 -0
- package/dist/algorithms.d.ts.map +1 -0
- package/dist/algorithms.js +2 -0
- package/dist/algorithms.js.map +1 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +22 -7
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +7 -7
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +1 -1
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.js +113 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
- package/dist/src/algorithms/centrality/hits.d.ts +35 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/hits.js +146 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +6 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +3 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +30 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/katz.js +85 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -0
- package/dist/src/algorithms/centrality/pagerank.js +3 -3
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.js +297 -0
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
- package/dist/src/algorithms/community/index.d.ts +12 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -0
- package/dist/src/algorithms/community/index.js +12 -0
- package/dist/src/algorithms/community/index.js.map +1 -0
- package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
- package/dist/src/algorithms/community/label-propagation.js +307 -0
- package/dist/src/algorithms/community/label-propagation.js.map +1 -0
- package/dist/src/algorithms/community/leiden.d.ts +33 -0
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
- package/dist/src/algorithms/community/leiden.js +434 -0
- package/dist/src/algorithms/community/leiden.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +19 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain.js +219 -0
- package/dist/src/algorithms/community/louvain.js.map +1 -0
- package/dist/src/algorithms/components/connected.js +7 -7
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/index.d.ts +7 -0
- package/dist/src/algorithms/index.d.ts.map +1 -1
- package/dist/src/algorithms/index.js +14 -0
- package/dist/src/algorithms/index.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
- package/dist/src/algorithms/matching/bipartite.js +127 -0
- package/dist/src/algorithms/matching/bipartite.js.map +1 -0
- package/dist/src/algorithms/matching/index.d.ts +8 -0
- package/dist/src/algorithms/matching/index.d.ts.map +1 -0
- package/dist/src/algorithms/matching/index.js +6 -0
- package/dist/src/algorithms/matching/index.js.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
- package/dist/src/algorithms/matching/isomorphism.js +291 -0
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
- package/dist/src/algorithms/mst/index.d.ts +4 -0
- package/dist/src/algorithms/mst/index.d.ts.map +1 -0
- package/dist/src/algorithms/mst/index.js +3 -0
- package/dist/src/algorithms/mst/index.js.map +1 -0
- package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
- package/dist/src/algorithms/mst/kruskal.js +43 -0
- package/dist/src/algorithms/mst/kruskal.js.map +1 -0
- package/dist/src/algorithms/mst/prim.d.ts +5 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
- package/dist/src/algorithms/mst/prim.js +63 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
- package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js +1 -0
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +2 -2
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +11 -11
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +48 -0
- package/dist/src/clustering/hierarchical.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical.js +439 -0
- package/dist/src/clustering/hierarchical.js.map +1 -0
- package/dist/src/clustering/index.d.ts +7 -0
- package/dist/src/clustering/index.d.ts.map +1 -0
- package/dist/src/clustering/index.js +5 -0
- package/dist/src/clustering/index.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +63 -0
- package/dist/src/clustering/k-core.d.ts.map +1 -0
- package/dist/src/clustering/k-core.js +377 -0
- package/dist/src/clustering/k-core.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +36 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -0
- package/dist/src/clustering/mcl.js +343 -0
- package/dist/src/clustering/mcl.js.map +1 -0
- package/dist/src/clustering/spectral.d.ts +28 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -0
- package/dist/src/clustering/spectral.js +551 -0
- package/dist/src/clustering/spectral.js.map +1 -0
- package/dist/src/core/graph.d.ts +9 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +31 -11
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/priority-queue.js +0 -2
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.js +2 -5
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +53 -0
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
- package/dist/src/flow/ford-fulkerson.js +355 -0
- package/dist/src/flow/ford-fulkerson.js.map +1 -0
- package/dist/src/flow/index.d.ts +3 -0
- package/dist/src/flow/index.d.ts.map +1 -0
- package/dist/src/flow/index.js +3 -0
- package/dist/src/flow/index.js.map +1 -0
- package/dist/src/flow/min-cut.d.ts +49 -0
- package/dist/src/flow/min-cut.d.ts.map +1 -0
- package/dist/src/flow/min-cut.js +383 -0
- package/dist/src/flow/min-cut.js.map +1 -0
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
- package/dist/src/link-prediction/adamic-adar.js +256 -0
- package/dist/src/link-prediction/adamic-adar.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors.js +155 -0
- package/dist/src/link-prediction/common-neighbors.js.map +1 -0
- package/dist/src/link-prediction/index.d.ts +7 -0
- package/dist/src/link-prediction/index.d.ts.map +1 -0
- package/dist/src/link-prediction/index.js +6 -0
- package/dist/src/link-prediction/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +50 -0
- package/dist/src/pathfinding/astar.d.ts.map +1 -0
- package/dist/src/pathfinding/astar.js +180 -0
- package/dist/src/pathfinding/astar.js.map +1 -0
- package/dist/src/pathfinding/index.d.ts +3 -0
- package/dist/src/pathfinding/index.d.ts.map +1 -0
- package/dist/src/pathfinding/index.js +3 -0
- package/dist/src/pathfinding/index.js.map +1 -0
- package/dist/src/pathfinding/utils.d.ts +18 -0
- package/dist/src/pathfinding/utils.d.ts.map +1 -0
- package/dist/src/pathfinding/utils.js +68 -0
- package/dist/src/pathfinding/utils.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +83 -0
- package/dist/src/research/grsbm.d.ts.map +1 -0
- package/dist/src/research/grsbm.js +411 -0
- package/dist/src/research/grsbm.js.map +1 -0
- package/dist/src/research/index.d.ts +12 -0
- package/dist/src/research/index.d.ts.map +1 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/index.js.map +1 -0
- package/dist/src/research/sync.d.ts +49 -0
- package/dist/src/research/sync.d.ts.map +1 -0
- package/dist/src/research/sync.js +342 -0
- package/dist/src/research/sync.js.map +1 -0
- package/dist/src/research/terahac.d.ts +63 -0
- package/dist/src/research/terahac.d.ts.map +1 -0
- package/dist/src/research/terahac.js +375 -0
- package/dist/src/research/terahac.js.map +1 -0
- package/dist/src/types/index.d.ts +9 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/graphNode.d.ts +8 -0
- package/dist/src/utils/graphNode.d.ts.map +1 -0
- package/dist/src/utils/graphNode.js +2 -0
- package/dist/src/utils/graphNode.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +21 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -0
- package/dist/src/utils/priorityQueue.js +101 -0
- package/dist/src/utils/priorityQueue.js.map +1 -0
- package/package.json +13 -2
- package/src/algorithms/centrality/betweenness.ts +25 -7
- package/src/algorithms/centrality/closeness.ts +7 -7
- package/src/algorithms/centrality/degree.ts +1 -1
- package/src/algorithms/centrality/eigenvector.ts +162 -0
- package/src/algorithms/centrality/hits.ts +210 -0
- package/src/algorithms/centrality/index.ts +6 -0
- package/src/algorithms/centrality/katz.ts +139 -0
- package/src/algorithms/centrality/pagerank.ts +3 -3
- package/src/algorithms/community/girvan-newman.ts +372 -0
- package/src/algorithms/community/index.ts +13 -0
- package/src/algorithms/community/label-propagation.ts +392 -0
- package/src/algorithms/community/leiden.ts +572 -0
- package/src/algorithms/community/louvain.ts +298 -0
- package/src/algorithms/components/connected.ts +7 -7
- package/src/algorithms/index.ts +21 -0
- package/src/algorithms/matching/bipartite.ts +179 -0
- package/src/algorithms/matching/index.ts +8 -0
- package/src/algorithms/matching/isomorphism.ts +392 -0
- package/src/algorithms/mst/index.ts +4 -0
- package/src/algorithms/mst/kruskal.ts +61 -0
- package/src/algorithms/mst/prim.ts +80 -0
- package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
- package/src/algorithms/shortest-path/dijkstra.ts +5 -5
- package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
- package/src/algorithms/shortest-path/index.ts +2 -0
- package/src/algorithms/traversal/bfs.ts +2 -2
- package/src/algorithms/traversal/dfs.ts +11 -11
- package/src/clustering/hierarchical.ts +555 -0
- package/src/clustering/index.ts +6 -0
- package/src/clustering/k-core.ts +460 -0
- package/src/clustering/mcl.ts +454 -0
- package/src/clustering/spectral.ts +672 -0
- package/src/core/graph.ts +34 -6
- package/src/data-structures/union-find.ts +2 -2
- package/src/flow/ford-fulkerson.ts +459 -0
- package/src/flow/index.ts +2 -0
- package/src/flow/min-cut.ts +482 -0
- package/src/index.ts +5 -0
- package/src/link-prediction/adamic-adar.ts +353 -0
- package/src/link-prediction/common-neighbors.ts +252 -0
- package/src/link-prediction/index.ts +20 -0
- package/src/pathfinding/astar.ts +229 -0
- package/src/pathfinding/index.ts +2 -0
- package/src/pathfinding/utils.ts +87 -0
- package/src/research/grsbm.ts +589 -0
- package/src/research/index.ts +17 -0
- package/src/research/sync.ts +469 -0
- package/src/research/terahac.ts +506 -0
- package/src/types/index.ts +11 -0
- package/src/utils/graphNode.ts +7 -0
- package/src/utils/priorityQueue.ts +120 -0
- package/dist/test/browser/basic.test.d.ts +0 -2
- package/dist/test/browser/basic.test.d.ts.map +0 -1
- package/dist/test/browser/basic.test.js +0 -70
- package/dist/test/browser/basic.test.js.map +0 -1
- package/dist/test/unit/bellman-ford.test.d.ts +0 -2
- package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
- package/dist/test/unit/bellman-ford.test.js +0 -231
- package/dist/test/unit/bellman-ford.test.js.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
- package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/betweenness-centrality.test.js +0 -311
- package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
- package/dist/test/unit/bfs.test.d.ts +0 -2
- package/dist/test/unit/bfs.test.d.ts.map +0 -1
- package/dist/test/unit/bfs.test.js +0 -277
- package/dist/test/unit/bfs.test.js.map +0 -1
- package/dist/test/unit/closeness.test.d.ts +0 -2
- package/dist/test/unit/closeness.test.d.ts.map +0 -1
- package/dist/test/unit/closeness.test.js +0 -266
- package/dist/test/unit/closeness.test.js.map +0 -1
- package/dist/test/unit/connected-components.test.d.ts +0 -2
- package/dist/test/unit/connected-components.test.d.ts.map +0 -1
- package/dist/test/unit/connected-components.test.js +0 -355
- package/dist/test/unit/connected-components.test.js.map +0 -1
- package/dist/test/unit/degree-centrality.test.d.ts +0 -2
- package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
- package/dist/test/unit/degree-centrality.test.js +0 -260
- package/dist/test/unit/degree-centrality.test.js.map +0 -1
- package/dist/test/unit/dfs.test.d.ts +0 -2
- package/dist/test/unit/dfs.test.d.ts.map +0 -1
- package/dist/test/unit/dfs.test.js +0 -350
- package/dist/test/unit/dfs.test.js.map +0 -1
- package/dist/test/unit/dijkstra.test.d.ts +0 -2
- package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
- package/dist/test/unit/dijkstra.test.js +0 -311
- package/dist/test/unit/dijkstra.test.js.map +0 -1
- package/dist/test/unit/graph.test.d.ts +0 -2
- package/dist/test/unit/graph.test.d.ts.map +0 -1
- package/dist/test/unit/graph.test.js +0 -175
- package/dist/test/unit/graph.test.js.map +0 -1
- package/dist/test/unit/pagerank.test.d.ts +0 -2
- package/dist/test/unit/pagerank.test.d.ts.map +0 -1
- package/dist/test/unit/pagerank.test.js +0 -281
- package/dist/test/unit/pagerank.test.js.map +0 -1
- package/dist/test/unit/priority-queue.test.d.ts +0 -2
- package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
- package/dist/test/unit/priority-queue.test.js +0 -189
- package/dist/test/unit/priority-queue.test.js.map +0 -1
- package/dist/test/unit/union-find.test.d.ts +0 -2
- package/dist/test/unit/union-find.test.d.ts.map +0 -1
- package/dist/test/unit/union-find.test.js +0 -245
- package/dist/test/unit/union-find.test.js.map +0 -1
|
@@ -67,7 +67,7 @@ export function betweennessCentrality(
|
|
|
67
67
|
|
|
68
68
|
stack.push(current);
|
|
69
69
|
|
|
70
|
-
for (const neighbor of graph.neighbors(current)) {
|
|
70
|
+
for (const neighbor of Array.from(graph.neighbors(current))) {
|
|
71
71
|
const currentDistance = distance.get(current);
|
|
72
72
|
let neighborDistance = distance.get(neighbor);
|
|
73
73
|
|
|
@@ -112,7 +112,17 @@ export function betweennessCentrality(
|
|
|
112
112
|
const vDelta = delta.get(v) ?? 0;
|
|
113
113
|
|
|
114
114
|
if (vSigma > 0 && wSigma > 0) {
|
|
115
|
-
|
|
115
|
+
let contribution = (vSigma / wSigma) * (1 + wDelta);
|
|
116
|
+
|
|
117
|
+
// Apply endpoints option: when false, exclude endpoint contributions
|
|
118
|
+
if (!options.endpoints) {
|
|
119
|
+
// For standard betweenness, don't count paths that only involve endpoints
|
|
120
|
+
const isTargetEndpoint = predecessors.get(w)?.length === 0 && w !== source;
|
|
121
|
+
if (isTargetEndpoint) {
|
|
122
|
+
contribution = 0; // Exclude endpoint contributions
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
116
126
|
delta.set(v, vDelta + contribution);
|
|
117
127
|
}
|
|
118
128
|
}
|
|
@@ -187,7 +197,7 @@ export function edgeBetweennessCentrality(
|
|
|
187
197
|
const edgeCentrality = new Map<string, number>();
|
|
188
198
|
|
|
189
199
|
// Initialize edge centrality scores
|
|
190
|
-
for (const edge of graph.edges()) {
|
|
200
|
+
for (const edge of Array.from(graph.edges())) {
|
|
191
201
|
const edgeKey = `${String(edge.source)}-${String(edge.target)}`;
|
|
192
202
|
edgeCentrality.set(edgeKey, 0);
|
|
193
203
|
}
|
|
@@ -222,7 +232,7 @@ export function edgeBetweennessCentrality(
|
|
|
222
232
|
|
|
223
233
|
stack.push(current);
|
|
224
234
|
|
|
225
|
-
for (const neighbor of graph.neighbors(current)) {
|
|
235
|
+
for (const neighbor of Array.from(graph.neighbors(current))) {
|
|
226
236
|
const currentDistance = distance.get(current);
|
|
227
237
|
let neighborDistance = distance.get(neighbor);
|
|
228
238
|
|
|
@@ -264,7 +274,15 @@ export function edgeBetweennessCentrality(
|
|
|
264
274
|
const vSigma = sigma.get(v) ?? 0;
|
|
265
275
|
|
|
266
276
|
if (vSigma > 0 && wSigma > 0) {
|
|
267
|
-
|
|
277
|
+
let edgeContribution = vSigma / wSigma * (1 + wDelta);
|
|
278
|
+
|
|
279
|
+
// Apply endpoints option for edge betweenness
|
|
280
|
+
if (!options.endpoints) {
|
|
281
|
+
const isTargetEndpoint = predecessors.get(w)?.length === 0 && w !== source;
|
|
282
|
+
if (isTargetEndpoint) {
|
|
283
|
+
edgeContribution = 0; // Exclude endpoint contributions
|
|
284
|
+
}
|
|
285
|
+
}
|
|
268
286
|
|
|
269
287
|
// Update edge centrality
|
|
270
288
|
const edgeKey = `${String(v)}-${String(w)}`;
|
|
@@ -281,7 +299,7 @@ export function edgeBetweennessCentrality(
|
|
|
281
299
|
|
|
282
300
|
// For undirected graphs, divide by 2 (each shortest path is counted twice)
|
|
283
301
|
if (!graph.isDirected) {
|
|
284
|
-
for (const [edgeKey, centrality] of edgeCentrality) {
|
|
302
|
+
for (const [edgeKey, centrality] of Array.from(edgeCentrality)) {
|
|
285
303
|
edgeCentrality.set(edgeKey, centrality / 2);
|
|
286
304
|
}
|
|
287
305
|
}
|
|
@@ -292,7 +310,7 @@ export function edgeBetweennessCentrality(
|
|
|
292
310
|
const normalizationFactor = graph.isDirected ? (n - 1) * (n - 2) : ((n - 1) * (n - 2)) / 2;
|
|
293
311
|
|
|
294
312
|
if (normalizationFactor > 0) {
|
|
295
|
-
for (const [edgeKey, centrality] of edgeCentrality) {
|
|
313
|
+
for (const [edgeKey, centrality] of Array.from(edgeCentrality)) {
|
|
296
314
|
edgeCentrality.set(edgeKey, centrality / normalizationFactor);
|
|
297
315
|
}
|
|
298
316
|
}
|
|
@@ -66,7 +66,7 @@ export function nodeClosenessCentrality(
|
|
|
66
66
|
|
|
67
67
|
if (options.harmonic) {
|
|
68
68
|
// Harmonic centrality: sum of reciprocals of distances
|
|
69
|
-
for (const [targetNode, distance] of distances) {
|
|
69
|
+
for (const [targetNode, distance] of Array.from(distances)) {
|
|
70
70
|
if (targetNode !== node && distance > 0) {
|
|
71
71
|
centrality += 1 / distance;
|
|
72
72
|
}
|
|
@@ -76,7 +76,7 @@ export function nodeClosenessCentrality(
|
|
|
76
76
|
let totalDistance = 0;
|
|
77
77
|
let reachableNodes = 0;
|
|
78
78
|
|
|
79
|
-
for (const [targetNode, distance] of distances) {
|
|
79
|
+
for (const [targetNode, distance] of Array.from(distances)) {
|
|
80
80
|
if (targetNode !== node) {
|
|
81
81
|
totalDistance += distance;
|
|
82
82
|
reachableNodes++;
|
|
@@ -134,7 +134,7 @@ function singleSourceShortestPathLengths(
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
// Explore neighbors
|
|
137
|
-
for (const neighbor of graph.neighbors(current.node)) {
|
|
137
|
+
for (const neighbor of Array.from(graph.neighbors(current.node))) {
|
|
138
138
|
if (!visited.has(neighbor)) {
|
|
139
139
|
const newDistance = current.distance + 1;
|
|
140
140
|
|
|
@@ -192,7 +192,7 @@ export function nodeWeightedClosenessCentrality(
|
|
|
192
192
|
|
|
193
193
|
if (options.harmonic) {
|
|
194
194
|
// Harmonic centrality: sum of reciprocals of distances
|
|
195
|
-
for (const [targetNode, distance] of distances) {
|
|
195
|
+
for (const [targetNode, distance] of Array.from(distances)) {
|
|
196
196
|
if (targetNode !== node && distance > 0 && distance < Infinity) {
|
|
197
197
|
centrality += 1 / distance;
|
|
198
198
|
}
|
|
@@ -202,7 +202,7 @@ export function nodeWeightedClosenessCentrality(
|
|
|
202
202
|
let totalDistance = 0;
|
|
203
203
|
let reachableNodes = 0;
|
|
204
204
|
|
|
205
|
-
for (const [targetNode, distance] of distances) {
|
|
205
|
+
for (const [targetNode, distance] of Array.from(distances)) {
|
|
206
206
|
if (targetNode !== node && distance < Infinity) {
|
|
207
207
|
totalDistance += distance;
|
|
208
208
|
reachableNodes++;
|
|
@@ -244,7 +244,7 @@ function dijkstraDistances(
|
|
|
244
244
|
const pq: {node: NodeId, distance: number}[] = [];
|
|
245
245
|
|
|
246
246
|
// Initialize distances
|
|
247
|
-
for (const node of graph.nodes()) {
|
|
247
|
+
for (const node of Array.from(graph.nodes())) {
|
|
248
248
|
distances.set(node.id, node.id === source ? 0 : Infinity);
|
|
249
249
|
}
|
|
250
250
|
|
|
@@ -267,7 +267,7 @@ function dijkstraDistances(
|
|
|
267
267
|
}
|
|
268
268
|
|
|
269
269
|
// Explore neighbors
|
|
270
|
-
for (const neighbor of graph.neighbors(current.node)) {
|
|
270
|
+
for (const neighbor of Array.from(graph.neighbors(current.node))) {
|
|
271
271
|
if (visited.has(neighbor)) {
|
|
272
272
|
continue;
|
|
273
273
|
}
|
|
@@ -21,7 +21,7 @@ export function degreeCentrality(
|
|
|
21
21
|
// Calculate normalization factor
|
|
22
22
|
const maxPossibleDegree = graph.isDirected ? nodeCount - 1 : nodeCount - 1;
|
|
23
23
|
|
|
24
|
-
for (const node of graph.nodes()) {
|
|
24
|
+
for (const node of Array.from(graph.nodes())) {
|
|
25
25
|
let degree: number;
|
|
26
26
|
|
|
27
27
|
if (graph.isDirected && options.mode) {
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {CentralityOptions, CentralityResult} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Eigenvector centrality implementation
|
|
6
|
+
*
|
|
7
|
+
* Measures the influence of a node based on the influence of its neighbors.
|
|
8
|
+
* A node has high eigenvector centrality if it is connected to nodes
|
|
9
|
+
* that themselves have high eigenvector centrality.
|
|
10
|
+
*
|
|
11
|
+
* Time complexity: O(V + E) per iteration
|
|
12
|
+
* Space complexity: O(V)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface EigenvectorCentralityOptions extends CentralityOptions {
|
|
16
|
+
maxIterations?: number; // Maximum iterations (default: 100)
|
|
17
|
+
tolerance?: number; // Convergence tolerance (default: 1e-6)
|
|
18
|
+
startVector?: Map<string, number>; // Initial vector (optional)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Calculate eigenvector centrality for all nodes in the graph
|
|
23
|
+
* Uses the power iteration method to find the dominant eigenvector
|
|
24
|
+
*/
|
|
25
|
+
export function eigenvectorCentrality(
|
|
26
|
+
graph: Graph,
|
|
27
|
+
options: EigenvectorCentralityOptions = {},
|
|
28
|
+
): CentralityResult {
|
|
29
|
+
const {
|
|
30
|
+
maxIterations = 100,
|
|
31
|
+
tolerance = 1e-6,
|
|
32
|
+
normalized = true,
|
|
33
|
+
startVector,
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
const centrality: CentralityResult = {};
|
|
37
|
+
const nodes = Array.from(graph.nodes());
|
|
38
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
39
|
+
|
|
40
|
+
if (nodeIds.length === 0) {
|
|
41
|
+
return centrality;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Initialize the eigenvector
|
|
45
|
+
let currentVector = new Map<string, number>();
|
|
46
|
+
let previousVector = new Map<string, number>();
|
|
47
|
+
|
|
48
|
+
if (startVector) {
|
|
49
|
+
// Use provided start vector
|
|
50
|
+
for (const nodeId of nodeIds) {
|
|
51
|
+
const key = nodeId.toString();
|
|
52
|
+
currentVector.set(key, startVector.get(key) ?? 1.0 / Math.sqrt(nodeIds.length));
|
|
53
|
+
}
|
|
54
|
+
} else {
|
|
55
|
+
// Initialize with uniform distribution
|
|
56
|
+
const initialValue = 1.0 / Math.sqrt(nodeIds.length);
|
|
57
|
+
for (const nodeId of nodeIds) {
|
|
58
|
+
currentVector.set(nodeId.toString(), initialValue);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Power iteration
|
|
63
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
64
|
+
previousVector = new Map(currentVector);
|
|
65
|
+
currentVector = new Map();
|
|
66
|
+
|
|
67
|
+
// Update each node's centrality based on neighbors
|
|
68
|
+
for (const nodeId of nodeIds) {
|
|
69
|
+
let sum = 0;
|
|
70
|
+
const neighbors = Array.from(graph.neighbors(nodeId));
|
|
71
|
+
|
|
72
|
+
for (const neighbor of neighbors) {
|
|
73
|
+
const neighborKey = neighbor.toString();
|
|
74
|
+
const prevValue = previousVector.get(neighborKey);
|
|
75
|
+
sum += prevValue ?? 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
currentVector.set(nodeId.toString(), sum);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Normalize the vector
|
|
82
|
+
let norm = 0;
|
|
83
|
+
for (const value of Array.from(currentVector.values())) {
|
|
84
|
+
norm += value * value;
|
|
85
|
+
}
|
|
86
|
+
norm = Math.sqrt(norm);
|
|
87
|
+
|
|
88
|
+
if (norm === 0) {
|
|
89
|
+
// Graph has no edges or is disconnected
|
|
90
|
+
for (const nodeId of nodeIds) {
|
|
91
|
+
centrality[nodeId.toString()] = 0;
|
|
92
|
+
}
|
|
93
|
+
return centrality;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Normalize the vector
|
|
97
|
+
for (const [nodeId, value] of Array.from(currentVector)) {
|
|
98
|
+
currentVector.set(nodeId, value / norm);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check for convergence
|
|
102
|
+
let maxDiff = 0;
|
|
103
|
+
for (const [nodeId, value] of Array.from(currentVector)) {
|
|
104
|
+
const prevValue = previousVector.get(nodeId) ?? 0;
|
|
105
|
+
const diff = Math.abs(value - prevValue);
|
|
106
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (maxDiff < tolerance) {
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Prepare results
|
|
115
|
+
for (const [nodeId, value] of Array.from(currentVector)) {
|
|
116
|
+
centrality[nodeId] = value;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Additional normalization if requested (normalize to [0,1] range)
|
|
120
|
+
if (normalized) {
|
|
121
|
+
let maxValue = 0;
|
|
122
|
+
let minValue = Number.POSITIVE_INFINITY;
|
|
123
|
+
|
|
124
|
+
for (const value of Object.values(centrality)) {
|
|
125
|
+
maxValue = Math.max(maxValue, value);
|
|
126
|
+
minValue = Math.min(minValue, value);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const range = maxValue - minValue;
|
|
130
|
+
if (range > 0) {
|
|
131
|
+
for (const nodeId of Object.keys(centrality)) {
|
|
132
|
+
const centralityValue = centrality[nodeId];
|
|
133
|
+
if (centralityValue !== undefined) {
|
|
134
|
+
centrality[nodeId] = (centralityValue - minValue) / range;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
// All values are the same, set to 1
|
|
139
|
+
for (const nodeId of Object.keys(centrality)) {
|
|
140
|
+
centrality[nodeId] = maxValue > 0 ? 1 : 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return centrality;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Calculate eigenvector centrality for a specific node
|
|
150
|
+
*/
|
|
151
|
+
export function nodeEigenvectorCentrality(
|
|
152
|
+
graph: Graph,
|
|
153
|
+
nodeId: string | number,
|
|
154
|
+
options: EigenvectorCentralityOptions = {},
|
|
155
|
+
): number {
|
|
156
|
+
if (!graph.hasNode(nodeId)) {
|
|
157
|
+
throw new Error(`Node ${String(nodeId)} not found in graph`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const centrality = eigenvectorCentrality(graph, options);
|
|
161
|
+
return centrality[nodeId.toString()] ?? 0;
|
|
162
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {CentralityOptions} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* HITS (Hyperlink-Induced Topic Search) algorithm implementation
|
|
6
|
+
*
|
|
7
|
+
* Identifies hub and authority scores for nodes in a graph.
|
|
8
|
+
* - Authorities: nodes with valuable information (pointed to by hubs)
|
|
9
|
+
* - Hubs: nodes that point to many authorities
|
|
10
|
+
*
|
|
11
|
+
* Originally designed for web page ranking but applicable to any directed network.
|
|
12
|
+
*
|
|
13
|
+
* Time complexity: O(V + E) per iteration
|
|
14
|
+
* Space complexity: O(V)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface HITSResult {
|
|
18
|
+
hubs: Record<string, number>;
|
|
19
|
+
authorities: Record<string, number>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface HITSOptions extends CentralityOptions {
|
|
23
|
+
maxIterations?: number; // Maximum iterations (default: 100)
|
|
24
|
+
tolerance?: number; // Convergence tolerance (default: 1e-6)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Calculate HITS hub and authority scores for all nodes in the graph
|
|
29
|
+
* Uses iterative method with normalization
|
|
30
|
+
*/
|
|
31
|
+
export function hits(
|
|
32
|
+
graph: Graph,
|
|
33
|
+
options: HITSOptions = {},
|
|
34
|
+
): HITSResult {
|
|
35
|
+
const {
|
|
36
|
+
maxIterations = 100,
|
|
37
|
+
tolerance = 1e-6,
|
|
38
|
+
normalized = true,
|
|
39
|
+
} = options;
|
|
40
|
+
|
|
41
|
+
const hubs: Record<string, number> = {};
|
|
42
|
+
const authorities: Record<string, number> = {};
|
|
43
|
+
|
|
44
|
+
const nodes = Array.from(graph.nodes());
|
|
45
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
46
|
+
|
|
47
|
+
if (nodeIds.length === 0) {
|
|
48
|
+
return {hubs, authorities};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initialize scores
|
|
52
|
+
let currentHubs = new Map<string, number>();
|
|
53
|
+
let currentAuthorities = new Map<string, number>();
|
|
54
|
+
let previousHubs = new Map<string, number>();
|
|
55
|
+
let previousAuthorities = new Map<string, number>();
|
|
56
|
+
|
|
57
|
+
// Start with uniform distribution
|
|
58
|
+
const initialValue = 1.0 / Math.sqrt(nodeIds.length);
|
|
59
|
+
for (const nodeId of nodeIds) {
|
|
60
|
+
const key = nodeId.toString();
|
|
61
|
+
currentHubs.set(key, initialValue);
|
|
62
|
+
currentAuthorities.set(key, initialValue);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Iterative computation
|
|
66
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
67
|
+
previousHubs = new Map(currentHubs);
|
|
68
|
+
previousAuthorities = new Map(currentAuthorities);
|
|
69
|
+
|
|
70
|
+
// Update authority scores
|
|
71
|
+
// Authority score = sum of hub scores of nodes pointing to it
|
|
72
|
+
const newAuthorities = new Map<string, number>();
|
|
73
|
+
for (const nodeId of nodeIds) {
|
|
74
|
+
let authorityScore = 0;
|
|
75
|
+
const inNeighbors = Array.from(graph.inNeighbors(nodeId));
|
|
76
|
+
|
|
77
|
+
for (const inNeighbor of inNeighbors) {
|
|
78
|
+
const neighborKey = inNeighbor.toString();
|
|
79
|
+
authorityScore += previousHubs.get(neighborKey) ?? 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
newAuthorities.set(nodeId.toString(), authorityScore);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Update hub scores
|
|
86
|
+
// Hub score = sum of authority scores of nodes it points to
|
|
87
|
+
const newHubs = new Map<string, number>();
|
|
88
|
+
for (const nodeId of nodeIds) {
|
|
89
|
+
let hubScore = 0;
|
|
90
|
+
const outNeighbors = Array.from(graph.outNeighbors(nodeId));
|
|
91
|
+
|
|
92
|
+
for (const outNeighbor of outNeighbors) {
|
|
93
|
+
const neighborKey = outNeighbor.toString();
|
|
94
|
+
hubScore += previousAuthorities.get(neighborKey) ?? 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
newHubs.set(nodeId.toString(), hubScore);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Normalize authority scores
|
|
101
|
+
let authNorm = 0;
|
|
102
|
+
for (const value of Array.from(newAuthorities.values())) {
|
|
103
|
+
authNorm += value * value;
|
|
104
|
+
}
|
|
105
|
+
authNorm = Math.sqrt(authNorm);
|
|
106
|
+
|
|
107
|
+
if (authNorm > 0) {
|
|
108
|
+
for (const [nodeId, value] of Array.from(newAuthorities)) {
|
|
109
|
+
newAuthorities.set(nodeId, value / authNorm);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Normalize hub scores
|
|
114
|
+
let hubNorm = 0;
|
|
115
|
+
for (const value of Array.from(newHubs.values())) {
|
|
116
|
+
hubNorm += value * value;
|
|
117
|
+
}
|
|
118
|
+
hubNorm = Math.sqrt(hubNorm);
|
|
119
|
+
|
|
120
|
+
if (hubNorm > 0) {
|
|
121
|
+
for (const [nodeId, value] of Array.from(newHubs)) {
|
|
122
|
+
newHubs.set(nodeId, value / hubNorm);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
currentAuthorities = newAuthorities;
|
|
127
|
+
currentHubs = newHubs;
|
|
128
|
+
|
|
129
|
+
// Check for convergence
|
|
130
|
+
let maxDiff = 0;
|
|
131
|
+
for (const [nodeId, value] of currentHubs) {
|
|
132
|
+
const prevValue = previousHubs.get(nodeId) ?? 0;
|
|
133
|
+
const diff = Math.abs(value - prevValue);
|
|
134
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
135
|
+
}
|
|
136
|
+
for (const [nodeId, value] of currentAuthorities) {
|
|
137
|
+
const prevValue = previousAuthorities.get(nodeId) ?? 0;
|
|
138
|
+
const diff = Math.abs(value - prevValue);
|
|
139
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (maxDiff < tolerance) {
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Prepare results
|
|
148
|
+
for (const [nodeId, value] of currentHubs) {
|
|
149
|
+
hubs[nodeId] = value;
|
|
150
|
+
}
|
|
151
|
+
for (const [nodeId, value] of currentAuthorities) {
|
|
152
|
+
authorities[nodeId] = value;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// The algorithm already normalizes to unit L2 norm during iterations
|
|
156
|
+
// Additional max normalization is only applied if explicitly requested
|
|
157
|
+
if (!normalized) {
|
|
158
|
+
// If normalization is explicitly disabled, normalize to [0, 1] range
|
|
159
|
+
let maxHub = 0;
|
|
160
|
+
let maxAuth = 0;
|
|
161
|
+
|
|
162
|
+
for (const value of Object.values(hubs)) {
|
|
163
|
+
maxHub = Math.max(maxHub, value);
|
|
164
|
+
}
|
|
165
|
+
for (const value of Object.values(authorities)) {
|
|
166
|
+
maxAuth = Math.max(maxAuth, value);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (maxHub > 0) {
|
|
170
|
+
for (const nodeId of Object.keys(hubs)) {
|
|
171
|
+
const hubValue = hubs[nodeId];
|
|
172
|
+
if (hubValue !== undefined) {
|
|
173
|
+
hubs[nodeId] = hubValue / maxHub;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (maxAuth > 0) {
|
|
179
|
+
for (const nodeId of Object.keys(authorities)) {
|
|
180
|
+
const authValue = authorities[nodeId];
|
|
181
|
+
if (authValue !== undefined) {
|
|
182
|
+
authorities[nodeId] = authValue / maxAuth;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {hubs, authorities};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Calculate HITS scores for a specific node
|
|
193
|
+
*/
|
|
194
|
+
export function nodeHITS(
|
|
195
|
+
graph: Graph,
|
|
196
|
+
nodeId: string | number,
|
|
197
|
+
options: HITSOptions = {},
|
|
198
|
+
): {hub: number, authority: number} {
|
|
199
|
+
if (!graph.hasNode(nodeId)) {
|
|
200
|
+
throw new Error(`Node ${String(nodeId)} not found in graph`);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const result = hits(graph, options);
|
|
204
|
+
const key = nodeId.toString();
|
|
205
|
+
|
|
206
|
+
return {
|
|
207
|
+
hub: result.hubs[key] ?? 0,
|
|
208
|
+
authority: result.authorities[key] ?? 0,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
@@ -7,5 +7,11 @@ export {betweennessCentrality, edgeBetweennessCentrality, nodeBetweennessCentral
|
|
|
7
7
|
export type {ClosenessCentralityOptions} from "./closeness.js";
|
|
8
8
|
export {closenessCentrality, nodeClosenessCentrality, nodeWeightedClosenessCentrality, weightedClosenessCentrality} from "./closeness.js";
|
|
9
9
|
export {degreeCentrality, nodeDegreeCentrality} from "./degree.js";
|
|
10
|
+
export type {EigenvectorCentralityOptions} from "./eigenvector.js";
|
|
11
|
+
export {eigenvectorCentrality, nodeEigenvectorCentrality} from "./eigenvector.js";
|
|
12
|
+
export type {HITSOptions, HITSResult} from "./hits.js";
|
|
13
|
+
export {hits, nodeHITS} from "./hits.js";
|
|
14
|
+
export type {KatzCentralityOptions} from "./katz.js";
|
|
15
|
+
export {katzCentrality, nodeKatzCentrality} from "./katz.js";
|
|
10
16
|
export type {PageRankOptions, PageRankResult} from "./pagerank.js";
|
|
11
17
|
export {pageRank, pageRankCentrality, personalizedPageRank, topPageRankNodes} from "./pagerank.js";
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type {Graph} from "../../core/graph.js";
|
|
2
|
+
import type {CentralityOptions, CentralityResult} from "../../types/index.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Katz centrality implementation
|
|
6
|
+
*
|
|
7
|
+
* A generalization of eigenvector centrality that gives each node a base amount
|
|
8
|
+
* of influence regardless of its position in the network. Uses an attenuation
|
|
9
|
+
* factor (alpha) to control how much a node's centrality depends on its neighbors.
|
|
10
|
+
*
|
|
11
|
+
* Katz centrality = alpha * sum(neighbors' centrality) + beta
|
|
12
|
+
*
|
|
13
|
+
* Time complexity: O(V + E) per iteration
|
|
14
|
+
* Space complexity: O(V)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
export interface KatzCentralityOptions extends CentralityOptions {
|
|
18
|
+
alpha?: number; // Attenuation factor (default: 0.1)
|
|
19
|
+
beta?: number; // Base centrality weight (default: 1.0)
|
|
20
|
+
maxIterations?: number; // Maximum iterations (default: 100)
|
|
21
|
+
tolerance?: number; // Convergence tolerance (default: 1e-6)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Calculate Katz centrality for all nodes in the graph
|
|
26
|
+
* Uses iterative method to compute centrality scores
|
|
27
|
+
*/
|
|
28
|
+
export function katzCentrality(
|
|
29
|
+
graph: Graph,
|
|
30
|
+
options: KatzCentralityOptions = {},
|
|
31
|
+
): CentralityResult {
|
|
32
|
+
const {
|
|
33
|
+
alpha = 0.1,
|
|
34
|
+
beta = 1.0,
|
|
35
|
+
maxIterations = 100,
|
|
36
|
+
tolerance = 1e-6,
|
|
37
|
+
normalized = true,
|
|
38
|
+
} = options;
|
|
39
|
+
|
|
40
|
+
const centrality: CentralityResult = {};
|
|
41
|
+
const nodes = Array.from(graph.nodes());
|
|
42
|
+
const nodeIds = nodes.map((node) => node.id);
|
|
43
|
+
|
|
44
|
+
if (nodeIds.length === 0) {
|
|
45
|
+
return centrality;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check if alpha is valid (should be less than 1/lambda_max)
|
|
49
|
+
// For simplicity, we'll trust the user's choice or use conservative default
|
|
50
|
+
|
|
51
|
+
// Initialize centrality scores
|
|
52
|
+
let currentScores = new Map<string, number>();
|
|
53
|
+
let previousScores = new Map<string, number>();
|
|
54
|
+
|
|
55
|
+
// Start with beta for all nodes
|
|
56
|
+
for (const nodeId of nodeIds) {
|
|
57
|
+
currentScores.set(nodeId.toString(), beta);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Iterative computation
|
|
61
|
+
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
62
|
+
previousScores = new Map(currentScores);
|
|
63
|
+
currentScores = new Map();
|
|
64
|
+
|
|
65
|
+
// Update each node's centrality
|
|
66
|
+
for (const nodeId of nodeIds) {
|
|
67
|
+
let sum = 0;
|
|
68
|
+
|
|
69
|
+
// For directed graphs, use in-neighbors
|
|
70
|
+
// For undirected graphs, use all neighbors
|
|
71
|
+
const neighbors = graph.isDirected ?
|
|
72
|
+
Array.from(graph.inNeighbors(nodeId)) :
|
|
73
|
+
graph.neighbors(nodeId);
|
|
74
|
+
|
|
75
|
+
for (const neighbor of neighbors) {
|
|
76
|
+
const neighborKey = neighbor.toString();
|
|
77
|
+
sum += previousScores.get(neighborKey) ?? 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
currentScores.set(nodeId.toString(), (alpha * sum) + beta);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for convergence
|
|
84
|
+
let maxDiff = 0;
|
|
85
|
+
for (const [nodeId, value] of Array.from(currentScores)) {
|
|
86
|
+
const prevValue = previousScores.get(nodeId) ?? 0;
|
|
87
|
+
const diff = Math.abs(value - prevValue);
|
|
88
|
+
maxDiff = Math.max(maxDiff, diff);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (maxDiff < tolerance) {
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Prepare results
|
|
97
|
+
for (const [nodeId, value] of Array.from(currentScores)) {
|
|
98
|
+
centrality[nodeId] = value;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Normalize if requested
|
|
102
|
+
if (normalized) {
|
|
103
|
+
let maxValue = 0;
|
|
104
|
+
let minValue = Number.POSITIVE_INFINITY;
|
|
105
|
+
|
|
106
|
+
for (const value of Object.values(centrality)) {
|
|
107
|
+
maxValue = Math.max(maxValue, value);
|
|
108
|
+
minValue = Math.min(minValue, value);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const range = maxValue - minValue;
|
|
112
|
+
if (range > 0) {
|
|
113
|
+
for (const nodeId of Object.keys(centrality)) {
|
|
114
|
+
const centralityValue = centrality[nodeId];
|
|
115
|
+
if (centralityValue !== undefined) {
|
|
116
|
+
centrality[nodeId] = (centralityValue - minValue) / range;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return centrality;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Calculate Katz centrality for a specific node
|
|
127
|
+
*/
|
|
128
|
+
export function nodeKatzCentrality(
|
|
129
|
+
graph: Graph,
|
|
130
|
+
nodeId: string | number,
|
|
131
|
+
options: KatzCentralityOptions = {},
|
|
132
|
+
): number {
|
|
133
|
+
if (!graph.hasNode(nodeId)) {
|
|
134
|
+
throw new Error(`Node ${String(nodeId)} not found in graph`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const centrality = katzCentrality(graph, options);
|
|
138
|
+
return centrality[nodeId.toString()] ?? 0;
|
|
139
|
+
}
|