@graphty/algorithms 2.1.2 → 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
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Link prediction over a snapshot: common-neighbour and Adamic-Adar scores for one pair, a list of
|
|
3
|
+
* pairs, every pair, or one node's candidates, and the ranking metrics of a held-out evaluation.
|
|
4
|
+
*
|
|
5
|
+
* Every score is a merge of two sorted rows with simple-graph semantics: a neighbour reached by
|
|
6
|
+
* parallel arcs counts once, and a self-loop makes a node its own neighbour. With
|
|
7
|
+
* `{ directed: true }` a pair (u, v) intersects out(u) with in(v) -- the nodes on a path u -> z -> v
|
|
8
|
+
* -- and otherwise the two out rows (the two rows of an undirected snapshot).
|
|
9
|
+
*
|
|
10
|
+
* A node index at or beyond `nodeCount`, `INVALID_INDEX` included, is an absent node: its pairs
|
|
11
|
+
* score 0 and it has no candidates.
|
|
12
|
+
* @module
|
|
13
|
+
*/
|
|
14
|
+
import { sortedRowMerge } from "./common-neighbors.js";
|
|
15
|
+
/**
|
|
16
|
+
* Adamic-Adar weights are multiples of 2^-36, so any sum of them below 2^16 is exact and the same in
|
|
17
|
+
* whatever order it is added: two pairs whose common neighbours have the same degrees score
|
|
18
|
+
* bit-identically, and a ranking never splits them by rounding. The snap moves a weight by at most
|
|
19
|
+
* 2^-37, far inside the 1e-9 relative tolerance of a score.
|
|
20
|
+
*/
|
|
21
|
+
const WEIGHT_GRID = 2 ** 36;
|
|
22
|
+
/**
|
|
23
|
+
* The Adamic-Adar weight of a common neighbour of the given degree: 1 / ln(degree) snapped to the
|
|
24
|
+
* weight grid, 1 for degree 1 (ln 1 is 0) and 0 for degree 0. The legacy `adamicAdarScore` adds
|
|
25
|
+
* the unsnapped weights in neighbour-visit order, so its scores differ from these in the last bits
|
|
26
|
+
* and it may order two exactly tied pairs either way.
|
|
27
|
+
* @param degree - The neighbour's degree
|
|
28
|
+
* @returns The weight
|
|
29
|
+
*/
|
|
30
|
+
function adamicAdarWeight(degree) {
|
|
31
|
+
// ponytail: exact only while a pair's score stays below 2^16 (tens of thousands of hub neighbours);
|
|
32
|
+
// a coarser grid chosen from the graph's largest row sum lifts that ceiling if it is ever reached.
|
|
33
|
+
if (degree > 1) {
|
|
34
|
+
return Math.round(WEIGHT_GRID / Math.log(degree)) / WEIGHT_GRID;
|
|
35
|
+
}
|
|
36
|
+
return degree === 1 ? 1 : 0;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The number of distinct entries of one row of a sorted adjacency.
|
|
40
|
+
* @param view - The adjacency
|
|
41
|
+
* @param z - The row
|
|
42
|
+
* @returns The count
|
|
43
|
+
*/
|
|
44
|
+
function distinctRowLength(view, z) {
|
|
45
|
+
const start = view.rowPtr[z];
|
|
46
|
+
const end = view.rowPtr[z + 1];
|
|
47
|
+
let count = 0;
|
|
48
|
+
for (let a = start; a < end; a++) {
|
|
49
|
+
if (a === start || view.colIdx[a] !== view.colIdx[a - 1]) {
|
|
50
|
+
count++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return count;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The Adamic-Adar weight of a node as a common neighbour, from its degree in distinct neighbours:
|
|
57
|
+
* out-neighbours for `{ directed: true }` or an undirected snapshot, else out- plus in-neighbours,
|
|
58
|
+
* a self-loop in both. Costs the node's degree, so one pair costs the degrees of its common
|
|
59
|
+
* neighbours, not the whole graph.
|
|
60
|
+
* @param s - The snapshot
|
|
61
|
+
* @param directed - The `directed` option
|
|
62
|
+
* @returns The weight of node z
|
|
63
|
+
*/
|
|
64
|
+
function adamicAdarWeightOf(s, directed) {
|
|
65
|
+
const rev = s.directed && !directed ? s.reverse() : undefined;
|
|
66
|
+
return (z) => adamicAdarWeight(distinctRowLength(s, z) + (rev === undefined ? 0 : distinctRowLength(rev, z)));
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The pair score: a count of common neighbours, or their weight sum when `weight` is given.
|
|
70
|
+
* @param s - The snapshot
|
|
71
|
+
* @param o - Options
|
|
72
|
+
* @param weight - Optional weight of a common neighbour
|
|
73
|
+
* @returns The score of (u, v); 0 when either is absent
|
|
74
|
+
*/
|
|
75
|
+
function scorer(s, o, weight) {
|
|
76
|
+
const bwd = o.directed === true ? s.reverse() : s;
|
|
77
|
+
const n = s.nodeCount;
|
|
78
|
+
return (u, v) => (u < n && v < n ? sortedRowMerge(s, bwd, u, v, weight) : 0);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* The Adamic-Adar pair score for many pairs: every node's weight computed once, up front.
|
|
82
|
+
* @param s - The snapshot
|
|
83
|
+
* @param o - Options
|
|
84
|
+
* @returns The score of (u, v)
|
|
85
|
+
*/
|
|
86
|
+
function adamicAdarScorer(s, o) {
|
|
87
|
+
const weightOf = adamicAdarWeightOf(s, o.directed === true);
|
|
88
|
+
const w = Float64Array.from({ length: s.nodeCount }, (_, z) => weightOf(z));
|
|
89
|
+
return scorer(s, o, (z) => w[z]);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Sort pairs by score, highest first and stable, and keep `order.slice(0, end)`.
|
|
93
|
+
* @param sources - Pair sources in enumeration order
|
|
94
|
+
* @param targets - Pair targets
|
|
95
|
+
* @param scores - Pair scores
|
|
96
|
+
* @param end - The slice end
|
|
97
|
+
* @returns The ranked pairs
|
|
98
|
+
*/
|
|
99
|
+
function ranked(sources, targets, scores, end) {
|
|
100
|
+
const order = Array.from(scores, (_, k) => k)
|
|
101
|
+
.sort((a, b) => scores[b] - scores[a])
|
|
102
|
+
.slice(0, end);
|
|
103
|
+
return {
|
|
104
|
+
sources: Uint32Array.from(order, (k) => sources[k]),
|
|
105
|
+
targets: Uint32Array.from(order, (k) => targets[k]),
|
|
106
|
+
scores: Float64Array.from(order, (k) => scores[k]),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function predict(s, o, score) {
|
|
110
|
+
const sources = [];
|
|
111
|
+
const targets = [];
|
|
112
|
+
const scores = [];
|
|
113
|
+
// Each unordered pair is scored once as (u, v), u < v; without `directed` the reverse pair is
|
|
114
|
+
// listed too with the same score. Existence is tested on the arc u -> v only.
|
|
115
|
+
const mirror = o.directed !== true;
|
|
116
|
+
for (let u = 0; u < s.nodeCount; u++) {
|
|
117
|
+
for (let v = u + 1; v < s.nodeCount; v++) {
|
|
118
|
+
if (o.includeExisting !== true && s.hasArc(u, v)) {
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const x = score(u, v);
|
|
122
|
+
if (x > 0) {
|
|
123
|
+
sources.push(u);
|
|
124
|
+
targets.push(v);
|
|
125
|
+
scores.push(x);
|
|
126
|
+
if (mirror) {
|
|
127
|
+
sources.push(v);
|
|
128
|
+
targets.push(u);
|
|
129
|
+
scores.push(x);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const { topK } = o;
|
|
135
|
+
return ranked(sources, targets, scores, topK !== undefined && topK > 0 ? topK : scores.length);
|
|
136
|
+
}
|
|
137
|
+
function forPairs(pairs, score) {
|
|
138
|
+
return Float64Array.from(pairs.sources, (u, k) => score(u, pairs.targets[k]));
|
|
139
|
+
}
|
|
140
|
+
function candidatesOf(s, u, o, score) {
|
|
141
|
+
const n = s.nodeCount;
|
|
142
|
+
const sources = [];
|
|
143
|
+
const targets = [];
|
|
144
|
+
const scores = [];
|
|
145
|
+
if (u < n) {
|
|
146
|
+
const list = o.candidates ?? Array.from({ length: n }, (_, i) => i);
|
|
147
|
+
for (let k = 0; k < list.length; k++) {
|
|
148
|
+
const v = list[k];
|
|
149
|
+
if (v === u || (o.includeExisting !== true && v < n && s.hasArc(u, v))) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const x = score(u, v);
|
|
153
|
+
if (x > 0) {
|
|
154
|
+
sources.push(u);
|
|
155
|
+
targets.push(v);
|
|
156
|
+
scores.push(x);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return ranked(sources, targets, scores, o.topK ?? 10);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Rank held-out edges against non-edges by score (stable, edges first among equal scores), then
|
|
164
|
+
* report the threshold with the best F1 and the AUC; the AUC is 0.5 when either side is empty.
|
|
165
|
+
* @param edges - The scores of the held-out edges
|
|
166
|
+
* @param nonEdges - The scores of the non-edges
|
|
167
|
+
* @returns The metrics
|
|
168
|
+
*/
|
|
169
|
+
function rankingMetrics(edges, nonEdges) {
|
|
170
|
+
const all = [...edges, ...nonEdges];
|
|
171
|
+
const order = Array.from(all, (_, k) => k).sort((a, b) => all[b] - all[a]);
|
|
172
|
+
let tp = 0;
|
|
173
|
+
let fp = 0;
|
|
174
|
+
let bestF1 = 0;
|
|
175
|
+
let bestPrecision = 0;
|
|
176
|
+
let bestRecall = 0;
|
|
177
|
+
let auc = 0;
|
|
178
|
+
for (const k of order) {
|
|
179
|
+
if (k < edges.length) {
|
|
180
|
+
tp++;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
auc += tp;
|
|
184
|
+
fp++;
|
|
185
|
+
}
|
|
186
|
+
const precision = tp / (tp + fp);
|
|
187
|
+
const recall = tp / edges.length;
|
|
188
|
+
const f1 = precision + recall > 0 ? (2 * (precision * recall)) / (precision + recall) : 0;
|
|
189
|
+
if (f1 > bestF1) {
|
|
190
|
+
bestF1 = f1;
|
|
191
|
+
bestPrecision = precision;
|
|
192
|
+
bestRecall = recall;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
auc = tp > 0 && fp > 0 ? auc / (tp * fp) : 0.5;
|
|
196
|
+
return { precision: bestPrecision, recall: bestRecall, f1Score: bestF1, auc };
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Common-neighbour scores of every pair not already joined, ranked.
|
|
200
|
+
* @param s - The snapshot
|
|
201
|
+
* @param o - Options
|
|
202
|
+
* @returns The pairs with a positive score, highest first
|
|
203
|
+
* @public
|
|
204
|
+
*/
|
|
205
|
+
export function commonNeighborsPrediction(s, o = {}) {
|
|
206
|
+
return predict(s, o, scorer(s, o));
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Common-neighbour scores of the given pairs, in order.
|
|
210
|
+
* @param s - The snapshot
|
|
211
|
+
* @param pairs - The pairs
|
|
212
|
+
* @param o - Options
|
|
213
|
+
* @returns One score per pair
|
|
214
|
+
* @public
|
|
215
|
+
*/
|
|
216
|
+
export function commonNeighborsForPairs(s, pairs, o = {}) {
|
|
217
|
+
return forPairs(pairs, scorer(s, o));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* One node's best common-neighbour candidates, ranked.
|
|
221
|
+
* @param s - The snapshot
|
|
222
|
+
* @param u - The node index
|
|
223
|
+
* @param o - Options
|
|
224
|
+
* @returns (u, v) pairs with a positive score, highest first
|
|
225
|
+
* @public
|
|
226
|
+
*/
|
|
227
|
+
export function getTopCandidatesForNode(s, u, o = {}) {
|
|
228
|
+
return candidatesOf(s, u, o, scorer(s, o));
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Ranking metrics of the common-neighbour score on held-out edges against non-edges.
|
|
232
|
+
* @param s - The training snapshot
|
|
233
|
+
* @param edges - The held-out edges
|
|
234
|
+
* @param nonEdges - The non-edges
|
|
235
|
+
* @param o - Options
|
|
236
|
+
* @returns The metrics
|
|
237
|
+
* @public
|
|
238
|
+
*/
|
|
239
|
+
export function evaluateCommonNeighbors(s, edges, nonEdges, o = {}) {
|
|
240
|
+
const score = scorer(s, o);
|
|
241
|
+
return rankingMetrics(forPairs(edges, score), forPairs(nonEdges, score));
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The Adamic-Adar index of two nodes: the sum over distinct common neighbours z of 1 / ln(degree(z)).
|
|
245
|
+
* @param s - The snapshot
|
|
246
|
+
* @param u - A node index
|
|
247
|
+
* @param v - A node index
|
|
248
|
+
* @param o - Options
|
|
249
|
+
* @returns The score
|
|
250
|
+
* @public
|
|
251
|
+
*/
|
|
252
|
+
export function adamicAdarScore(s, u, v, o = {}) {
|
|
253
|
+
return scorer(s, o, adamicAdarWeightOf(s, o.directed === true))(u, v);
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Adamic-Adar scores of every pair not already joined, ranked.
|
|
257
|
+
* @param s - The snapshot
|
|
258
|
+
* @param o - Options
|
|
259
|
+
* @returns The pairs with a positive score, highest first
|
|
260
|
+
* @public
|
|
261
|
+
*/
|
|
262
|
+
export function adamicAdarPrediction(s, o = {}) {
|
|
263
|
+
return predict(s, o, adamicAdarScorer(s, o));
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Adamic-Adar scores of the given pairs, in order.
|
|
267
|
+
* @param s - The snapshot
|
|
268
|
+
* @param pairs - The pairs
|
|
269
|
+
* @param o - Options
|
|
270
|
+
* @returns One score per pair
|
|
271
|
+
* @public
|
|
272
|
+
*/
|
|
273
|
+
export function adamicAdarForPairs(s, pairs, o = {}) {
|
|
274
|
+
return forPairs(pairs, adamicAdarScorer(s, o));
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* One node's best Adamic-Adar candidates, ranked.
|
|
278
|
+
* @param s - The snapshot
|
|
279
|
+
* @param u - The node index
|
|
280
|
+
* @param o - Options
|
|
281
|
+
* @returns (u, v) pairs with a positive score, highest first
|
|
282
|
+
* @public
|
|
283
|
+
*/
|
|
284
|
+
export function getTopAdamicAdarCandidatesForNode(s, u, o = {}) {
|
|
285
|
+
return candidatesOf(s, u, o, adamicAdarScorer(s, o));
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Ranking metrics of the Adamic-Adar score on held-out edges against non-edges.
|
|
289
|
+
* @param s - The training snapshot
|
|
290
|
+
* @param edges - The held-out edges
|
|
291
|
+
* @param nonEdges - The non-edges
|
|
292
|
+
* @param o - Options
|
|
293
|
+
* @returns The metrics
|
|
294
|
+
* @public
|
|
295
|
+
*/
|
|
296
|
+
export function evaluateAdamicAdar(s, edges, nonEdges, o = {}) {
|
|
297
|
+
const score = adamicAdarScorer(s, o);
|
|
298
|
+
return rankingMetrics(forPairs(edges, score), forPairs(nonEdges, score));
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Both evaluations on the same held-out sets. The common-neighbour side always intersects the two
|
|
302
|
+
* OUT rows, whatever `directed` says, as the legacy function of this name does; the Adamic-Adar side
|
|
303
|
+
* honours `directed`.
|
|
304
|
+
* @param s - The training snapshot
|
|
305
|
+
* @param edges - The held-out edges
|
|
306
|
+
* @param nonEdges - The non-edges
|
|
307
|
+
* @param o - Options
|
|
308
|
+
* @returns The two sets of metrics
|
|
309
|
+
* @public
|
|
310
|
+
*/
|
|
311
|
+
export function compareAdamicAdarWithCommonNeighbors(s, edges, nonEdges, o = {}) {
|
|
312
|
+
return {
|
|
313
|
+
adamicAdar: evaluateAdamicAdar(s, edges, nonEdges, o),
|
|
314
|
+
commonNeighbors: evaluateCommonNeighbors(s, edges, nonEdges),
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
//# sourceMappingURL=link-prediction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"link-prediction.js","sourceRoot":"","sources":["../../../src/indexed/link-prediction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { AdjacencyView, NumericVector } from "@graphty/graph-format";
|
|
2
|
+
import { type LabelResult } from "./components.js";
|
|
3
|
+
/** Options of the index-based Markov clustering, with the legacy `markovClustering` defaults. @public */
|
|
4
|
+
export interface MarkovOptions {
|
|
5
|
+
/** Matrix power of each expansion step, an integer of at least 1; default 2. */
|
|
6
|
+
readonly expansion?: number | undefined;
|
|
7
|
+
/** Element-wise power of each inflation step, above 0; default 2. */
|
|
8
|
+
readonly inflation?: number | undefined;
|
|
9
|
+
/** Cap on expansion-inflation rounds; default 100. */
|
|
10
|
+
readonly maxIterations?: number | undefined;
|
|
11
|
+
/** Stop when no matrix entry moved by more than this in a round; default 1e-6. */
|
|
12
|
+
readonly tolerance?: number | undefined;
|
|
13
|
+
/** Entries below this are dropped after each inflation; default 1e-5. */
|
|
14
|
+
readonly pruningThreshold?: number | undefined;
|
|
15
|
+
/** Give every node a self-loop of weight 1 before the first round; default true. */
|
|
16
|
+
readonly selfLoops?: boolean | undefined;
|
|
17
|
+
/** Per-arc weight override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`. */
|
|
18
|
+
readonly weights?: NumericVector | undefined;
|
|
19
|
+
}
|
|
20
|
+
/** Result of the index-based Markov clustering. @public */
|
|
21
|
+
export interface MarkovResult extends LabelResult {
|
|
22
|
+
/** Nodes whose own flow survived (a positive diagonal entry), in index order. */
|
|
23
|
+
readonly attractors: Uint32Array;
|
|
24
|
+
/** Expansion-inflation rounds run. */
|
|
25
|
+
readonly iterations: number;
|
|
26
|
+
/** True when the last round moved no entry by more than `tolerance`. */
|
|
27
|
+
readonly converged: boolean;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Markov clustering (MCL; van Dongen, 2000), the index-based port of the legacy `markovClustering`.
|
|
31
|
+
*
|
|
32
|
+
* The flow matrix starts as the column-normalised adjacency: entry (i, j) is the weight of the arc
|
|
33
|
+
* i -> j, so column j spreads j's flow over the nodes with an arc into it (on an undirected
|
|
34
|
+
* snapshot, its neighbours). With `selfLoops` every diagonal entry is first set to 1, replacing a
|
|
35
|
+
* self-loop's weight. Each round raises the matrix to the `expansion` power, raises every entry to
|
|
36
|
+
* the `inflation` power, renormalises the columns, drops the entries below `pruningThreshold` and
|
|
37
|
+
* renormalises again, until no entry moves by more than `tolerance` or `maxIterations` rounds ran.
|
|
38
|
+
* Node j then joins the attractor with the largest entry in its column, the lowest index on a tie;
|
|
39
|
+
* a node whose column emptied is a community of its own.
|
|
40
|
+
*
|
|
41
|
+
* The matrix is kept sparse, and every sum is taken in the order the legacy dense loops take it, so
|
|
42
|
+
* the rounds are bit-identical to the legacy function's and so are the communities, their order and
|
|
43
|
+
* the attractors. Communities are labelled in the legacy function's order: the flow clusters by
|
|
44
|
+
* their lowest member, then the nodes whose column emptied. Two differences: parallel arcs are
|
|
45
|
+
* summed (a legacy graph has none), and `iterations` counts the rounds run, where the legacy
|
|
46
|
+
* function reports one more than `maxIterations` when it stops at the cap. A negative, NaN or
|
|
47
|
+
* infinite weight throws.
|
|
48
|
+
* @param s - Any snapshot or adjacency view
|
|
49
|
+
* @param options - MCL parameters and the weight override
|
|
50
|
+
* @returns The partition, the attractors, the rounds run and whether they converged
|
|
51
|
+
* @public
|
|
52
|
+
*/
|
|
53
|
+
export declare function markovClustering(s: AdjacencyView, options?: MarkovOptions): MarkovResult;
|
|
54
|
+
//# sourceMappingURL=markov.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markov.d.ts","sourceRoot":"","sources":["../../../src/indexed/markov.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE1E,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,iBAAiB,CAAC;AAE/D,yGAAyG;AACzG,MAAM,WAAW,aAAa;IAC1B,gFAAgF;IAChF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,qEAAqE;IACrE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,sDAAsD;IACtD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,kFAAkF;IAClF,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,yEAAyE;IACzE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/C,oFAAoF;IACpF,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IACzC,iGAAiG;IACjG,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAChD;AAED,2DAA2D;AAC3D,MAAM,WAAW,YAAa,SAAQ,WAAW;IAC7C,iFAAiF;IACjF,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;IACjC,sCAAsC;IACtC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAmMD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,GAAE,aAAkB,GAAG,YAAY,CA0E5F"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import { withGroups } from "./components.js";
|
|
2
|
+
/**
|
|
3
|
+
* Divide every column by its sum, summed in ascending row order as the legacy dense loop sums it.
|
|
4
|
+
* @param m - The matrix, changed in place
|
|
5
|
+
*/
|
|
6
|
+
function normaliseColumns(m) {
|
|
7
|
+
for (let j = 0; j + 1 < m.ptr.length; j++) {
|
|
8
|
+
let sum = 0;
|
|
9
|
+
for (let p = m.ptr[j]; p < m.ptr[j + 1]; p++) {
|
|
10
|
+
sum += m.val[p];
|
|
11
|
+
}
|
|
12
|
+
if (sum > 0) {
|
|
13
|
+
for (let p = m.ptr[j]; p < m.ptr[j + 1]; p++) {
|
|
14
|
+
m.val[p] /= sum;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The product `a * b`. For every entry the products are added in ascending k, the order the legacy
|
|
21
|
+
* dense triple loop adds them in; the zero products it adds as well leave a sum unchanged, so the
|
|
22
|
+
* result is bit-identical to the dense one.
|
|
23
|
+
* @param a - Left factor
|
|
24
|
+
* @param b - Right factor
|
|
25
|
+
* @param acc - n-long scratch, all zero on entry and on exit
|
|
26
|
+
* @param mark - n-long scratch, all zero on entry and on exit
|
|
27
|
+
* @returns The product
|
|
28
|
+
*/
|
|
29
|
+
function multiply(a, b, acc, mark) {
|
|
30
|
+
const n = acc.length;
|
|
31
|
+
const ptr = new Uint32Array(n + 1);
|
|
32
|
+
const rows = [];
|
|
33
|
+
const vals = [];
|
|
34
|
+
const touched = [];
|
|
35
|
+
for (let j = 0; j < n; j++) {
|
|
36
|
+
touched.length = 0;
|
|
37
|
+
for (let q = b.ptr[j]; q < b.ptr[j + 1]; q++) {
|
|
38
|
+
const k = b.row[q];
|
|
39
|
+
const bkj = b.val[q];
|
|
40
|
+
for (let p = a.ptr[k]; p < a.ptr[k + 1]; p++) {
|
|
41
|
+
const i = a.row[p];
|
|
42
|
+
if (mark[i] === 0) {
|
|
43
|
+
mark[i] = 1;
|
|
44
|
+
touched.push(i);
|
|
45
|
+
}
|
|
46
|
+
acc[i] += a.val[p] * bkj;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
touched.sort((x, y) => x - y);
|
|
50
|
+
for (const i of touched) {
|
|
51
|
+
rows.push(i);
|
|
52
|
+
vals.push(acc[i]);
|
|
53
|
+
acc[i] = 0;
|
|
54
|
+
mark[i] = 0;
|
|
55
|
+
}
|
|
56
|
+
ptr[j + 1] = rows.length;
|
|
57
|
+
}
|
|
58
|
+
return { ptr, row: Uint32Array.from(rows), val: Float64Array.from(vals) };
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Raise every entry to `inflation`, renormalise, drop the entries below `threshold` and renormalise
|
|
62
|
+
* again: the legacy inflate and prune steps.
|
|
63
|
+
* @param m - The expanded matrix
|
|
64
|
+
* @param inflation - The element-wise power
|
|
65
|
+
* @param threshold - The pruning threshold
|
|
66
|
+
* @returns The new matrix
|
|
67
|
+
*/
|
|
68
|
+
function inflateAndPrune(m, inflation, threshold) {
|
|
69
|
+
const powered = { ptr: m.ptr, row: m.row, val: m.val.map((v) => Math.pow(v, inflation)) };
|
|
70
|
+
normaliseColumns(powered);
|
|
71
|
+
const n = m.ptr.length - 1;
|
|
72
|
+
const ptr = new Uint32Array(n + 1);
|
|
73
|
+
const rows = [];
|
|
74
|
+
const vals = [];
|
|
75
|
+
for (let j = 0; j < n; j++) {
|
|
76
|
+
for (let p = powered.ptr[j]; p < powered.ptr[j + 1]; p++) {
|
|
77
|
+
if (powered.val[p] >= threshold) {
|
|
78
|
+
rows.push(powered.row[p]);
|
|
79
|
+
vals.push(powered.val[p]);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
ptr[j + 1] = rows.length;
|
|
83
|
+
}
|
|
84
|
+
const out = { ptr, row: Uint32Array.from(rows), val: Float64Array.from(vals) };
|
|
85
|
+
normaliseColumns(out);
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Whether no entry differs by more than `tolerance` between two matrices, an absent entry being 0.
|
|
90
|
+
* @param a - The previous matrix
|
|
91
|
+
* @param b - The new matrix
|
|
92
|
+
* @param tolerance - The tolerance
|
|
93
|
+
* @returns True when every entry is within tolerance
|
|
94
|
+
*/
|
|
95
|
+
function withinTolerance(a, b, tolerance) {
|
|
96
|
+
for (let j = 0; j + 1 < a.ptr.length; j++) {
|
|
97
|
+
let p = a.ptr[j];
|
|
98
|
+
let q = b.ptr[j];
|
|
99
|
+
const pe = a.ptr[j + 1];
|
|
100
|
+
const qe = b.ptr[j + 1];
|
|
101
|
+
while (p < pe || q < qe) {
|
|
102
|
+
let diff;
|
|
103
|
+
if (q >= qe || (p < pe && a.row[p] < b.row[q])) {
|
|
104
|
+
diff = a.val[p++];
|
|
105
|
+
}
|
|
106
|
+
else if (p >= pe || b.row[q] < a.row[p]) {
|
|
107
|
+
diff = b.val[q++];
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
diff = a.val[p++] - b.val[q++];
|
|
111
|
+
}
|
|
112
|
+
if (Math.abs(diff) > tolerance) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* The starting matrix: entry (i, j) is the weight of the arc i -> j (parallel arcs summed), the
|
|
121
|
+
* diagonal set to 1 when `selfLoops`, every column normalised.
|
|
122
|
+
* @param s - The adjacency
|
|
123
|
+
* @param weights - Per-arc weights, or null for all ones
|
|
124
|
+
* @param selfLoops - Whether to set the diagonal to 1
|
|
125
|
+
* @returns The column-stochastic matrix
|
|
126
|
+
*/
|
|
127
|
+
function transitionMatrix(s, weights, selfLoops) {
|
|
128
|
+
const n = s.nodeCount;
|
|
129
|
+
// Column j of the matrix is the set of arcs INTO j: a counting sort of the arcs by target, which
|
|
130
|
+
// keeps each column's rows ascending because the rows are visited in order.
|
|
131
|
+
const ptr = new Uint32Array(n + 1);
|
|
132
|
+
for (let a = 0; a < s.arcCount; a++) {
|
|
133
|
+
ptr[s.colIdx[a] + 1]++;
|
|
134
|
+
}
|
|
135
|
+
if (selfLoops) {
|
|
136
|
+
for (let j = 0; j < n; j++) {
|
|
137
|
+
ptr[j + 1]++;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
for (let j = 0; j < n; j++) {
|
|
141
|
+
ptr[j + 1] += ptr[j];
|
|
142
|
+
}
|
|
143
|
+
const fill = ptr.slice(0, n);
|
|
144
|
+
const row = new Uint32Array(ptr[n]);
|
|
145
|
+
const val = new Float64Array(ptr[n]);
|
|
146
|
+
const put = (i, j, w, replace) => {
|
|
147
|
+
const last = fill[j] - 1;
|
|
148
|
+
if (last >= ptr[j] && row[last] === i) {
|
|
149
|
+
val[last] = replace ? w : val[last] + w;
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
row[fill[j]] = i;
|
|
153
|
+
val[fill[j]++] = w;
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
for (let i = 0; i < n; i++) {
|
|
157
|
+
// The diagonal goes in its place in row order: the arcs i -> j with j < i were placed while
|
|
158
|
+
// visiting earlier rows, so for column i this is where row i belongs.
|
|
159
|
+
for (let a = s.rowPtr[i]; a < s.rowPtr[i + 1]; a++) {
|
|
160
|
+
const j = s.colIdx[a];
|
|
161
|
+
const w = weights === null ? 1 : weights[a];
|
|
162
|
+
if (!(w >= 0) || w === Infinity) {
|
|
163
|
+
throw new RangeError(`arc ${a} has weight ${w}; Markov clustering needs finite, non-negative weights`);
|
|
164
|
+
}
|
|
165
|
+
put(i, j, w, false);
|
|
166
|
+
}
|
|
167
|
+
if (selfLoops) {
|
|
168
|
+
put(i, i, 1, true);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Compact: merged parallel arcs and a self-loop folded into the diagonal left gaps.
|
|
172
|
+
const outPtr = new Uint32Array(n + 1);
|
|
173
|
+
const rows = [];
|
|
174
|
+
const vals = [];
|
|
175
|
+
for (let j = 0; j < n; j++) {
|
|
176
|
+
for (let p = ptr[j]; p < fill[j]; p++) {
|
|
177
|
+
rows.push(row[p]);
|
|
178
|
+
vals.push(val[p]);
|
|
179
|
+
}
|
|
180
|
+
outPtr[j + 1] = rows.length;
|
|
181
|
+
}
|
|
182
|
+
const m = { ptr: outPtr, row: Uint32Array.from(rows), val: Float64Array.from(vals) };
|
|
183
|
+
normaliseColumns(m);
|
|
184
|
+
return m;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Markov clustering (MCL; van Dongen, 2000), the index-based port of the legacy `markovClustering`.
|
|
188
|
+
*
|
|
189
|
+
* The flow matrix starts as the column-normalised adjacency: entry (i, j) is the weight of the arc
|
|
190
|
+
* i -> j, so column j spreads j's flow over the nodes with an arc into it (on an undirected
|
|
191
|
+
* snapshot, its neighbours). With `selfLoops` every diagonal entry is first set to 1, replacing a
|
|
192
|
+
* self-loop's weight. Each round raises the matrix to the `expansion` power, raises every entry to
|
|
193
|
+
* the `inflation` power, renormalises the columns, drops the entries below `pruningThreshold` and
|
|
194
|
+
* renormalises again, until no entry moves by more than `tolerance` or `maxIterations` rounds ran.
|
|
195
|
+
* Node j then joins the attractor with the largest entry in its column, the lowest index on a tie;
|
|
196
|
+
* a node whose column emptied is a community of its own.
|
|
197
|
+
*
|
|
198
|
+
* The matrix is kept sparse, and every sum is taken in the order the legacy dense loops take it, so
|
|
199
|
+
* the rounds are bit-identical to the legacy function's and so are the communities, their order and
|
|
200
|
+
* the attractors. Communities are labelled in the legacy function's order: the flow clusters by
|
|
201
|
+
* their lowest member, then the nodes whose column emptied. Two differences: parallel arcs are
|
|
202
|
+
* summed (a legacy graph has none), and `iterations` counts the rounds run, where the legacy
|
|
203
|
+
* function reports one more than `maxIterations` when it stops at the cap. A negative, NaN or
|
|
204
|
+
* infinite weight throws.
|
|
205
|
+
* @param s - Any snapshot or adjacency view
|
|
206
|
+
* @param options - MCL parameters and the weight override
|
|
207
|
+
* @returns The partition, the attractors, the rounds run and whether they converged
|
|
208
|
+
* @public
|
|
209
|
+
*/
|
|
210
|
+
export function markovClustering(s, options = {}) {
|
|
211
|
+
const expansion = options.expansion ?? 2;
|
|
212
|
+
const inflation = options.inflation ?? 2;
|
|
213
|
+
const maxIterations = options.maxIterations ?? 100;
|
|
214
|
+
const tolerance = options.tolerance ?? 1e-6;
|
|
215
|
+
const pruningThreshold = options.pruningThreshold ?? 1e-5;
|
|
216
|
+
if (!Number.isInteger(expansion) || expansion < 1) {
|
|
217
|
+
throw new RangeError(`expansion must be an integer of at least 1, got ${expansion}`);
|
|
218
|
+
}
|
|
219
|
+
if (!(inflation > 0) || inflation === Infinity) {
|
|
220
|
+
throw new RangeError(`inflation must be finite and above 0, got ${inflation}`);
|
|
221
|
+
}
|
|
222
|
+
if (!Number.isInteger(maxIterations) || maxIterations < 0) {
|
|
223
|
+
throw new RangeError(`maxIterations must be a non-negative integer, got ${maxIterations}`);
|
|
224
|
+
}
|
|
225
|
+
if (!(tolerance >= 0) || !(pruningThreshold >= 0)) {
|
|
226
|
+
throw new RangeError("tolerance and pruningThreshold must be non-negative");
|
|
227
|
+
}
|
|
228
|
+
const weights = options.weights ?? s.weights;
|
|
229
|
+
if (weights !== null && weights.length !== s.arcCount) {
|
|
230
|
+
throw new RangeError(`weights has ${weights.length} entries; the snapshot has ${s.arcCount} arcs`);
|
|
231
|
+
}
|
|
232
|
+
const n = s.nodeCount;
|
|
233
|
+
let m = transitionMatrix(s, weights, options.selfLoops ?? true);
|
|
234
|
+
const acc = new Float64Array(n);
|
|
235
|
+
const mark = new Uint8Array(n);
|
|
236
|
+
let converged = n === 0;
|
|
237
|
+
let iterations = 0;
|
|
238
|
+
while (!converged && iterations < maxIterations) {
|
|
239
|
+
let expanded = m;
|
|
240
|
+
for (let t = 1; t < expansion; t++) {
|
|
241
|
+
expanded = multiply(expanded, m, acc, mark);
|
|
242
|
+
}
|
|
243
|
+
const next = inflateAndPrune(expanded, inflation, pruningThreshold);
|
|
244
|
+
iterations++;
|
|
245
|
+
converged = withinTolerance(m, next, tolerance);
|
|
246
|
+
m = next;
|
|
247
|
+
}
|
|
248
|
+
const attractors = [];
|
|
249
|
+
// Column j's attractor: the row of its largest positive entry, the first on a tie.
|
|
250
|
+
const attractorOf = new Int32Array(n).fill(-1);
|
|
251
|
+
for (let j = 0; j < n; j++) {
|
|
252
|
+
let best = 0;
|
|
253
|
+
for (let p = m.ptr[j]; p < m.ptr[j + 1]; p++) {
|
|
254
|
+
if (m.row[p] === j && m.val[p] > 0) {
|
|
255
|
+
attractors.push(j);
|
|
256
|
+
}
|
|
257
|
+
if (m.val[p] > best) {
|
|
258
|
+
best = m.val[p];
|
|
259
|
+
attractorOf[j] = m.row[p];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const labels = new Uint32Array(n);
|
|
264
|
+
const labelOfAttractor = new Map();
|
|
265
|
+
for (let j = 0; j < n; j++) {
|
|
266
|
+
const a = attractorOf[j];
|
|
267
|
+
if (a >= 0) {
|
|
268
|
+
let label = labelOfAttractor.get(a);
|
|
269
|
+
if (label === undefined) {
|
|
270
|
+
label = labelOfAttractor.size;
|
|
271
|
+
labelOfAttractor.set(a, label);
|
|
272
|
+
}
|
|
273
|
+
labels[j] = label;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
let count = labelOfAttractor.size;
|
|
277
|
+
for (let j = 0; j < n; j++) {
|
|
278
|
+
if (attractorOf[j] < 0) {
|
|
279
|
+
labels[j] = count++;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return { ...withGroups(labels, count), attractors: Uint32Array.from(attractors), iterations, converged };
|
|
283
|
+
}
|
|
284
|
+
//# sourceMappingURL=markov.js.map
|