@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,201 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type GraphSnapshot,
|
|
3
|
+
INVALID_INDEX,
|
|
4
|
+
makeMask,
|
|
5
|
+
maskSet,
|
|
6
|
+
type NodeMask,
|
|
7
|
+
type NumericVector,
|
|
8
|
+
} from "@graphty/graph-format";
|
|
9
|
+
|
|
10
|
+
import { crossingEdges, edgeCapacities, type MinCutResult } from "./flow.js";
|
|
11
|
+
import { mulberry32 } from "./label-propagation.js";
|
|
12
|
+
import { IndexedMaxHeap } from "./structures/max-heap.js";
|
|
13
|
+
import { IntUnionFind } from "./structures/union-find.js";
|
|
14
|
+
|
|
15
|
+
/** Options of {@link stoerWagner}. @public */
|
|
16
|
+
export interface StoerWagnerOptions {
|
|
17
|
+
/** Per-arc weight override, arcCount long; without it the snapshot's weights (1 when unweighted). */
|
|
18
|
+
readonly weights?: NumericVector | undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Options of {@link kargerMinCut}. @public */
|
|
22
|
+
export interface KargerOptions {
|
|
23
|
+
/** Independent contraction trials; the lightest cut found wins. Default 100. */
|
|
24
|
+
readonly iterations?: number | undefined;
|
|
25
|
+
/** Seed of the edge orders; one seed gives one result, bit for bit. Default 42. */
|
|
26
|
+
readonly randomSeed?: number | undefined;
|
|
27
|
+
/** Per-arc weight override, arcCount long; without it the snapshot's weights (1 when unweighted). */
|
|
28
|
+
readonly weights?: NumericVector | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Global minimum cut by Stoer-Wagner, reading every edge as undirected (a directed snapshot's arcs
|
|
33
|
+
* count in both directions, so two opposite arcs between one pair add up).
|
|
34
|
+
*
|
|
35
|
+
* No graph is contracted (graph-format design 14.2): a representative array and member lists
|
|
36
|
+
* merge the last two nodes of each phase, and each phase's maximum-adjacency order accumulates the
|
|
37
|
+
* weights of the ORIGINAL arcs of the merged members in an {@link IndexedMaxHeap}. A phase starts
|
|
38
|
+
* from the lowest-index remaining representative and takes equal keys lowest index first, and the
|
|
39
|
+
* first phase with the lightest cut wins, which is the order the legacy implementation uses.
|
|
40
|
+
* O(n (m + n) log n).
|
|
41
|
+
* @param s - The snapshot
|
|
42
|
+
* @param options - The weight override
|
|
43
|
+
* @returns The cut value, the side holding the last node of the winning phase, and the cut edges;
|
|
44
|
+
* with fewer than two nodes the value is 0 and every node is on `side`
|
|
45
|
+
* @public
|
|
46
|
+
*/
|
|
47
|
+
export function stoerWagner(s: GraphSnapshot, options: StoerWagnerOptions = {}): MinCutResult {
|
|
48
|
+
const n = s.nodeCount;
|
|
49
|
+
const capacity = edgeCapacities(s, options.weights);
|
|
50
|
+
if (n < 2) {
|
|
51
|
+
return { cutValue: 0, side: makeMask(n, true), cutEdges: new Uint32Array(0) };
|
|
52
|
+
}
|
|
53
|
+
// Incident (neighbour, edge) pairs per node, both directions of every edge.
|
|
54
|
+
const { src, dst } = s.edgeList();
|
|
55
|
+
const offset = new Uint32Array(n + 1);
|
|
56
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
57
|
+
offset[src[e] + 1]++;
|
|
58
|
+
offset[dst[e] + 1]++;
|
|
59
|
+
}
|
|
60
|
+
for (let i = 0; i < n; i++) {
|
|
61
|
+
offset[i + 1] += offset[i];
|
|
62
|
+
}
|
|
63
|
+
const fill = offset.slice(0, n);
|
|
64
|
+
const neighbour = new Uint32Array(offset[n]);
|
|
65
|
+
const weight = new Float64Array(offset[n]);
|
|
66
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
67
|
+
neighbour[fill[src[e]]] = dst[e];
|
|
68
|
+
weight[fill[src[e]]++] = capacity[e];
|
|
69
|
+
neighbour[fill[dst[e]]] = src[e];
|
|
70
|
+
weight[fill[dst[e]]++] = capacity[e];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const rep = new Uint32Array(n);
|
|
74
|
+
const next = new Uint32Array(n).fill(INVALID_INDEX); // member lists, one per representative
|
|
75
|
+
const tail = new Uint32Array(n);
|
|
76
|
+
for (let i = 0; i < n; i++) {
|
|
77
|
+
rep[i] = i;
|
|
78
|
+
tail[i] = i;
|
|
79
|
+
}
|
|
80
|
+
const connection = new Float64Array(n);
|
|
81
|
+
const added = new Uint8Array(n);
|
|
82
|
+
const heap = new IndexedMaxHeap(n);
|
|
83
|
+
let best = Infinity;
|
|
84
|
+
let bestSide: NodeMask = makeMask(n);
|
|
85
|
+
|
|
86
|
+
for (let active = n; active > 1; active--) {
|
|
87
|
+
let start = INVALID_INDEX;
|
|
88
|
+
for (let i = 0; i < n; i++) {
|
|
89
|
+
if (rep[i] !== i) {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
added[i] = 0;
|
|
93
|
+
connection[i] = 0;
|
|
94
|
+
if (start === INVALID_INDEX) {
|
|
95
|
+
start = i;
|
|
96
|
+
} else {
|
|
97
|
+
heap.push(i, 0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
let prev = INVALID_INDEX;
|
|
101
|
+
let last = start;
|
|
102
|
+
for (;;) {
|
|
103
|
+
added[last] = 1;
|
|
104
|
+
for (let m = last; m !== INVALID_INDEX; m = next[m]) {
|
|
105
|
+
for (let k = offset[m]; k < offset[m + 1]; k++) {
|
|
106
|
+
const x = rep[neighbour[k]];
|
|
107
|
+
if (added[x] === 0) {
|
|
108
|
+
connection[x] += weight[k];
|
|
109
|
+
heap.pushOrIncrease(x, connection[x]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (heap.isEmpty()) {
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
prev = last;
|
|
117
|
+
last = heap.pop();
|
|
118
|
+
}
|
|
119
|
+
if (connection[last] < best) {
|
|
120
|
+
best = connection[last];
|
|
121
|
+
bestSide = makeMask(n);
|
|
122
|
+
for (let m = last; m !== INVALID_INDEX; m = next[m]) {
|
|
123
|
+
maskSet(bestSide, m, true);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Merge the last node of the phase into the one before it.
|
|
127
|
+
for (let m = last; m !== INVALID_INDEX; m = next[m]) {
|
|
128
|
+
rep[m] = prev;
|
|
129
|
+
}
|
|
130
|
+
next[tail[prev]] = last;
|
|
131
|
+
tail[prev] = tail[last];
|
|
132
|
+
}
|
|
133
|
+
return { cutValue: best, side: bestSide, cutEdges: crossingEdges(s, bestSide, false, capacity, false) };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Global minimum cut by Karger's randomised contraction, reading every edge as undirected. Each
|
|
138
|
+
* trial unions the endpoints of the edges in a seeded random order (an {@link IntUnionFind}, no
|
|
139
|
+
* graph is contracted) until two groups remain, so every edge is equally likely to be contracted
|
|
140
|
+
* next whatever its weight; the trial's cut is the weight of the edges between the two groups.
|
|
141
|
+
* A graph with more than two components gives a cut of 0 with node 0's component on `side`.
|
|
142
|
+
* O(m alpha(n)) per trial.
|
|
143
|
+
* @param s - The snapshot
|
|
144
|
+
* @param options - Trials, seed and weight override
|
|
145
|
+
* @returns The lightest cut found; with fewer than two nodes the value is 0 and `side` is empty
|
|
146
|
+
* @throws RangeError when `randomSeed` is not an integer or `iterations` is not a positive integer
|
|
147
|
+
* @public
|
|
148
|
+
*/
|
|
149
|
+
export function kargerMinCut(s: GraphSnapshot, options: KargerOptions = {}): MinCutResult {
|
|
150
|
+
const iterations = options.iterations ?? 100;
|
|
151
|
+
const randomSeed = options.randomSeed ?? 42;
|
|
152
|
+
if (!Number.isInteger(iterations) || iterations < 1) {
|
|
153
|
+
throw new RangeError(`iterations must be a positive integer, got ${iterations}`);
|
|
154
|
+
}
|
|
155
|
+
if (!Number.isInteger(randomSeed)) {
|
|
156
|
+
throw new RangeError(`randomSeed must be a finite integer, got ${randomSeed}`);
|
|
157
|
+
}
|
|
158
|
+
const n = s.nodeCount;
|
|
159
|
+
const capacity = edgeCapacities(s, options.weights);
|
|
160
|
+
if (n < 2) {
|
|
161
|
+
return { cutValue: 0, side: makeMask(n), cutEdges: new Uint32Array(0) };
|
|
162
|
+
}
|
|
163
|
+
const { src, dst } = s.edgeList();
|
|
164
|
+
const rand = mulberry32(randomSeed);
|
|
165
|
+
const order = new Uint32Array(s.edgeCount);
|
|
166
|
+
let best = Infinity;
|
|
167
|
+
let bestSide: NodeMask = makeMask(n);
|
|
168
|
+
for (let trial = 0; trial < iterations; trial++) {
|
|
169
|
+
for (let e = 0; e < order.length; e++) {
|
|
170
|
+
order[e] = e;
|
|
171
|
+
}
|
|
172
|
+
for (let i = order.length - 1; i > 0; i--) {
|
|
173
|
+
const j = Math.floor(rand() * (i + 1));
|
|
174
|
+
const t = order[i];
|
|
175
|
+
order[i] = order[j];
|
|
176
|
+
order[j] = t;
|
|
177
|
+
}
|
|
178
|
+
const uf = new IntUnionFind(n);
|
|
179
|
+
let groups = n;
|
|
180
|
+
for (let i = 0; i < order.length && groups > 2; i++) {
|
|
181
|
+
if (uf.union(src[order[i]], dst[order[i]])) {
|
|
182
|
+
groups--;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const root = uf.find(0);
|
|
186
|
+
let cut = 0;
|
|
187
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
188
|
+
if ((uf.find(src[e]) === root) !== (uf.find(dst[e]) === root)) {
|
|
189
|
+
cut += capacity[e];
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (cut < best) {
|
|
193
|
+
best = cut;
|
|
194
|
+
bestSide = makeMask(n);
|
|
195
|
+
for (let v = 0; v < n; v++) {
|
|
196
|
+
maskSet(bestSide, v, uf.find(v) === root);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { cutValue: best, side: bestSide, cutEdges: crossingEdges(s, bestSide, false, capacity, false) };
|
|
201
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type AdjacencyView, INVALID_INDEX, type NumericVector, type U32 } from "@graphty/graph-format";
|
|
2
|
+
|
|
3
|
+
/** Options of the index-based modularity. @public */
|
|
4
|
+
export interface ModularityOptions {
|
|
5
|
+
/** Resolution gamma: above 1 favours smaller communities; default 1. */
|
|
6
|
+
readonly resolution?: number | undefined;
|
|
7
|
+
/** Per-arc weight override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`. */
|
|
8
|
+
readonly weights?: NumericVector | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Newman's modularity of a partition: `sum_c in_c / 2m - gamma * (tot_c / 2m)^2`, where `2m` is
|
|
13
|
+
* twice the total edge weight, `in_c` counts every edge inside community c twice and `tot_c` sums
|
|
14
|
+
* its nodes' weighted degrees. Degrees follow graph-format's `weightedDegree()` convention -- a
|
|
15
|
+
* self-loop counts twice -- so `sum(tot_c) === 2m`. On a directed snapshot the direction is
|
|
16
|
+
* ignored: every arc counts as an undirected edge, and a node's degree is its in-weight plus its
|
|
17
|
+
* out-weight.
|
|
18
|
+
*
|
|
19
|
+
* On an undirected snapshot without self-loops this equals the legacy `calculateModularity`. On
|
|
20
|
+
* a directed snapshot or one with self-loops the two differ, and this one is the NetworkX value:
|
|
21
|
+
* the legacy degree counts a self-loop once and reads only out-neighbours on a directed graph, so
|
|
22
|
+
* its degrees do not sum to 2m.
|
|
23
|
+
*
|
|
24
|
+
* It is NOT the legacy `calculateMCLModularity`, in two ways. That function subtracts the null-model
|
|
25
|
+
* term `d(u) d(v) / 2m` once per edge whatever the partition: on two separate triangles it scores
|
|
26
|
+
* one community and the two triangles both 1/3, where this returns 0 and 1/2. And it ignores edge
|
|
27
|
+
* weights, where this reads the snapshot's weights by default; pass `weights` filled with 1 for
|
|
28
|
+
* the unweighted value.
|
|
29
|
+
* @param s - Any snapshot or adjacency view
|
|
30
|
+
* @param labels - Community of every node; `INVALID_INDEX` leaves a node out of every community
|
|
31
|
+
* @param options - Resolution and the weight override
|
|
32
|
+
* @returns The modularity, 0 when the snapshot has no edge weight
|
|
33
|
+
* @public
|
|
34
|
+
*/
|
|
35
|
+
export function modularity(s: AdjacencyView, labels: U32, options: ModularityOptions = {}): number {
|
|
36
|
+
const resolution = options.resolution ?? 1;
|
|
37
|
+
const weights = options.weights ?? s.weights;
|
|
38
|
+
if (labels.length !== s.nodeCount) {
|
|
39
|
+
throw new RangeError(`labels has ${labels.length} entries; the snapshot has ${s.nodeCount} nodes`);
|
|
40
|
+
}
|
|
41
|
+
if (weights !== null && weights.length !== s.arcCount) {
|
|
42
|
+
throw new RangeError(`weights has ${weights.length} entries; the snapshot has ${s.arcCount} arcs`);
|
|
43
|
+
}
|
|
44
|
+
const tot = new Map<number, number>();
|
|
45
|
+
const inside = new Map<number, number>();
|
|
46
|
+
const addTo = (map: Map<number, number>, label: number, w: number): void => {
|
|
47
|
+
if (label !== INVALID_INDEX) {
|
|
48
|
+
map.set(label, (map.get(label) ?? 0) + w);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
let m2 = 0;
|
|
52
|
+
for (let u = 0; u < s.nodeCount; u++) {
|
|
53
|
+
const end = s.rowPtr[u + 1];
|
|
54
|
+
for (let a = s.rowPtr[u]; a < end; a++) {
|
|
55
|
+
const v = s.colIdx[a];
|
|
56
|
+
const w = weights === null ? 1 : weights[a];
|
|
57
|
+
// An undirected edge between two nodes is read from both rows; a self-loop and a
|
|
58
|
+
// directed arc only once, so they count double here.
|
|
59
|
+
const twice = s.directed || v === u ? 2 * w : w;
|
|
60
|
+
m2 += twice;
|
|
61
|
+
if (s.directed) {
|
|
62
|
+
addTo(tot, labels[u], w);
|
|
63
|
+
addTo(tot, labels[v], w);
|
|
64
|
+
} else {
|
|
65
|
+
addTo(tot, labels[u], twice);
|
|
66
|
+
}
|
|
67
|
+
if (labels[u] === labels[v]) {
|
|
68
|
+
addTo(inside, labels[u], twice);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (m2 === 0) {
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
let q = 0;
|
|
76
|
+
for (const [label, degrees] of tot) {
|
|
77
|
+
const share = degrees / m2;
|
|
78
|
+
q += (inside.get(label) ?? 0) / m2 - resolution * share * share;
|
|
79
|
+
}
|
|
80
|
+
return q;
|
|
81
|
+
}
|
package/src/indexed/mst.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type GraphSnapshot, INVALID_INDEX, type NumericVector, type U32 } from "@graphty/graph-format";
|
|
2
2
|
|
|
3
|
+
import { IndexedMinHeap } from "./structures/min-heap.js";
|
|
3
4
|
import { IntUnionFind } from "./structures/union-find.js";
|
|
4
5
|
|
|
5
6
|
/** Options of the index-based MST. @public */
|
|
@@ -60,3 +61,80 @@ export function kruskalMST(s: GraphSnapshot, o: MstOptions = {}): MstResult {
|
|
|
60
61
|
}
|
|
61
62
|
return { edges: accepted.subarray(0, taken), totalWeight };
|
|
62
63
|
}
|
|
64
|
+
|
|
65
|
+
/** Options of the index-based Prim. @public */
|
|
66
|
+
export interface PrimOptions extends MstOptions {
|
|
67
|
+
/** The node index the (first) tree grows from; default 0. */
|
|
68
|
+
readonly start?: number | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Grow a tree in every component, rooted at `start` and then at each component's lowest node
|
|
71
|
+
* index, instead of throwing on a disconnected graph. Default false, as legacy `primMST`.
|
|
72
|
+
*/
|
|
73
|
+
readonly forest?: boolean | undefined;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Result of the index-based Prim: the MST result plus each node's discovery arc. @public */
|
|
77
|
+
export interface PrimResult extends MstResult {
|
|
78
|
+
/** The tree arc that reached each node, pointing at it; INVALID_INDEX for every root. */
|
|
79
|
+
readonly predArc: U32;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Prim's minimum spanning tree, grown from a start node by always taking the cheapest arc from the
|
|
84
|
+
* tree to a node outside it (an indexed heap keyed per node, so of several parallel edges the
|
|
85
|
+
* cheapest, and of equal ones the lowest-indexed, is the one taken). `edges` lists the logical
|
|
86
|
+
* edges in the order they joined the tree and `totalWeight` sums them in that order, as legacy
|
|
87
|
+
* `primMST` does. The start index is checked: one outside the graph throws a RangeError.
|
|
88
|
+
* @param s - An undirected snapshot
|
|
89
|
+
* @param o - Per-arc weight override, start node and the spanning-forest switch
|
|
90
|
+
* @returns The accepted edges, their total weight and the discovery arc per node
|
|
91
|
+
* @throws Error on a directed snapshot, and on a disconnected one unless `forest` is set
|
|
92
|
+
* @public
|
|
93
|
+
*/
|
|
94
|
+
export function primMST(s: GraphSnapshot, o: PrimOptions = {}): PrimResult {
|
|
95
|
+
if (s.directed) {
|
|
96
|
+
throw new Error("Prim's algorithm requires an undirected graph");
|
|
97
|
+
}
|
|
98
|
+
const { nodeCount: n, rowPtr, colIdx } = s;
|
|
99
|
+
const weights: NumericVector | null = o.weights ?? s.weights;
|
|
100
|
+
const key = new Float64Array(n).fill(Infinity);
|
|
101
|
+
const predArc = new Uint32Array(n).fill(INVALID_INDEX);
|
|
102
|
+
const inTree = new Uint8Array(n);
|
|
103
|
+
const heap = new IndexedMinHeap(n);
|
|
104
|
+
const accepted = new Uint32Array(Math.max(n - 1, 0));
|
|
105
|
+
let taken = 0;
|
|
106
|
+
let totalWeight = 0;
|
|
107
|
+
const grow = (root: number): void => {
|
|
108
|
+
heap.push(root, 0);
|
|
109
|
+
while (!heap.isEmpty()) {
|
|
110
|
+
const u = heap.pop();
|
|
111
|
+
inTree[u] = 1;
|
|
112
|
+
if (predArc[u] !== INVALID_INDEX) {
|
|
113
|
+
accepted[taken++] = s.arcToEdge[predArc[u]];
|
|
114
|
+
totalWeight += key[u];
|
|
115
|
+
}
|
|
116
|
+
for (let a = rowPtr[u]; a < rowPtr[u + 1]; a++) {
|
|
117
|
+
const v = colIdx[a];
|
|
118
|
+
const w = weights === null ? 1 : weights[a];
|
|
119
|
+
if (inTree[v] === 0 && w < key[v]) {
|
|
120
|
+
key[v] = w;
|
|
121
|
+
predArc[v] = a;
|
|
122
|
+
heap.pushOrDecrease(v, w);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
if (n > 0) {
|
|
128
|
+
grow(o.start ?? 0);
|
|
129
|
+
}
|
|
130
|
+
if (o.forest === true) {
|
|
131
|
+
for (let root = 0; root < n; root++) {
|
|
132
|
+
if (inTree[root] === 0) {
|
|
133
|
+
grow(root);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} else if (taken < n - 1) {
|
|
137
|
+
throw new Error("Graph is not connected");
|
|
138
|
+
}
|
|
139
|
+
return { edges: accepted.subarray(0, taken), totalWeight, predArc };
|
|
140
|
+
}
|
package/src/indexed/pagerank.ts
CHANGED
|
@@ -1,15 +1,22 @@
|
|
|
1
|
-
import type { F64, GraphSnapshot, NumericVector } from "@graphty/graph-format";
|
|
1
|
+
import type { F32, F64, GraphSnapshot, NumericVector } from "@graphty/graph-format";
|
|
2
2
|
|
|
3
|
-
/** Options of the index-based PageRank (graph-format design 14.2 Port 3
|
|
3
|
+
/** Options of the index-based PageRank (graph-format design 14.2 Port 3). @public */
|
|
4
4
|
export interface PageRankOptions {
|
|
5
5
|
/** Probability of following a link; default 0.85. */
|
|
6
6
|
readonly dampingFactor?: number | undefined;
|
|
7
7
|
/** Iteration cap; default 100. */
|
|
8
8
|
readonly maxIterations?: number | undefined;
|
|
9
|
-
/**
|
|
9
|
+
/** Convergence tolerance on the per-iteration change; default 1e-6. */
|
|
10
10
|
readonly tolerance?: number | undefined;
|
|
11
11
|
/** Use the snapshot's arc weights; default false. */
|
|
12
12
|
readonly weighted?: boolean | undefined;
|
|
13
|
+
/** Starting rank per node index, normalised to sum 1 (when the sum is positive); default uniform. */
|
|
14
|
+
readonly initialRanks?: F32 | F64 | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* How the per-iteration change is measured against `tolerance`: `"l1"` (default) sums it over
|
|
17
|
+
* all nodes, `"max"` takes the largest single-node change, the legacy `pageRank` rule.
|
|
18
|
+
*/
|
|
19
|
+
readonly convergenceNorm?: "l1" | "max" | undefined;
|
|
13
20
|
}
|
|
14
21
|
|
|
15
22
|
/** Result of the index-based PageRank. @public */
|
|
@@ -18,29 +25,74 @@ export interface PageRankResult {
|
|
|
18
25
|
readonly scores: F64;
|
|
19
26
|
/** Iterations actually run. */
|
|
20
27
|
readonly iterations: number;
|
|
21
|
-
/** Whether the
|
|
28
|
+
/** Whether the change fell below the tolerance. */
|
|
22
29
|
readonly converged: boolean;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
/**
|
|
26
|
-
* PageRank by pull over `reverse()`, with the dangling mass redistributed uniformly.
|
|
27
|
-
*
|
|
33
|
+
* PageRank by pull over `reverse()`, with the dangling mass redistributed uniformly. On an
|
|
34
|
+
* undirected snapshot every edge carries rank both ways (a self-loop once), which is PageRank on
|
|
35
|
+
* the directed graph holding both arcs of every edge.
|
|
36
|
+
* @param s - The snapshot
|
|
28
37
|
* @param o - Algorithm options
|
|
29
38
|
* @returns The scores, the iteration count and the convergence flag
|
|
30
39
|
* @public
|
|
31
40
|
*/
|
|
32
41
|
export function pageRank(s: GraphSnapshot, o: PageRankOptions = {}): PageRankResult {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
42
|
+
return run(s, null, o);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Personalized PageRank, as the legacy function computes it: the personalization vector,
|
|
47
|
+
* normalised to sum 1, replaces the uniform `1 / n` in the teleport, and each node receives
|
|
48
|
+
* `d * dangling / n` of the dangling mass scaled by its personalization. That keeps only a
|
|
49
|
+
* `1 / n` share of the dangling mass, so on a graph with a dangling node the scores sum to less
|
|
50
|
+
* than 1 (networkx spreads all of it). An all-zero vector gives plain PageRank, as the legacy
|
|
51
|
+
* function does for an empty list of personal nodes.
|
|
52
|
+
* @param s - The snapshot
|
|
53
|
+
* @param personalization - One finite, non-negative mass per node index
|
|
54
|
+
* @param o - Algorithm options
|
|
55
|
+
* @returns The scores, the iteration count and the convergence flag
|
|
56
|
+
* @public
|
|
57
|
+
*/
|
|
58
|
+
export function personalizedPageRank(
|
|
59
|
+
s: GraphSnapshot,
|
|
60
|
+
personalization: F32 | F64,
|
|
61
|
+
o: PageRankOptions = {},
|
|
62
|
+
): PageRankResult {
|
|
63
|
+
return run(s, distribution(personalization, s.nodeCount, "personalization"), o);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The shared power iteration.
|
|
68
|
+
* @param s - The snapshot
|
|
69
|
+
* @param p - The normalised personalization, or null for the uniform teleport
|
|
70
|
+
* @param o - Algorithm options
|
|
71
|
+
* @returns The scores, the iteration count and the convergence flag
|
|
72
|
+
*/
|
|
73
|
+
function run(s: GraphSnapshot, p: F64 | null, o: PageRankOptions): PageRankResult {
|
|
36
74
|
const n = s.nodeCount;
|
|
37
75
|
const d = o.dampingFactor ?? 0.85;
|
|
38
76
|
const maxIter = o.maxIterations ?? 100;
|
|
39
77
|
const tol = o.tolerance ?? 1e-6;
|
|
78
|
+
const useMax = o.convergenceNorm === "max";
|
|
79
|
+
if (n === 0) {
|
|
80
|
+
return { scores: new Float64Array(0), iterations: 0, converged: true };
|
|
81
|
+
}
|
|
40
82
|
const rev = s.reverse();
|
|
41
83
|
const weighted = o.weighted === true && rev.weights !== null;
|
|
42
|
-
|
|
43
|
-
|
|
84
|
+
// The f64 shadow toSnapshot keeps when a weight is not f32-exact, else the f32 arc weights.
|
|
85
|
+
const shadow = weighted ? s.edges.byRole("weight") : null;
|
|
86
|
+
const exact = shadow?.dtype === "f64" ? shadow.data : null;
|
|
87
|
+
let revW: NumericVector | null = weighted ? rev.weights : null;
|
|
88
|
+
let outW: NumericVector = weighted ? s.weightedOutDegree() : s.outDegree();
|
|
89
|
+
if (exact !== null) {
|
|
90
|
+
revW = gather(exact, rev.arcToEdge);
|
|
91
|
+
outW = rowSums(s, gather(exact, s.arcToEdge));
|
|
92
|
+
}
|
|
93
|
+
let rank =
|
|
94
|
+
(o.initialRanks === undefined ? null : distribution(o.initialRanks, n, "initialRanks")) ??
|
|
95
|
+
new Float64Array(n).fill(o.initialRanks === undefined ? 1 / n : 0);
|
|
44
96
|
let next = new Float64Array(n);
|
|
45
97
|
let it = 0;
|
|
46
98
|
let converged = false;
|
|
@@ -51,7 +103,9 @@ export function pageRank(s: GraphSnapshot, o: PageRankOptions = {}): PageRankRes
|
|
|
51
103
|
dangling += rank[u];
|
|
52
104
|
}
|
|
53
105
|
}
|
|
54
|
-
|
|
106
|
+
// Teleport (1 - d) spread by p or uniformly; the dangling mass d * dangling spread uniformly,
|
|
107
|
+
// and with p scaled by p[v] on top of the uniform 1 / n, as the legacy function does.
|
|
108
|
+
const spread = 1 - d + d * dangling * (p === null ? 1 : 1 / n);
|
|
55
109
|
let delta = 0;
|
|
56
110
|
for (let v = 0; v < n; v++) {
|
|
57
111
|
let acc = 0;
|
|
@@ -60,14 +114,73 @@ export function pageRank(s: GraphSnapshot, o: PageRankOptions = {}): PageRankRes
|
|
|
60
114
|
const u = rev.colIdx[a];
|
|
61
115
|
const ow = outW[u];
|
|
62
116
|
if (ow > 0) {
|
|
63
|
-
acc += (rank[u] * (
|
|
117
|
+
acc += (rank[u] * (revW === null ? 1 : revW[a])) / ow;
|
|
64
118
|
}
|
|
65
119
|
}
|
|
66
|
-
next[v] =
|
|
67
|
-
|
|
120
|
+
next[v] = spread * (p === null ? 1 / n : p[v]) + d * acc;
|
|
121
|
+
const change = Math.abs(next[v] - rank[v]);
|
|
122
|
+
delta = useMax ? Math.max(delta, change) : delta + change;
|
|
68
123
|
}
|
|
69
124
|
[rank, next] = [next, rank];
|
|
70
125
|
converged = delta < tol;
|
|
71
126
|
}
|
|
72
127
|
return { scores: rank, iterations: it, converged };
|
|
73
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* One value per arc, read through the arc's logical edge.
|
|
132
|
+
* @param perEdge - One value per logical edge
|
|
133
|
+
* @param arcToEdge - The logical edge of every arc
|
|
134
|
+
* @returns One value per arc
|
|
135
|
+
*/
|
|
136
|
+
function gather(perEdge: F64, arcToEdge: Uint32Array): F64 {
|
|
137
|
+
const out = new Float64Array(arcToEdge.length);
|
|
138
|
+
for (let a = 0; a < out.length; a++) {
|
|
139
|
+
out[a] = perEdge[arcToEdge[a]];
|
|
140
|
+
}
|
|
141
|
+
return out;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The sum of each node's out-arc values.
|
|
146
|
+
* @param s - The snapshot
|
|
147
|
+
* @param perArc - One value per forward arc
|
|
148
|
+
* @returns One sum per node
|
|
149
|
+
*/
|
|
150
|
+
function rowSums(s: GraphSnapshot, perArc: F64): F64 {
|
|
151
|
+
const out = new Float64Array(s.nodeCount);
|
|
152
|
+
for (let u = 0; u < s.nodeCount; u++) {
|
|
153
|
+
for (let a = s.rowPtr[u]; a < s.rowPtr[u + 1]; a++) {
|
|
154
|
+
out[u] += perArc[a];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return out;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Check a per-node mass vector and normalise it to sum 1.
|
|
162
|
+
* @param v - The caller's vector
|
|
163
|
+
* @param n - The node count
|
|
164
|
+
* @param name - The option's name, for the message
|
|
165
|
+
* @returns A normalised copy, or null when every entry is 0
|
|
166
|
+
*/
|
|
167
|
+
function distribution(v: F32 | F64, n: number, name: string): F64 | null {
|
|
168
|
+
if (v.length !== n) {
|
|
169
|
+
throw new Error(`PageRank: ${name} has ${String(v.length)} entries for ${String(n)} nodes`);
|
|
170
|
+
}
|
|
171
|
+
let total = 0;
|
|
172
|
+
for (const mass of v) {
|
|
173
|
+
if (!Number.isFinite(mass) || mass < 0) {
|
|
174
|
+
throw new Error(`PageRank: ${name} must be finite and non-negative, got ${String(mass)}`);
|
|
175
|
+
}
|
|
176
|
+
total += mass;
|
|
177
|
+
}
|
|
178
|
+
if (total === 0) {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const out = new Float64Array(n);
|
|
182
|
+
for (let u = 0; u < n; u++) {
|
|
183
|
+
out[u] = v[u] / total;
|
|
184
|
+
}
|
|
185
|
+
return out;
|
|
186
|
+
}
|