@graphty/algorithms 2.1.1 → 2.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 +139 -50
- package/dist/algorithms.js +15260 -10390
- package/dist/algorithms.js.map +1 -1
- package/dist/algorithms.standalone.js +26675 -21542
- package/dist/algorithms.standalone.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +18 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +40 -1
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +20 -0
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +93 -8
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +9 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +18 -0
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +29 -0
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +9 -0
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +23 -0
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +9 -0
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +31 -0
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +13 -13
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +47 -10
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts +27 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.d.ts.map +1 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js +335 -0
- package/dist/src/algorithms/community/girvan-newman-legacy.js.map +1 -0
- package/dist/src/algorithms/community/girvan-newman.d.ts +5 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +34 -308
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +9 -6
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +24 -110
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +28 -77
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +59 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +46 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +27 -0
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +15 -5
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +97 -107
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +2 -12
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +47 -308
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +5 -25
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +0 -3
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +0 -4
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +33 -154
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/clustering/hierarchical-legacy.d.ts +56 -0
- package/dist/src/clustering/hierarchical-legacy.d.ts.map +1 -0
- package/dist/src/clustering/hierarchical-legacy.js +441 -0
- package/dist/src/clustering/hierarchical-legacy.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +7 -39
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +56 -417
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/k-core-legacy.d.ts +30 -0
- package/dist/src/clustering/k-core-legacy.d.ts.map +1 -0
- package/dist/src/clustering/k-core-legacy.js +191 -0
- package/dist/src/clustering/k-core-legacy.js.map +1 -0
- package/dist/src/clustering/k-core.d.ts +8 -7
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +33 -174
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl-legacy.d.ts +42 -0
- package/dist/src/clustering/mcl-legacy.d.ts.map +1 -0
- package/dist/src/clustering/mcl-legacy.js +386 -0
- package/dist/src/clustering/mcl-legacy.js.map +1 -0
- package/dist/src/clustering/mcl.d.ts +9 -32
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +48 -374
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/index.d.ts +29 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexed/accelerator.d.ts +141 -11
- package/dist/src/indexed/accelerator.d.ts.map +1 -1
- package/dist/src/indexed/accelerator.js +319 -5
- package/dist/src/indexed/accelerator.js.map +1 -1
- package/dist/src/indexed/all-pairs.d.ts +59 -0
- package/dist/src/indexed/all-pairs.d.ts.map +1 -0
- package/dist/src/indexed/all-pairs.js +274 -0
- package/dist/src/indexed/all-pairs.js.map +1 -0
- package/dist/src/indexed/bellman-ford.d.ts +25 -0
- package/dist/src/indexed/bellman-ford.d.ts.map +1 -0
- package/dist/src/indexed/bellman-ford.js +59 -0
- package/dist/src/indexed/bellman-ford.js.map +1 -0
- package/dist/src/indexed/betweenness.d.ts +85 -0
- package/dist/src/indexed/betweenness.d.ts.map +1 -0
- package/dist/src/indexed/betweenness.js +188 -0
- package/dist/src/indexed/betweenness.js.map +1 -0
- package/dist/src/indexed/bfs.d.ts +69 -2
- package/dist/src/indexed/bfs.d.ts.map +1 -1
- package/dist/src/indexed/bfs.js +148 -1
- package/dist/src/indexed/bfs.js.map +1 -1
- package/dist/src/indexed/bipartite.d.ts +37 -0
- package/dist/src/indexed/bipartite.d.ts.map +1 -0
- package/dist/src/indexed/bipartite.js +54 -0
- package/dist/src/indexed/bipartite.js.map +1 -0
- package/dist/src/indexed/closeness.d.ts +49 -0
- package/dist/src/indexed/closeness.d.ts.map +1 -0
- package/dist/src/indexed/closeness.js +127 -0
- package/dist/src/indexed/closeness.js.map +1 -0
- package/dist/src/indexed/common-neighbors.d.ts +14 -1
- package/dist/src/indexed/common-neighbors.d.ts.map +1 -1
- package/dist/src/indexed/common-neighbors.js +24 -14
- package/dist/src/indexed/common-neighbors.js.map +1 -1
- package/dist/src/indexed/degree.d.ts +19 -0
- package/dist/src/indexed/degree.d.ts.map +1 -0
- package/dist/src/indexed/degree.js +45 -0
- package/dist/src/indexed/degree.js.map +1 -0
- package/dist/src/indexed/delta-pagerank.d.ts +145 -0
- package/dist/src/indexed/delta-pagerank.d.ts.map +1 -0
- package/dist/src/indexed/delta-pagerank.js +428 -0
- package/dist/src/indexed/delta-pagerank.js.map +1 -0
- package/dist/src/indexed/dfs.d.ts +55 -0
- package/dist/src/indexed/dfs.d.ts.map +1 -0
- package/dist/src/indexed/dfs.js +146 -0
- package/dist/src/indexed/dfs.js.map +1 -0
- package/dist/src/indexed/eigenvector.d.ts +48 -0
- package/dist/src/indexed/eigenvector.d.ts.map +1 -0
- package/dist/src/indexed/eigenvector.js +162 -0
- package/dist/src/indexed/eigenvector.js.map +1 -0
- package/dist/src/indexed/facade.d.ts +109 -0
- package/dist/src/indexed/facade.d.ts.map +1 -0
- package/dist/src/indexed/facade.js +204 -0
- package/dist/src/indexed/facade.js.map +1 -0
- package/dist/src/indexed/flow.d.ts +120 -0
- package/dist/src/indexed/flow.d.ts.map +1 -0
- package/dist/src/indexed/flow.js +377 -0
- package/dist/src/indexed/flow.js.map +1 -0
- package/dist/src/indexed/girvan-newman.d.ts +42 -0
- package/dist/src/indexed/girvan-newman.d.ts.map +1 -0
- package/dist/src/indexed/girvan-newman.js +90 -0
- package/dist/src/indexed/girvan-newman.js.map +1 -0
- package/dist/src/indexed/grsbm.d.ts +85 -0
- package/dist/src/indexed/grsbm.d.ts.map +1 -0
- package/dist/src/indexed/grsbm.js +287 -0
- package/dist/src/indexed/grsbm.js.map +1 -0
- package/dist/src/indexed/hierarchical.d.ts +69 -0
- package/dist/src/indexed/hierarchical.d.ts.map +1 -0
- package/dist/src/indexed/hierarchical.js +245 -0
- package/dist/src/indexed/hierarchical.js.map +1 -0
- package/dist/src/indexed/index.d.ts +29 -3
- package/dist/src/indexed/index.d.ts.map +1 -1
- package/dist/src/indexed/index.js +29 -3
- package/dist/src/indexed/index.js.map +1 -1
- package/dist/src/indexed/isomorphism.d.ts +42 -0
- package/dist/src/indexed/isomorphism.d.ts.map +1 -0
- package/dist/src/indexed/isomorphism.js +269 -0
- package/dist/src/indexed/isomorphism.js.map +1 -0
- package/dist/src/indexed/label-propagation.d.ts +134 -0
- package/dist/src/indexed/label-propagation.d.ts.map +1 -0
- package/dist/src/indexed/label-propagation.js +486 -0
- package/dist/src/indexed/label-propagation.js.map +1 -0
- package/dist/src/indexed/leiden.d.ts +52 -0
- package/dist/src/indexed/leiden.d.ts.map +1 -0
- package/dist/src/indexed/leiden.js +384 -0
- package/dist/src/indexed/leiden.js.map +1 -0
- package/dist/src/indexed/link-prediction.d.ts +147 -0
- package/dist/src/indexed/link-prediction.d.ts.map +1 -0
- package/dist/src/indexed/link-prediction.js +317 -0
- package/dist/src/indexed/link-prediction.js.map +1 -0
- package/dist/src/indexed/markov.d.ts +54 -0
- package/dist/src/indexed/markov.d.ts.map +1 -0
- package/dist/src/indexed/markov.js +284 -0
- package/dist/src/indexed/markov.js.map +1 -0
- package/dist/src/indexed/matching.d.ts +53 -0
- package/dist/src/indexed/matching.d.ts.map +1 -0
- package/dist/src/indexed/matching.js +139 -0
- package/dist/src/indexed/matching.js.map +1 -0
- package/dist/src/indexed/min-cut.d.ts +48 -0
- package/dist/src/indexed/min-cut.d.ts.map +1 -0
- package/dist/src/indexed/min-cut.js +175 -0
- package/dist/src/indexed/min-cut.js.map +1 -0
- package/dist/src/indexed/modularity.d.ts +34 -0
- package/dist/src/indexed/modularity.d.ts.map +1 -0
- package/dist/src/indexed/modularity.js +74 -0
- package/dist/src/indexed/modularity.js.map +1 -0
- package/dist/src/indexed/mst.d.ts +29 -1
- package/dist/src/indexed/mst.d.ts.map +1 -1
- package/dist/src/indexed/mst.js +62 -0
- package/dist/src/indexed/mst.js.map +1 -1
- package/dist/src/indexed/pagerank.d.ts +29 -6
- package/dist/src/indexed/pagerank.d.ts.map +1 -1
- package/dist/src/indexed/pagerank.js +107 -11
- package/dist/src/indexed/pagerank.js.map +1 -1
- package/dist/src/indexed/point-to-point.d.ts +53 -0
- package/dist/src/indexed/point-to-point.d.ts.map +1 -0
- package/dist/src/indexed/point-to-point.js +173 -0
- package/dist/src/indexed/point-to-point.js.map +1 -0
- package/dist/src/indexed/scc.d.ts +53 -0
- package/dist/src/indexed/scc.d.ts.map +1 -0
- package/dist/src/indexed/scc.js +110 -0
- package/dist/src/indexed/scc.js.map +1 -0
- package/dist/src/indexed/spectral.d.ts +61 -0
- package/dist/src/indexed/spectral.d.ts.map +1 -0
- package/dist/src/indexed/spectral.js +476 -0
- package/dist/src/indexed/spectral.js.map +1 -0
- package/dist/src/indexed/structures/bit-set.d.ts +45 -0
- package/dist/src/indexed/structures/bit-set.d.ts.map +1 -0
- package/dist/src/indexed/structures/bit-set.js +57 -0
- package/dist/src/indexed/structures/bit-set.js.map +1 -0
- package/dist/src/indexed/structures/max-heap.d.ts +52 -0
- package/dist/src/indexed/structures/max-heap.d.ts.map +1 -0
- package/dist/src/indexed/structures/max-heap.js +66 -0
- package/dist/src/indexed/structures/max-heap.js.map +1 -0
- package/dist/src/indexed/structures/min-heap.d.ts +30 -1
- package/dist/src/indexed/structures/min-heap.d.ts.map +1 -1
- package/dist/src/indexed/structures/min-heap.js +46 -6
- package/dist/src/indexed/structures/min-heap.js.map +1 -1
- package/dist/src/indexed/structures/ring-queue.d.ts +41 -0
- package/dist/src/indexed/structures/ring-queue.d.ts.map +1 -0
- package/dist/src/indexed/structures/ring-queue.js +64 -0
- package/dist/src/indexed/structures/ring-queue.js.map +1 -0
- package/dist/src/indexed/sync.d.ts +50 -0
- package/dist/src/indexed/sync.d.ts.map +1 -0
- package/dist/src/indexed/sync.js +175 -0
- package/dist/src/indexed/sync.js.map +1 -0
- package/dist/src/indexed/terahac.d.ts +58 -0
- package/dist/src/indexed/terahac.d.ts.map +1 -0
- package/dist/src/indexed/terahac.js +244 -0
- package/dist/src/indexed/terahac.js.map +1 -0
- package/dist/src/indexed/to-snapshot.d.ts +45 -2
- package/dist/src/indexed/to-snapshot.d.ts.map +1 -1
- package/dist/src/indexed/to-snapshot.js +140 -6
- package/dist/src/indexed/to-snapshot.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts +71 -0
- package/dist/src/link-prediction/common-neighbors-legacy.d.ts.map +1 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js +172 -0
- package/dist/src/link-prediction/common-neighbors-legacy.js.map +1 -0
- package/dist/src/link-prediction/common-neighbors.d.ts +11 -19
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +56 -123
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +6 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +6 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/research/grsbm-legacy.d.ts +82 -0
- package/dist/src/research/grsbm-legacy.d.ts.map +1 -0
- package/dist/src/research/grsbm-legacy.js +416 -0
- package/dist/src/research/grsbm-legacy.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +9 -66
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +67 -391
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/sync-legacy.d.ts +48 -0
- package/dist/src/research/sync-legacy.d.ts.map +1 -0
- package/dist/src/research/sync-legacy.js +337 -0
- package/dist/src/research/sync-legacy.js.map +1 -0
- package/dist/src/research/sync.d.ts +10 -32
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +28 -316
- package/dist/src/research/sync.js.map +1 -1
- package/package.json +4 -3
- package/src/algorithms/centrality/betweenness.ts +51 -1
- package/src/algorithms/centrality/closeness.ts +112 -12
- package/src/algorithms/centrality/degree.ts +19 -0
- package/src/algorithms/centrality/eigenvector.ts +34 -0
- package/src/algorithms/centrality/hits.ts +24 -0
- package/src/algorithms/centrality/katz.ts +32 -0
- package/src/algorithms/centrality/pagerank.ts +53 -13
- package/src/algorithms/community/girvan-newman-legacy.ts +414 -0
- package/src/algorithms/community/girvan-newman.ts +36 -386
- package/src/algorithms/community/label-propagation.ts +24 -133
- package/src/algorithms/components/connected.ts +28 -95
- package/src/algorithms/mst/kruskal.ts +65 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +49 -0
- package/src/algorithms/shortest-path/dijkstra.ts +29 -0
- package/src/algorithms/shortest-path/floyd-warshall.ts +106 -136
- package/src/algorithms/traversal/bfs-unified.ts +52 -366
- package/src/algorithms/traversal/bfs-variants.ts +5 -28
- package/src/algorithms/traversal/bfs.ts +0 -4
- package/src/algorithms/traversal/dfs.ts +39 -182
- package/src/clustering/hierarchical-legacy.ts +551 -0
- package/src/clustering/hierarchical.ts +64 -520
- package/src/clustering/k-core-legacy.ts +229 -0
- package/src/clustering/k-core.ts +38 -209
- package/src/clustering/mcl-legacy.ts +498 -0
- package/src/clustering/mcl.ts +46 -479
- package/src/index.ts +90 -3
- package/src/indexed/accelerator.ts +502 -16
- package/src/indexed/all-pairs.ts +342 -0
- package/src/indexed/bellman-ford.ts +72 -0
- package/src/indexed/betweenness.ts +258 -0
- package/src/indexed/bfs.ts +189 -3
- package/src/indexed/bipartite.ts +77 -0
- package/src/indexed/closeness.ts +160 -0
- package/src/indexed/common-neighbors.ts +31 -14
- package/src/indexed/degree.ts +55 -0
- package/src/indexed/delta-pagerank.ts +513 -0
- package/src/indexed/dfs.ts +189 -0
- package/src/indexed/eigenvector.ts +202 -0
- package/src/indexed/facade.ts +229 -0
- package/src/indexed/flow.ts +500 -0
- package/src/indexed/girvan-newman.ts +120 -0
- package/src/indexed/grsbm.ts +390 -0
- package/src/indexed/hierarchical.ts +295 -0
- package/src/indexed/index.ts +94 -3
- package/src/indexed/isomorphism.ts +305 -0
- package/src/indexed/label-propagation.ts +582 -0
- package/src/indexed/leiden.ts +436 -0
- package/src/indexed/link-prediction.ts +399 -0
- package/src/indexed/markov.ts +324 -0
- package/src/indexed/matching.ts +192 -0
- package/src/indexed/min-cut.ts +201 -0
- package/src/indexed/modularity.ts +81 -0
- package/src/indexed/mst.ts +79 -1
- package/src/indexed/pagerank.ts +128 -15
- package/src/indexed/point-to-point.ts +220 -0
- package/src/indexed/scc.ts +132 -0
- package/src/indexed/spectral.ts +550 -0
- package/src/indexed/structures/bit-set.ts +65 -0
- package/src/indexed/structures/max-heap.ts +74 -0
- package/src/indexed/structures/min-heap.ts +51 -6
- package/src/indexed/structures/ring-queue.ts +70 -0
- package/src/indexed/sync.ts +229 -0
- package/src/indexed/terahac.ts +290 -0
- package/src/indexed/to-snapshot.ts +156 -6
- package/src/link-prediction/common-neighbors-legacy.ts +252 -0
- package/src/link-prediction/common-neighbors.ts +65 -168
- package/src/optimized/direction-optimized-bfs.ts +6 -1
- package/src/research/grsbm-legacy.ts +586 -0
- package/src/research/grsbm.ts +76 -555
- package/src/research/sync-legacy.ts +456 -0
- package/src/research/sync.ts +30 -431
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import { degreeCentrality as indexedDegree } from "../../indexed/degree.js";
|
|
3
|
+
import { scoresToRecord } from "../../indexed/facade.js";
|
|
4
|
+
import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
|
|
2
5
|
import type { CentralityOptions, CentralityResult } from "../../types/index.js";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -15,6 +18,22 @@ import type { CentralityOptions, CentralityResult } from "../../types/index.js";
|
|
|
15
18
|
* @returns Centrality scores for each node keyed by node ID
|
|
16
19
|
*/
|
|
17
20
|
export function degreeCentrality(graph: Graph, options: CentralityOptions = {}): CentralityResult {
|
|
21
|
+
if (needsLegacyCode(graph)) {
|
|
22
|
+
return legacyDegreeCentrality(graph, options);
|
|
23
|
+
}
|
|
24
|
+
const s = toSnapshot(graph);
|
|
25
|
+
return scoresToRecord(s.ids, indexedDegree(s, { mode: options.mode, normalized: options.normalized }));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The implementation {@link degreeCentrality} delegates away from, kept as its differential-test
|
|
30
|
+
* oracle. Deleted at the removal release.
|
|
31
|
+
* @param graph - The input graph to analyze
|
|
32
|
+
* @param options - Algorithm configuration options
|
|
33
|
+
* @returns Centrality scores for each node keyed by node ID
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export function legacyDegreeCentrality(graph: Graph, options: CentralityOptions = {}): CentralityResult {
|
|
18
37
|
const centrality: CentralityResult = {};
|
|
19
38
|
const { nodeCount } = graph;
|
|
20
39
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
2
|
import { ConvergenceError } from "../../errors.js";
|
|
3
|
+
import { eigenvectorCentrality as indexedEigenvector } from "../../indexed/eigenvector.js";
|
|
4
|
+
import { scoresToRecord } from "../../indexed/facade.js";
|
|
5
|
+
import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
|
|
3
6
|
import type { CentralityOptions, CentralityResult } from "../../types/index.js";
|
|
4
7
|
|
|
5
8
|
/**
|
|
@@ -41,6 +44,37 @@ export interface EigenvectorCentralityOptions extends CentralityOptions {
|
|
|
41
44
|
* raises `PowerIterationFailedConvergence`. Raise `maxIterations` or `tolerance` and call again.
|
|
42
45
|
*/
|
|
43
46
|
export function eigenvectorCentrality(graph: Graph, options: EigenvectorCentralityOptions = {}): CentralityResult {
|
|
47
|
+
if (needsLegacyCode(graph)) {
|
|
48
|
+
return legacyEigenvectorCentrality(graph, options);
|
|
49
|
+
}
|
|
50
|
+
const s = toSnapshot(graph);
|
|
51
|
+
const { startVector } = options;
|
|
52
|
+
const r = indexedEigenvector(s, {
|
|
53
|
+
maxIterations: options.maxIterations,
|
|
54
|
+
tolerance: options.tolerance,
|
|
55
|
+
normalized: options.normalized,
|
|
56
|
+
mode: options.mode,
|
|
57
|
+
startVector:
|
|
58
|
+
startVector === undefined
|
|
59
|
+
? undefined
|
|
60
|
+
: Float64Array.from({ length: s.nodeCount }, (_, i) => startVector.get(String(s.ids.idOf(i))) ?? 1),
|
|
61
|
+
});
|
|
62
|
+
return scoresToRecord(s.ids, r.scores);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The implementation {@link eigenvectorCentrality} delegates away from, kept as its
|
|
67
|
+
* differential-test oracle. Deleted at the removal release.
|
|
68
|
+
* @param graph - The graph to compute eigenvector centrality on
|
|
69
|
+
* @param options - Configuration options for the computation
|
|
70
|
+
* @returns Object mapping node IDs to their eigenvector centrality scores
|
|
71
|
+
* @throws {ConvergenceError} When `maxIterations` passes do not meet `tolerance`
|
|
72
|
+
* @internal
|
|
73
|
+
*/
|
|
74
|
+
export function legacyEigenvectorCentrality(
|
|
75
|
+
graph: Graph,
|
|
76
|
+
options: EigenvectorCentralityOptions = {},
|
|
77
|
+
): CentralityResult {
|
|
44
78
|
const { maxIterations = 100, tolerance = 1e-6, normalized = true, startVector, mode = "in" } = options;
|
|
45
79
|
|
|
46
80
|
const nodeIds = Array.from(graph.nodes(), (node) => node.id);
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import { scoresToRecord } from "../../indexed/facade.js";
|
|
3
|
+
import { hits as indexedHits } from "../../indexed/hits.js";
|
|
4
|
+
import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
|
|
2
5
|
import type { CentralityOptions } from "../../types/index.js";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -32,6 +35,27 @@ export interface HITSOptions extends CentralityOptions {
|
|
|
32
35
|
* @returns Object containing hub and authority scores for all nodes
|
|
33
36
|
*/
|
|
34
37
|
export function hits(graph: Graph, options: HITSOptions = {}): HITSResult {
|
|
38
|
+
if (needsLegacyCode(graph)) {
|
|
39
|
+
return legacyHits(graph, options);
|
|
40
|
+
}
|
|
41
|
+
const s = toSnapshot(graph);
|
|
42
|
+
const r = indexedHits(s, {
|
|
43
|
+
maxIterations: options.maxIterations,
|
|
44
|
+
tolerance: options.tolerance,
|
|
45
|
+
normalized: options.normalized,
|
|
46
|
+
});
|
|
47
|
+
return { hubs: scoresToRecord(s.ids, r.hubs), authorities: scoresToRecord(s.ids, r.authorities) };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The implementation {@link hits} delegates away from, kept as its differential-test oracle.
|
|
52
|
+
* Deleted at the removal release.
|
|
53
|
+
* @param graph - The graph to compute HITS scores on
|
|
54
|
+
* @param options - Configuration options for the computation
|
|
55
|
+
* @returns Object containing hub and authority scores for all nodes
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
export function legacyHits(graph: Graph, options: HITSOptions = {}): HITSResult {
|
|
35
59
|
const { maxIterations = 100, tolerance = 1e-6, normalized = true } = options;
|
|
36
60
|
|
|
37
61
|
const hubs: Record<string, number> = {};
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import { scoresToRecord } from "../../indexed/facade.js";
|
|
3
|
+
import { katzCentrality as indexedKatz } from "../../indexed/katz.js";
|
|
4
|
+
import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
|
|
2
5
|
import type { CentralityOptions, CentralityResult } from "../../types/index.js";
|
|
3
6
|
|
|
4
7
|
/**
|
|
@@ -29,6 +32,35 @@ export interface KatzCentralityOptions extends CentralityOptions {
|
|
|
29
32
|
* @returns Object mapping node IDs to their Katz centrality scores
|
|
30
33
|
*/
|
|
31
34
|
export function katzCentrality(graph: Graph, options: KatzCentralityOptions = {}): CentralityResult {
|
|
35
|
+
const { alpha = 0.1, beta = 1.0 } = options;
|
|
36
|
+
// A negative alpha or beta can leave every score negative, and the legacy rescale measures the
|
|
37
|
+
// maximum from 0 rather than from the largest score.
|
|
38
|
+
if (alpha < 0 || beta < 0) {
|
|
39
|
+
return legacyKatzCentrality(graph, options);
|
|
40
|
+
}
|
|
41
|
+
if (needsLegacyCode(graph)) {
|
|
42
|
+
return legacyKatzCentrality(graph, options);
|
|
43
|
+
}
|
|
44
|
+
const s = toSnapshot(graph);
|
|
45
|
+
const r = indexedKatz(s, {
|
|
46
|
+
alpha,
|
|
47
|
+
beta,
|
|
48
|
+
maxIterations: options.maxIterations,
|
|
49
|
+
tolerance: options.tolerance,
|
|
50
|
+
normalized: options.normalized,
|
|
51
|
+
});
|
|
52
|
+
return scoresToRecord(s.ids, r.scores);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The implementation {@link katzCentrality} delegates away from, kept as its differential-test
|
|
57
|
+
* oracle and for a negative `alpha` or `beta`. Deleted at the removal release.
|
|
58
|
+
* @param graph - The graph to compute Katz centrality on
|
|
59
|
+
* @param options - Configuration options for the computation
|
|
60
|
+
* @returns Object mapping node IDs to their Katz centrality scores
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
export function legacyKatzCentrality(graph: Graph, options: KatzCentralityOptions = {}): CentralityResult {
|
|
32
64
|
const { alpha = 0.1, beta = 1.0, maxIterations = 100, tolerance = 1e-6, normalized = true } = options;
|
|
33
65
|
|
|
34
66
|
const centrality: CentralityResult = {};
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
+
import { type F64, INVALID_INDEX } from "@graphty/graph-format";
|
|
2
|
+
|
|
1
3
|
import type { Graph } from "../../core/graph.js";
|
|
4
|
+
import { deltaPageRank } from "../../indexed/delta-pagerank.js";
|
|
5
|
+
import { exactArcWeights, scoresToRecord } from "../../indexed/facade.js";
|
|
6
|
+
import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
|
|
2
7
|
import type { NodeId } from "../../types/index.js";
|
|
3
8
|
import { SimpleDeltaPageRank } from "./delta-pagerank-simple.js";
|
|
4
9
|
|
|
@@ -38,9 +43,8 @@ export interface PageRankOptions {
|
|
|
38
43
|
*/
|
|
39
44
|
weight?: string;
|
|
40
45
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* Set explicitly to override automatic heuristic.
|
|
46
|
+
* Accepted and ignored. It once chose between two engines that compute the same iteration, so
|
|
47
|
+
* it never changed the result, and now there is one engine.
|
|
44
48
|
*/
|
|
45
49
|
useDelta?: boolean;
|
|
46
50
|
}
|
|
@@ -66,21 +70,57 @@ export interface PageRankResult {
|
|
|
66
70
|
/**
|
|
67
71
|
* Calculate PageRank for all nodes in the graph
|
|
68
72
|
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* The delta-based approach provides significant speedup for:
|
|
73
|
-
* - Incremental updates after graph modifications
|
|
74
|
-
* - Graphs with localized changes
|
|
75
|
-
* - Early convergence detection per vertex
|
|
76
|
-
*
|
|
77
|
-
* For initial computation on small-medium graphs, standard algorithm may be faster
|
|
78
|
-
* due to lower overhead.
|
|
73
|
+
* Power iteration with the dangling mass spread over every node and an L-infinity stopping rule.
|
|
74
|
+
* `useDelta` is ignored: it chose between two engines that computed the same iteration.
|
|
79
75
|
* @param graph - The directed input graph to analyze
|
|
80
76
|
* @param options - Algorithm configuration options
|
|
81
77
|
* @returns PageRank result containing ranks, iteration count, and convergence status
|
|
82
78
|
*/
|
|
83
79
|
export function pageRank(graph: Graph, options: PageRankOptions = {}): PageRankResult {
|
|
80
|
+
if (needsLegacyCode(graph)) {
|
|
81
|
+
return legacyPageRank(graph, options);
|
|
82
|
+
}
|
|
83
|
+
const s = toSnapshot(graph);
|
|
84
|
+
const { personalization, initialRanks, weight } = options;
|
|
85
|
+
// The legacy code normalises the personalization over every entry of the Map, so an id the
|
|
86
|
+
// graph lacks takes a share; the port sees only the graph's nodes.
|
|
87
|
+
if (
|
|
88
|
+
personalization !== undefined &&
|
|
89
|
+
[...personalization.keys()].some((id) => s.ids.indexOf(id) === INVALID_INDEX)
|
|
90
|
+
) {
|
|
91
|
+
return legacyPageRank(graph, options);
|
|
92
|
+
}
|
|
93
|
+
const perNode = (map: Map<NodeId, number> | undefined, fill: number): F64 | undefined => {
|
|
94
|
+
if (map === undefined) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
const out = new Float64Array(s.nodeCount);
|
|
98
|
+
for (let i = 0; i < out.length; i++) {
|
|
99
|
+
out[i] = map.get(s.ids.idOf(i)) ?? fill;
|
|
100
|
+
}
|
|
101
|
+
return out;
|
|
102
|
+
};
|
|
103
|
+
const r = deltaPageRank(s, {
|
|
104
|
+
dampingFactor: options.dampingFactor,
|
|
105
|
+
maxIterations: options.maxIterations,
|
|
106
|
+
tolerance: options.tolerance,
|
|
107
|
+
weighted: Boolean(weight),
|
|
108
|
+
weights: weight ? exactArcWeights(s) : undefined,
|
|
109
|
+
initialRanks: perNode(initialRanks, 1 / s.nodeCount),
|
|
110
|
+
personalization: perNode(personalization, 0),
|
|
111
|
+
});
|
|
112
|
+
return { ranks: scoresToRecord(s.ids, r.scores), iterations: r.iterations, converged: r.converged };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The implementation {@link pageRank} delegates away from, kept as its differential-test oracle and
|
|
117
|
+
* for a personalization naming a node the graph lacks. Deleted at the removal release.
|
|
118
|
+
* @param graph - The directed input graph to analyze
|
|
119
|
+
* @param options - Algorithm configuration options
|
|
120
|
+
* @returns PageRank result containing ranks, iteration count, and convergence status
|
|
121
|
+
* @internal
|
|
122
|
+
*/
|
|
123
|
+
export function legacyPageRank(graph: Graph, options: PageRankOptions = {}): PageRankResult {
|
|
84
124
|
const {
|
|
85
125
|
dampingFactor = 0.85,
|
|
86
126
|
maxIterations = 100,
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Girvan-Newman as it was before `girvanNewman` delegated to `indexed.girvanNewman`, unchanged. The
|
|
3
|
+
* facade in `girvan-newman.ts` still calls it for a directed graph, a self-loop, a NaN weight or
|
|
4
|
+
* ids spelled alike, and its facade test uses it as the oracle.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { Graph } from "../../core/graph.js";
|
|
8
|
+
import type { CommunityResult, ComponentResult, GirvanNewmanOptions, NodeId } from "../../types/index.js";
|
|
9
|
+
import { connectedComponents } from "../components/connected.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Girvan-Newman community detection algorithm
|
|
13
|
+
*
|
|
14
|
+
* Implements the Girvan-Newman method for community detection by iteratively
|
|
15
|
+
* removing edges with the highest betweenness centrality until the graph
|
|
16
|
+
* splits into disconnected components.
|
|
17
|
+
*
|
|
18
|
+
* This is a divisive hierarchical clustering algorithm that produces a
|
|
19
|
+
* dendrogram of community structures.
|
|
20
|
+
*
|
|
21
|
+
* References:
|
|
22
|
+
* - Girvan, M., & Newman, M. E. J. (2002). Community structure in social
|
|
23
|
+
* and biological networks. Proceedings of the National Academy of Sciences,
|
|
24
|
+
* 99(12), 7821-7826.
|
|
25
|
+
* @param graph - The input graph
|
|
26
|
+
* @param options - Algorithm options
|
|
27
|
+
* @returns Array of community detection results representing the dendrogram
|
|
28
|
+
*/
|
|
29
|
+
export function girvanNewman(graph: Graph, options: GirvanNewmanOptions = {}): CommunityResult[] {
|
|
30
|
+
const { maxCommunities } = options;
|
|
31
|
+
const minCommunitySize = options.minCommunitySize ?? 1;
|
|
32
|
+
const maxIterations = options.maxIterations ?? 100; // Prevent infinite loops
|
|
33
|
+
|
|
34
|
+
const dendrogram: CommunityResult[] = [];
|
|
35
|
+
const workingGraph = graph.clone();
|
|
36
|
+
let iterations = 0;
|
|
37
|
+
|
|
38
|
+
// Initial state: one large component
|
|
39
|
+
let components = getConnectedComponentsResult(workingGraph);
|
|
40
|
+
dendrogram.push({
|
|
41
|
+
communities: components.components.filter((community) => community.length >= minCommunitySize),
|
|
42
|
+
modularity: calculateModularity(graph, components.componentMap),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
while (Array.from(workingGraph.edges()).length > 0 && iterations < maxIterations) {
|
|
46
|
+
iterations++;
|
|
47
|
+
// Calculate edge betweenness centrality
|
|
48
|
+
const { betweenness: edgeBetweenness, edgeEndpoints } = calculateEdgeBetweenness(workingGraph);
|
|
49
|
+
|
|
50
|
+
if (edgeBetweenness.size === 0) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Find edges with maximum betweenness
|
|
55
|
+
const maxBetweenness = Math.max(...edgeBetweenness.values());
|
|
56
|
+
const edgesToRemove: { source: NodeId; target: NodeId }[] = [];
|
|
57
|
+
|
|
58
|
+
for (const [edgeKey, centrality] of edgeBetweenness) {
|
|
59
|
+
if (Math.abs(centrality - maxBetweenness) < 1e-10) {
|
|
60
|
+
// Use original edge endpoints to preserve node ID types (number vs string)
|
|
61
|
+
const endpoints = edgeEndpoints.get(edgeKey);
|
|
62
|
+
if (endpoints) {
|
|
63
|
+
edgesToRemove.push(endpoints);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Remove edges with highest betweenness
|
|
69
|
+
for (const { source, target } of edgesToRemove) {
|
|
70
|
+
workingGraph.removeEdge(source, target);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Find new connected components (communities)
|
|
74
|
+
components = getConnectedComponentsResult(workingGraph);
|
|
75
|
+
|
|
76
|
+
// Filter communities by minimum size
|
|
77
|
+
const validCommunities = components.components.filter((community) => community.length >= minCommunitySize);
|
|
78
|
+
|
|
79
|
+
// Calculate modularity for the new community structure
|
|
80
|
+
const modularity = calculateModularity(graph, components.componentMap);
|
|
81
|
+
|
|
82
|
+
dendrogram.push({
|
|
83
|
+
communities: validCommunities,
|
|
84
|
+
modularity,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Stop if desired number of communities reached
|
|
88
|
+
if (maxCommunities && validCommunities.length >= maxCommunities) {
|
|
89
|
+
break;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Stop if no more meaningful communities can be formed
|
|
93
|
+
if (validCommunities.length === workingGraph.nodeCount) {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return dendrogram;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Edge betweenness result containing centrality values and original edge endpoints
|
|
103
|
+
*/
|
|
104
|
+
interface EdgeBetweennessResult {
|
|
105
|
+
betweenness: Map<string, number>;
|
|
106
|
+
edgeEndpoints: Map<string, { source: NodeId; target: NodeId }>;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Calculate edge betweenness centrality for all edges in the graph
|
|
111
|
+
*
|
|
112
|
+
* Edge betweenness is the fraction of shortest paths that pass through the edge.
|
|
113
|
+
* We adapt node betweenness centrality calculation to work with edges.
|
|
114
|
+
* @param graph - The input graph to analyze
|
|
115
|
+
* @returns Object with betweenness map and edge endpoints map (preserving original ID types)
|
|
116
|
+
*/
|
|
117
|
+
function calculateEdgeBetweenness(graph: Graph): EdgeBetweennessResult {
|
|
118
|
+
const edgeBetweenness = new Map<string, number>();
|
|
119
|
+
const edgeEndpoints = new Map<string, { source: NodeId; target: NodeId }>();
|
|
120
|
+
|
|
121
|
+
// Initialize all edges with 0 betweenness and store original endpoints
|
|
122
|
+
for (const edge of graph.edges()) {
|
|
123
|
+
const edgeKey = getEdgeKey(edge.source, edge.target);
|
|
124
|
+
edgeBetweenness.set(edgeKey, 0);
|
|
125
|
+
edgeEndpoints.set(edgeKey, { source: edge.source, target: edge.target });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// For each node as source, calculate shortest paths and accumulate edge betweenness
|
|
129
|
+
for (const sourceNode of graph.nodes()) {
|
|
130
|
+
const source = sourceNode.id;
|
|
131
|
+
|
|
132
|
+
// Run BFS to find all shortest paths from source
|
|
133
|
+
const { distances, predecessors, pathCounts } = findAllShortestPaths(graph, source);
|
|
134
|
+
|
|
135
|
+
// Calculate dependency for each node and accumulate edge betweenness
|
|
136
|
+
const dependency = new Map<NodeId, number>();
|
|
137
|
+
|
|
138
|
+
// Initialize dependency
|
|
139
|
+
for (const node of graph.nodes()) {
|
|
140
|
+
dependency.set(node.id, 0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Process nodes in order of decreasing distance
|
|
144
|
+
const sortedNodes = Array.from(distances.keys())
|
|
145
|
+
.filter((node) => {
|
|
146
|
+
const distance = distances.get(node);
|
|
147
|
+
return distance !== undefined && distance < Infinity;
|
|
148
|
+
})
|
|
149
|
+
.sort((a, b) => {
|
|
150
|
+
const distanceA = distances.get(a);
|
|
151
|
+
const distanceB = distances.get(b);
|
|
152
|
+
if (distanceA === undefined || distanceB === undefined) {
|
|
153
|
+
return 0;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return distanceB - distanceA;
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
for (const node of sortedNodes) {
|
|
160
|
+
if (node === source) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const nodeDependency = dependency.get(node);
|
|
165
|
+
const nodePathCount = pathCounts.get(node);
|
|
166
|
+
|
|
167
|
+
if (nodeDependency === undefined || nodePathCount === undefined) {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Distribute dependency to predecessors
|
|
172
|
+
const nodePredecessors = predecessors.get(node) ?? [];
|
|
173
|
+
for (const predecessor of nodePredecessors) {
|
|
174
|
+
const predPathCount = pathCounts.get(predecessor);
|
|
175
|
+
if (predPathCount === undefined) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const edgeDependency = (predPathCount / nodePathCount) * (1 + nodeDependency);
|
|
180
|
+
|
|
181
|
+
// Update predecessor dependency
|
|
182
|
+
const currentDependency = dependency.get(predecessor);
|
|
183
|
+
if (currentDependency !== undefined) {
|
|
184
|
+
dependency.set(predecessor, currentDependency + edgeDependency);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Update edge betweenness
|
|
188
|
+
const edgeKey = getEdgeKey(predecessor, node);
|
|
189
|
+
const currentBetweenness = edgeBetweenness.get(edgeKey) ?? 0;
|
|
190
|
+
edgeBetweenness.set(edgeKey, currentBetweenness + edgeDependency);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Normalize: divide by 2 for undirected graphs (each edge counted twice)
|
|
196
|
+
if (!graph.isDirected) {
|
|
197
|
+
for (const [edgeKey, betweenness] of edgeBetweenness) {
|
|
198
|
+
edgeBetweenness.set(edgeKey, betweenness / 2);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return { betweenness: edgeBetweenness, edgeEndpoints };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Find all shortest paths from a source node using optimized BFS
|
|
207
|
+
* @param graph - The input graph to search
|
|
208
|
+
* @param source - The source node to compute shortest paths from
|
|
209
|
+
* @returns Object containing distances, predecessors, and path counts
|
|
210
|
+
*/
|
|
211
|
+
function findAllShortestPaths(
|
|
212
|
+
graph: Graph,
|
|
213
|
+
source: NodeId,
|
|
214
|
+
): {
|
|
215
|
+
distances: Map<NodeId, number>;
|
|
216
|
+
predecessors: Map<NodeId, NodeId[]>;
|
|
217
|
+
pathCounts: Map<NodeId, number>;
|
|
218
|
+
} {
|
|
219
|
+
const distances = new Map<NodeId, number>();
|
|
220
|
+
const predecessors = new Map<NodeId, NodeId[]>();
|
|
221
|
+
const pathCounts = new Map<NodeId, number>();
|
|
222
|
+
const queue: NodeId[] = [];
|
|
223
|
+
const visited = new Set<NodeId>();
|
|
224
|
+
|
|
225
|
+
// Only initialize for reachable nodes
|
|
226
|
+
distances.set(source, 0);
|
|
227
|
+
predecessors.set(source, []);
|
|
228
|
+
pathCounts.set(source, 1);
|
|
229
|
+
queue.push(source);
|
|
230
|
+
|
|
231
|
+
// Optimized BFS with early termination
|
|
232
|
+
let queueIndex = 0;
|
|
233
|
+
while (queueIndex < queue.length) {
|
|
234
|
+
const current = queue[queueIndex++];
|
|
235
|
+
if (current === undefined) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (visited.has(current)) {
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
visited.add(current);
|
|
244
|
+
|
|
245
|
+
const currentDistance = distances.get(current);
|
|
246
|
+
if (currentDistance === undefined) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
for (const neighbor of graph.neighbors(current)) {
|
|
251
|
+
// Skip if already processed
|
|
252
|
+
if (visited.has(neighbor)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const edgeWeight = 1; // Unweighted for community detection
|
|
257
|
+
const newDistance = currentDistance + edgeWeight;
|
|
258
|
+
const neighborDistance = distances.get(neighbor) ?? Infinity;
|
|
259
|
+
|
|
260
|
+
if (newDistance < neighborDistance) {
|
|
261
|
+
// Found shorter path
|
|
262
|
+
distances.set(neighbor, newDistance);
|
|
263
|
+
predecessors.set(neighbor, [current]);
|
|
264
|
+
const currentPathCount = pathCounts.get(current);
|
|
265
|
+
if (currentPathCount !== undefined) {
|
|
266
|
+
pathCounts.set(neighbor, currentPathCount);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
queue.push(neighbor);
|
|
270
|
+
} else if (newDistance === neighborDistance) {
|
|
271
|
+
// Found alternative shortest path
|
|
272
|
+
const neighborPredecessors = predecessors.get(neighbor);
|
|
273
|
+
if (neighborPredecessors) {
|
|
274
|
+
neighborPredecessors.push(current);
|
|
275
|
+
const currentPathCount = pathCounts.get(current);
|
|
276
|
+
if (currentPathCount !== undefined) {
|
|
277
|
+
pathCounts.set(neighbor, (pathCounts.get(neighbor) ?? 0) + currentPathCount);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return { distances, predecessors, pathCounts };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Generate a consistent edge key for undirected graphs
|
|
289
|
+
* @param source - The source node ID
|
|
290
|
+
* @param target - The target node ID
|
|
291
|
+
* @returns A consistent string key for the edge (ordered alphabetically)
|
|
292
|
+
*/
|
|
293
|
+
function getEdgeKey(source: NodeId, target: NodeId): string {
|
|
294
|
+
// For undirected graphs, ensure consistent ordering
|
|
295
|
+
const sourceStr = String(source);
|
|
296
|
+
const targetStr = String(target);
|
|
297
|
+
|
|
298
|
+
if (sourceStr <= targetStr) {
|
|
299
|
+
return `${sourceStr}|${targetStr}`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return `${targetStr}|${sourceStr}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Calculate modularity for a given community structure
|
|
307
|
+
*
|
|
308
|
+
* Newman's Q, written per community:
|
|
309
|
+
*
|
|
310
|
+
* Q = sum over communities c of [ w_in(c) / m - ( K_c / (2m) )^2 ]
|
|
311
|
+
*
|
|
312
|
+
* where m is the total edge weight with each undirected edge counted once, w_in(c) is the summed
|
|
313
|
+
* weight of the edges with both endpoints inside c, and K_c is the summed weighted degree of c's
|
|
314
|
+
* nodes.
|
|
315
|
+
*
|
|
316
|
+
* WHY NOT A SUM OVER EDGES. The textbook double sum runs over every ordered PAIR of nodes inside
|
|
317
|
+
* a community, not only over the pairs an edge happens to join. Collecting the null-model term
|
|
318
|
+
* only where an edge exists leaves the penalty far too small, and the shortfall grows with
|
|
319
|
+
* community size -- so the uncut whole graph, whose modularity is 0 by definition, outscores
|
|
320
|
+
* every real cut and a caller reading the dendrogram the standard way is handed one community
|
|
321
|
+
* containing everything. The per-community form above counts every pair exactly once and still
|
|
322
|
+
* costs one pass over the edges rather than a pass over every pair.
|
|
323
|
+
* @param graph - The original graph
|
|
324
|
+
* @param communityMap - Map from node IDs to community indices
|
|
325
|
+
* @returns The modularity score of the partition
|
|
326
|
+
*/
|
|
327
|
+
function calculateModularity(graph: Graph, communityMap: Map<NodeId, number>): number {
|
|
328
|
+
const totalEdgeWeight = getTotalEdgeWeight(graph);
|
|
329
|
+
if (totalEdgeWeight === 0) {
|
|
330
|
+
return 0;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Summed weighted degree of each community's nodes.
|
|
334
|
+
const degreeSum = new Map<number, number>();
|
|
335
|
+
for (const node of graph.nodes()) {
|
|
336
|
+
const community = communityMap.get(node.id);
|
|
337
|
+
if (community === undefined) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
degreeSum.set(community, (degreeSum.get(community) ?? 0) + getNodeDegree(graph, node.id));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Summed weight of the edges that stay inside a community.
|
|
345
|
+
const internalWeight = new Map<number, number>();
|
|
346
|
+
for (const edge of graph.edges()) {
|
|
347
|
+
const communityI = communityMap.get(edge.source);
|
|
348
|
+
const communityJ = communityMap.get(edge.target);
|
|
349
|
+
|
|
350
|
+
if (communityI === undefined || communityI !== communityJ) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
internalWeight.set(communityI, (internalWeight.get(communityI) ?? 0) + (edge.weight ?? 1));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let modularity = 0;
|
|
358
|
+
for (const [community, degrees] of degreeSum) {
|
|
359
|
+
const share = degrees / (2 * totalEdgeWeight);
|
|
360
|
+
modularity += (internalWeight.get(community) ?? 0) / totalEdgeWeight - share * share;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return modularity;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Calculate total edge weight in the graph
|
|
368
|
+
* @param graph - The input graph
|
|
369
|
+
* @returns Total sum of all edge weights
|
|
370
|
+
*/
|
|
371
|
+
function getTotalEdgeWeight(graph: Graph): number {
|
|
372
|
+
let totalWeight = 0;
|
|
373
|
+
|
|
374
|
+
for (const edge of graph.edges()) {
|
|
375
|
+
totalWeight += edge.weight ?? 1;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return totalWeight;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Get the total degree (sum of edge weights) for a node
|
|
383
|
+
* @param graph - The input graph
|
|
384
|
+
* @param nodeId - The node ID to compute degree for
|
|
385
|
+
* @returns The weighted degree of the node
|
|
386
|
+
*/
|
|
387
|
+
function getNodeDegree(graph: Graph, nodeId: NodeId): number {
|
|
388
|
+
let degree = 0;
|
|
389
|
+
|
|
390
|
+
for (const neighbor of graph.neighbors(nodeId)) {
|
|
391
|
+
const edge = graph.getEdge(nodeId, neighbor);
|
|
392
|
+
degree += edge?.weight ?? 1;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return degree;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Convert connected components result to ComponentResult format
|
|
400
|
+
* @param graph - The input graph
|
|
401
|
+
* @returns Object containing components array and node-to-component map
|
|
402
|
+
*/
|
|
403
|
+
function getConnectedComponentsResult(graph: Graph): ComponentResult {
|
|
404
|
+
const components = connectedComponents(graph);
|
|
405
|
+
const componentMap = new Map<NodeId, number>();
|
|
406
|
+
|
|
407
|
+
components.forEach((component, index) => {
|
|
408
|
+
component.forEach((nodeId) => {
|
|
409
|
+
componentMap.set(nodeId, index);
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
return { components, componentMap };
|
|
414
|
+
}
|