@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
package/src/indexed/bfs.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AdjacencyView, INVALID_INDEX, type U32 } from "@graphty/graph-format";
|
|
1
|
+
import { type AdjacencyView, type GraphSnapshot, INVALID_INDEX, type U32 } from "@graphty/graph-format";
|
|
2
2
|
|
|
3
3
|
/** Result of the index-based BFS (graph-format design 14.2 Port 1). @public */
|
|
4
4
|
export interface BfsResult {
|
|
@@ -12,10 +12,93 @@ export interface BfsResult {
|
|
|
12
12
|
readonly visitedCount: number;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
+
/** The neighbour-order option the order-sensitive traversals share. @public */
|
|
16
|
+
export interface ArcOrderOption {
|
|
17
|
+
/**
|
|
18
|
+
* A permutation of the arc indices, `arcCount` long, whose slice `[rowPtr[u], rowPtr[u + 1])`
|
|
19
|
+
* lists node `u`'s arcs in the order to try them. The default is row order (ascending neighbour
|
|
20
|
+
* index). A caller that must reproduce a traversal over another neighbour order -- a legacy
|
|
21
|
+
* `Graph` hands out neighbours in insertion order -- passes that order here.
|
|
22
|
+
*/
|
|
23
|
+
readonly arcOrder?: U32 | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
15
26
|
/** Options of the index-based BFS. @public */
|
|
16
|
-
export interface BfsOptions {
|
|
27
|
+
export interface BfsOptions extends ArcOrderOption {
|
|
17
28
|
/** Stop expanding at this depth; unbounded when omitted. */
|
|
18
29
|
readonly maxDepth?: number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Stop when this node index is taken off the queue, before its neighbours are expanded. Every
|
|
32
|
+
* node discovered by then stays in `order`; the target's own position in `order` ends the
|
|
33
|
+
* prefix of nodes that were expanded.
|
|
34
|
+
* @throws RangeError when it is not a node index
|
|
35
|
+
*/
|
|
36
|
+
readonly target?: number | undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Check a start (or target) node index.
|
|
41
|
+
* @param g - The adjacency
|
|
42
|
+
* @param start - The node index
|
|
43
|
+
* @param what - What the index is, for the error message
|
|
44
|
+
* @throws RangeError when `start` is not a node index of `g`
|
|
45
|
+
*/
|
|
46
|
+
export function checkStart(g: AdjacencyView, start: number, what = "start"): void {
|
|
47
|
+
if (!Number.isInteger(start) || start < 0 || start >= g.nodeCount) {
|
|
48
|
+
throw new RangeError(`${what} node index ${String(start)} is out of range for ${String(g.nodeCount)} nodes`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Check an optional target node index.
|
|
54
|
+
* @param g - The adjacency
|
|
55
|
+
* @param target - The option, or undefined
|
|
56
|
+
* @returns The target, or INVALID_INDEX for none
|
|
57
|
+
* @throws RangeError when `target` is set and is not a node index of `g`
|
|
58
|
+
*/
|
|
59
|
+
export function checkTarget(g: AdjacencyView, target: number | undefined): number {
|
|
60
|
+
if (target === undefined) {
|
|
61
|
+
return INVALID_INDEX;
|
|
62
|
+
}
|
|
63
|
+
checkStart(g, target, "target");
|
|
64
|
+
return target;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Check an `arcOrder` option: `arcCount` entries, and every row's slice a permutation of that row's
|
|
69
|
+
* arcs.
|
|
70
|
+
* @param g - The adjacency
|
|
71
|
+
* @param arcOrder - The option, or undefined
|
|
72
|
+
* @returns The order, or null for row order
|
|
73
|
+
* @throws RangeError when the order does not fit the adjacency
|
|
74
|
+
*/
|
|
75
|
+
export function checkArcOrder(g: AdjacencyView, arcOrder: U32 | undefined): U32 | null {
|
|
76
|
+
if (arcOrder === undefined) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
const { rowPtr, nodeCount } = g;
|
|
80
|
+
if (arcOrder.length !== g.arcCount) {
|
|
81
|
+
throw new RangeError(`arcOrder has ${String(arcOrder.length)} entries, expected ${String(g.arcCount)}`);
|
|
82
|
+
}
|
|
83
|
+
const seen = new Uint8Array(arcOrder.length);
|
|
84
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
85
|
+
const begin = rowPtr[u];
|
|
86
|
+
const end = rowPtr[u + 1];
|
|
87
|
+
for (let a = begin; a < end; a++) {
|
|
88
|
+
if (arcOrder[a] < begin || arcOrder[a] >= end) {
|
|
89
|
+
throw new RangeError(
|
|
90
|
+
`arcOrder[${String(a)}] = ${String(arcOrder[a])} is not an arc of node ${String(u)}`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (seen[arcOrder[a]] === 1) {
|
|
94
|
+
throw new RangeError(
|
|
95
|
+
`arcOrder[${String(a)}] = ${String(arcOrder[a])} repeats an arc of node ${String(u)}`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
seen[arcOrder[a]] = 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return arcOrder;
|
|
19
102
|
}
|
|
20
103
|
|
|
21
104
|
/**
|
|
@@ -28,24 +111,30 @@ export interface BfsOptions {
|
|
|
28
111
|
* @public
|
|
29
112
|
*/
|
|
30
113
|
export function breadthFirstSearch(g: AdjacencyView, start: number, options: BfsOptions = {}): BfsResult {
|
|
114
|
+
checkStart(g, start);
|
|
31
115
|
const { nodeCount, rowPtr, colIdx } = g;
|
|
116
|
+
const arcOrder = checkArcOrder(g, options.arcOrder);
|
|
32
117
|
const parent = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
33
118
|
const depth = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
34
119
|
const order = new Uint32Array(nodeCount);
|
|
35
120
|
const maxDepth = options.maxDepth ?? INVALID_INDEX;
|
|
121
|
+
const target = checkTarget(g, options.target);
|
|
36
122
|
let head = 0;
|
|
37
123
|
let tail = 0;
|
|
38
124
|
order[tail++] = start;
|
|
39
125
|
depth[start] = 0;
|
|
40
126
|
while (head < tail) {
|
|
41
127
|
const u = order[head++];
|
|
128
|
+
if (u === target) {
|
|
129
|
+
break;
|
|
130
|
+
}
|
|
42
131
|
const d = depth[u];
|
|
43
132
|
if (d >= maxDepth) {
|
|
44
133
|
continue;
|
|
45
134
|
}
|
|
46
135
|
const end = rowPtr[u + 1];
|
|
47
136
|
for (let a = rowPtr[u]; a < end; a++) {
|
|
48
|
-
const v = colIdx[a];
|
|
137
|
+
const v = colIdx[arcOrder === null ? a : arcOrder[a]];
|
|
49
138
|
if (depth[v] === INVALID_INDEX) {
|
|
50
139
|
depth[v] = d + 1;
|
|
51
140
|
parent[v] = u;
|
|
@@ -55,3 +144,100 @@ export function breadthFirstSearch(g: AdjacencyView, start: number, options: Bfs
|
|
|
55
144
|
}
|
|
56
145
|
return { order: order.subarray(0, tail), parent, depth, visitedCount: tail };
|
|
57
146
|
}
|
|
147
|
+
|
|
148
|
+
/** Options of {@link directionOptimizedBfs}. @public */
|
|
149
|
+
export interface DirectionOptimizedBfsOptions {
|
|
150
|
+
/**
|
|
151
|
+
* Switch from top-down to bottom-up once the frontier's out-arcs exceed the unvisited nodes'
|
|
152
|
+
* out-arcs divided by `alpha`. Default 15.
|
|
153
|
+
*/
|
|
154
|
+
readonly alpha?: number | undefined;
|
|
155
|
+
/** Switch back to top-down once the frontier shrinks below `nodeCount / beta` nodes. Default 18. */
|
|
156
|
+
readonly beta?: number | undefined;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Direction-optimising breadth-first search (Beamer, Asanovic and Patterson, SC'12): a top-down
|
|
161
|
+
* step expands the frontier's out-arcs, a bottom-up step has every unvisited node look for a
|
|
162
|
+
* frontier node among its in-neighbours over `s.reverse()` (fetched only when a bottom-up step
|
|
163
|
+
* runs), and the search switches between the two
|
|
164
|
+
* by frontier size. Both steps give a node the LOWEST-index frontier node that reaches it as its
|
|
165
|
+
* parent, so the result does not depend on which steps ran: `depth` equals `breadthFirstSearch`'s,
|
|
166
|
+
* and `order` lists the visited nodes level by level, ascending within a level.
|
|
167
|
+
* @param s - The snapshot to traverse
|
|
168
|
+
* @param source - The node index to start from
|
|
169
|
+
* @param options - The switching thresholds
|
|
170
|
+
* @returns The visit order, the parent array, the depth array and the visited count
|
|
171
|
+
* @public
|
|
172
|
+
*/
|
|
173
|
+
export function directionOptimizedBfs(
|
|
174
|
+
s: GraphSnapshot,
|
|
175
|
+
source: number,
|
|
176
|
+
options: DirectionOptimizedBfsOptions = {},
|
|
177
|
+
): BfsResult {
|
|
178
|
+
checkStart(s, source);
|
|
179
|
+
const { nodeCount, rowPtr, colIdx } = s;
|
|
180
|
+
const alpha = options.alpha ?? 15;
|
|
181
|
+
const beta = options.beta ?? 18;
|
|
182
|
+
let reverse: AdjacencyView | null = null;
|
|
183
|
+
const parent = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
184
|
+
const depth = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
185
|
+
// order[levelStart, tail) is the current frontier, sorted ascending.
|
|
186
|
+
const order = new Uint32Array(nodeCount);
|
|
187
|
+
order[0] = source;
|
|
188
|
+
depth[source] = 0;
|
|
189
|
+
let levelStart = 0;
|
|
190
|
+
let tail = 1;
|
|
191
|
+
let unexploredArcs = s.arcCount - (rowPtr[source + 1] - rowPtr[source]);
|
|
192
|
+
let bottomUp = false;
|
|
193
|
+
for (let d = 0; levelStart < tail; d++) {
|
|
194
|
+
const frontierSize = tail - levelStart;
|
|
195
|
+
if (bottomUp) {
|
|
196
|
+
bottomUp = frontierSize >= nodeCount / beta;
|
|
197
|
+
} else {
|
|
198
|
+
let frontierArcs = 0;
|
|
199
|
+
for (let i = levelStart; i < tail; i++) {
|
|
200
|
+
frontierArcs += rowPtr[order[i] + 1] - rowPtr[order[i]];
|
|
201
|
+
}
|
|
202
|
+
bottomUp = frontierArcs > unexploredArcs / alpha;
|
|
203
|
+
}
|
|
204
|
+
const levelEnd = tail;
|
|
205
|
+
if (bottomUp) {
|
|
206
|
+
reverse ??= s.reverse();
|
|
207
|
+
for (let v = 0; v < nodeCount; v++) {
|
|
208
|
+
if (depth[v] !== INVALID_INDEX) {
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
const end = reverse.rowPtr[v + 1];
|
|
212
|
+
for (let a = reverse.rowPtr[v]; a < end; a++) {
|
|
213
|
+
const u = reverse.colIdx[a];
|
|
214
|
+
if (depth[u] === d) {
|
|
215
|
+
parent[v] = u;
|
|
216
|
+
depth[v] = d + 1;
|
|
217
|
+
order[tail++] = v;
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
for (let i = levelStart; i < levelEnd; i++) {
|
|
224
|
+
const u = order[i];
|
|
225
|
+
const end = rowPtr[u + 1];
|
|
226
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
227
|
+
const v = colIdx[a];
|
|
228
|
+
if (depth[v] === INVALID_INDEX) {
|
|
229
|
+
parent[v] = u;
|
|
230
|
+
depth[v] = d + 1;
|
|
231
|
+
order[tail++] = v;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
order.subarray(levelEnd, tail).sort();
|
|
236
|
+
}
|
|
237
|
+
for (let i = levelEnd; i < tail; i++) {
|
|
238
|
+
unexploredArcs -= rowPtr[order[i] + 1] - rowPtr[order[i]];
|
|
239
|
+
}
|
|
240
|
+
levelStart = levelEnd;
|
|
241
|
+
}
|
|
242
|
+
return { order: order.subarray(0, tail), parent, depth, visitedCount: tail };
|
|
243
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { type AdjacencyView, type GraphSnapshot, makeMask, maskSet, type NodeMask } from "@graphty/graph-format";
|
|
2
|
+
|
|
3
|
+
/** Result of the index-based bipartiteness test. @public */
|
|
4
|
+
export interface BipartiteResult {
|
|
5
|
+
/** Whether the nodes split into two sides with no arc inside a side. */
|
|
6
|
+
readonly bipartite: boolean;
|
|
7
|
+
/**
|
|
8
|
+
* The two sides as one mask: a set bit puts the node on the second side, a clear bit on the
|
|
9
|
+
* first. The lowest-index node of every component is on the first side. Null when not bipartite.
|
|
10
|
+
*/
|
|
11
|
+
readonly sides: NodeMask | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Options of {@link isBipartite}. @public */
|
|
15
|
+
export interface BipartiteOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Which arcs of a directed snapshot to colour along. "both" (default) follows out- and in-arcs,
|
|
18
|
+
* which answers for the graph with direction ignored. "out" follows out-arcs only, as the legacy
|
|
19
|
+
* `bipartitePartition` does: roots are taken in index order and a node first reached through an
|
|
20
|
+
* in-arc is coloured as a new root, so the answer depends on node order. Ignored on an undirected
|
|
21
|
+
* snapshot.
|
|
22
|
+
*/
|
|
23
|
+
readonly arcs?: "both" | "out" | undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const UNSEEN = 2;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Two-colour the graph by BFS, one BFS per component in index order. A self-loop makes a graph
|
|
30
|
+
* non-bipartite; parallel arcs change nothing. Arc direction plays no part in bipartiteness, so on
|
|
31
|
+
* a directed snapshot every node is coloured against its in-neighbours (over `s.reverse()`) as well
|
|
32
|
+
* as its out-neighbours. Following out-arcs only -- as the legacy `bipartitePartition` does -- makes
|
|
33
|
+
* the answer depend on node order: with the single arc `a -> b`, a walk that starts at `b` never
|
|
34
|
+
* meets `a`, colours it as a new root and then finds the arc inside one side. `options.arcs: "out"`
|
|
35
|
+
* gives that legacy answer.
|
|
36
|
+
* @param s - The snapshot to test
|
|
37
|
+
* @param options - Which arcs to follow on a directed snapshot
|
|
38
|
+
* @returns Whether the graph is bipartite and, when it is, its two sides
|
|
39
|
+
* @public
|
|
40
|
+
*/
|
|
41
|
+
export function isBipartite(s: GraphSnapshot, options: BipartiteOptions = {}): BipartiteResult {
|
|
42
|
+
const { nodeCount } = s;
|
|
43
|
+
const views: AdjacencyView[] = s.directed && options.arcs !== "out" ? [s, s.reverse()] : [s];
|
|
44
|
+
const side = new Uint8Array(nodeCount).fill(UNSEEN);
|
|
45
|
+
const queue = new Uint32Array(nodeCount);
|
|
46
|
+
for (let r = 0; r < nodeCount; r++) {
|
|
47
|
+
if (side[r] !== UNSEEN) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
side[r] = 0;
|
|
51
|
+
let head = 0;
|
|
52
|
+
let tail = 0;
|
|
53
|
+
queue[tail++] = r;
|
|
54
|
+
while (head < tail) {
|
|
55
|
+
const u = queue[head++];
|
|
56
|
+
for (const { rowPtr, colIdx } of views) {
|
|
57
|
+
const end = rowPtr[u + 1];
|
|
58
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
59
|
+
const v = colIdx[a];
|
|
60
|
+
if (side[v] === UNSEEN) {
|
|
61
|
+
side[v] = side[u] ^ 1;
|
|
62
|
+
queue[tail++] = v;
|
|
63
|
+
} else if (side[v] === side[u]) {
|
|
64
|
+
return { bipartite: false, sides: null };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const sides = makeMask(nodeCount);
|
|
71
|
+
for (let i = 0; i < nodeCount; i++) {
|
|
72
|
+
if (side[i] === 1) {
|
|
73
|
+
maskSet(sides, i, true);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { bipartite: true, sides };
|
|
77
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import type { GraphSnapshot, NumericVector } from "@graphty/graph-format";
|
|
2
|
+
|
|
3
|
+
import type { ScoresResult } from "./betweenness.js";
|
|
4
|
+
import { IndexedMinHeap } from "./structures/min-heap.js";
|
|
5
|
+
|
|
6
|
+
/** Options of the index-based closeness centrality, matching the legacy `closenessCentrality`. @public */
|
|
7
|
+
export interface ClosenessOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Scale by the fraction of other nodes reached (Wasserman and Faust), or with `harmonic` divide by
|
|
10
|
+
* `n - 1`. Default false.
|
|
11
|
+
*/
|
|
12
|
+
readonly normalized?: boolean | undefined;
|
|
13
|
+
/** Sum `1 / distance` instead of taking `1 / sum(distance)`; better on disconnected graphs. Default false. */
|
|
14
|
+
readonly harmonic?: boolean | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* Stop searching from nodes this far away or farther. A node past the cutoff is still counted when an edge
|
|
17
|
+
* reaches it from a node closer than the cutoff, as in the legacy functions. Default: every reachable node.
|
|
18
|
+
*/
|
|
19
|
+
readonly cutoff?: number | undefined;
|
|
20
|
+
/**
|
|
21
|
+
* Measure distance by edge weight (Dijkstra) rather than by hops, as the legacy
|
|
22
|
+
* `weightedClosenessCentrality` does. Default false: weights are ignored, as the legacy
|
|
23
|
+
* `closenessCentrality` ignores them.
|
|
24
|
+
*/
|
|
25
|
+
readonly weighted?: boolean | undefined;
|
|
26
|
+
/** Per-arc weight override, arcCount long, read when `weighted`; a facade passes the exact f64 weights. */
|
|
27
|
+
readonly weights?: NumericVector | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Scores one source at a time, reusing its scratch arrays across sources. */
|
|
31
|
+
type Scorer = (source: number) => number;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build the per-source scorer: a breadth-first search, or Dijkstra when `weighted`, then the legacy formula.
|
|
35
|
+
* @param s - The snapshot
|
|
36
|
+
* @param o - The options
|
|
37
|
+
* @returns The scorer
|
|
38
|
+
*/
|
|
39
|
+
function scorer(s: GraphSnapshot, o: ClosenessOptions): Scorer {
|
|
40
|
+
const { nodeCount: n, rowPtr, colIdx } = s;
|
|
41
|
+
const cutoff = o.cutoff ?? Infinity;
|
|
42
|
+
const weights = o.weighted === true ? (o.weights ?? s.weights) : null;
|
|
43
|
+
const dist = new Float64Array(n).fill(Infinity);
|
|
44
|
+
const reached = new Uint32Array(n);
|
|
45
|
+
const heap = weights === null ? null : new IndexedMinHeap(n);
|
|
46
|
+
// A popped node is final, as in the legacy search: without this a negative cycle never ends.
|
|
47
|
+
const settled = new Uint8Array(n);
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Fill `dist` from the source and list the reached nodes in `reached`.
|
|
51
|
+
* @param source - The source index
|
|
52
|
+
* @returns How many nodes were reached, the source included
|
|
53
|
+
*/
|
|
54
|
+
function search(source: number): number {
|
|
55
|
+
let tail = 0;
|
|
56
|
+
dist[source] = 0;
|
|
57
|
+
reached[tail++] = source;
|
|
58
|
+
if (heap === null || weights === null) {
|
|
59
|
+
for (let head = 0; head < tail; head++) {
|
|
60
|
+
const v = reached[head];
|
|
61
|
+
if (dist[v] >= cutoff) {
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
|
|
65
|
+
const w = colIdx[a];
|
|
66
|
+
if (dist[w] === Infinity) {
|
|
67
|
+
dist[w] = dist[v] + 1;
|
|
68
|
+
reached[tail++] = w;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return tail;
|
|
73
|
+
}
|
|
74
|
+
heap.push(source, 0);
|
|
75
|
+
while (!heap.isEmpty()) {
|
|
76
|
+
const v = heap.pop();
|
|
77
|
+
settled[v] = 1;
|
|
78
|
+
if (dist[v] >= cutoff) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
|
|
82
|
+
const w = colIdx[a];
|
|
83
|
+
if (settled[w] === 1) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const d = dist[v] + weights[a];
|
|
87
|
+
if (d < dist[w]) {
|
|
88
|
+
if (dist[w] === Infinity) {
|
|
89
|
+
reached[tail++] = w;
|
|
90
|
+
}
|
|
91
|
+
dist[w] = d;
|
|
92
|
+
heap.pushOrDecrease(w, d);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return tail;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (source: number): number => {
|
|
100
|
+
const count = search(source);
|
|
101
|
+
let score = 0;
|
|
102
|
+
let total = 0;
|
|
103
|
+
for (let i = 1; i < count; i++) {
|
|
104
|
+
const d = dist[reached[i]];
|
|
105
|
+
if (o.harmonic === true) {
|
|
106
|
+
score += d > 0 ? 1 / d : 0;
|
|
107
|
+
} else {
|
|
108
|
+
total += d;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
for (let i = 0; i < count; i++) {
|
|
112
|
+
dist[reached[i]] = Infinity;
|
|
113
|
+
settled[reached[i]] = 0;
|
|
114
|
+
}
|
|
115
|
+
if (o.harmonic === true) {
|
|
116
|
+
return o.normalized === true && n > 1 ? score / (n - 1) : score;
|
|
117
|
+
}
|
|
118
|
+
if (total <= 0) {
|
|
119
|
+
return 0;
|
|
120
|
+
}
|
|
121
|
+
return o.normalized === true && n > 1 ? (count - 1) / total / (n - 1) : 1 / total;
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Closeness centrality of every node: `1 / sum(distance to each reached node)` by default, the legacy
|
|
127
|
+
* `closenessCentrality` (hops) and `weightedClosenessCentrality` (`weighted: true`) numbers. The weighted
|
|
128
|
+
* route reads the snapshot's f32 arc weights, so it matches legacy to f32 rounding; pass the f64 weights as
|
|
129
|
+
* `weights` for the exact legacy sums. As in legacy, a node's distance is final once it is searched from, so a
|
|
130
|
+
* negative weight gives the legacy (not the true shortest) distance and never loops. An unreached node adds
|
|
131
|
+
* nothing, and a node that reaches nothing scores 0.
|
|
132
|
+
* @param s - The snapshot
|
|
133
|
+
* @param options - Normalisation, harmonic form, cutoff and weights
|
|
134
|
+
* @returns One score per node index; `iterations` is the number of searches run
|
|
135
|
+
* @public
|
|
136
|
+
*/
|
|
137
|
+
export function closenessCentrality(s: GraphSnapshot, options: ClosenessOptions = {}): ScoresResult {
|
|
138
|
+
const score = scorer(s, options);
|
|
139
|
+
const scores = new Float64Array(s.nodeCount);
|
|
140
|
+
for (let v = 0; v < s.nodeCount; v++) {
|
|
141
|
+
scores[v] = score(v);
|
|
142
|
+
}
|
|
143
|
+
return { scores, iterations: s.nodeCount, converged: true };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Closeness centrality of one node, with one search rather than n.
|
|
148
|
+
* @param s - The snapshot
|
|
149
|
+
* @param node - The node index
|
|
150
|
+
* @param options - As {@link closenessCentrality}
|
|
151
|
+
* @returns The node's score
|
|
152
|
+
* @throws RangeError for a node index outside `[0, nodeCount)`
|
|
153
|
+
* @public
|
|
154
|
+
*/
|
|
155
|
+
export function nodeClosenessCentrality(s: GraphSnapshot, node: number, options: ClosenessOptions = {}): number {
|
|
156
|
+
if (!Number.isInteger(node) || node < 0 || node >= s.nodeCount) {
|
|
157
|
+
throw new RangeError(`nodeClosenessCentrality: node must be an index in [0, ${s.nodeCount}), got ${node}`);
|
|
158
|
+
}
|
|
159
|
+
return scorer(s, options)(node);
|
|
160
|
+
}
|
|
@@ -7,32 +7,36 @@ export interface CommonNeighborsOptions {
|
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* Merge row u of `fwd` with row v of `bwd` (both sorted) and add up each DISTINCT common neighbour
|
|
11
|
+
* once: 1 per neighbour, or `weight(z)` when a weight function is given. Skipping the repeats of a
|
|
12
|
+
* match in row v gives simple-graph semantics on a multigraph; a repeat in row u is then smaller
|
|
13
|
+
* than the next entry of row v and the ordinary advance passes it.
|
|
14
|
+
* @param fwd - The view whose row u is read
|
|
15
|
+
* @param bwd - The view whose row v is read
|
|
12
16
|
* @param u - A node index
|
|
13
17
|
* @param v - A node index
|
|
14
|
-
* @param
|
|
15
|
-
* @returns The count
|
|
16
|
-
* @public
|
|
18
|
+
* @param weight - Optional weight of a common neighbour
|
|
19
|
+
* @returns The count, or the weight sum, over distinct common neighbours
|
|
17
20
|
*/
|
|
18
|
-
export function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
export function sortedRowMerge(
|
|
22
|
+
fwd: AdjacencyView,
|
|
23
|
+
bwd: AdjacencyView,
|
|
24
|
+
u: number,
|
|
25
|
+
v: number,
|
|
26
|
+
weight?: (z: number) => number,
|
|
27
|
+
): number {
|
|
21
28
|
let i = fwd.rowPtr[u];
|
|
22
29
|
const iEnd = fwd.rowPtr[u + 1];
|
|
23
30
|
let j = bwd.rowPtr[v];
|
|
24
31
|
const jEnd = bwd.rowPtr[v + 1];
|
|
25
|
-
let
|
|
32
|
+
let sum = 0;
|
|
26
33
|
while (i < iEnd && j < jEnd) {
|
|
27
34
|
const a = fwd.colIdx[i];
|
|
28
35
|
const b = bwd.colIdx[j];
|
|
29
36
|
if (a === b) {
|
|
30
|
-
|
|
37
|
+
sum += weight === undefined ? 1 : weight(a);
|
|
31
38
|
i++;
|
|
32
39
|
j++;
|
|
33
|
-
while (i < iEnd && fwd.colIdx[i] === a) {
|
|
34
|
-
i++;
|
|
35
|
-
}
|
|
36
40
|
while (j < jEnd && bwd.colIdx[j] === b) {
|
|
37
41
|
j++;
|
|
38
42
|
}
|
|
@@ -42,5 +46,18 @@ export function commonNeighborsScore(s: GraphSnapshot, u: number, v: number, o:
|
|
|
42
46
|
j++;
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
return
|
|
49
|
+
return sum;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The number of distinct common neighbours of two nodes, by a merge of their sorted rows.
|
|
54
|
+
* @param s - The snapshot
|
|
55
|
+
* @param u - A node index
|
|
56
|
+
* @param v - A node index
|
|
57
|
+
* @param o - Options
|
|
58
|
+
* @returns The count of distinct common neighbours
|
|
59
|
+
* @public
|
|
60
|
+
*/
|
|
61
|
+
export function commonNeighborsScore(s: GraphSnapshot, u: number, v: number, o: CommonNeighborsOptions = {}): number {
|
|
62
|
+
return sortedRowMerge(s, o.directed === true ? s.reverse() : s, u, v);
|
|
46
63
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { AdjacencyView, F64, GraphSnapshot } from "@graphty/graph-format";
|
|
2
|
+
|
|
3
|
+
/** Options of the index-based degree centrality, matching the legacy `degreeCentrality`. @public */
|
|
4
|
+
export interface DegreeCentralityOptions {
|
|
5
|
+
/** On a directed snapshot, which neighbours to count; ignored when undirected. Default `"total"`. */
|
|
6
|
+
readonly mode?: "in" | "out" | "total" | undefined;
|
|
7
|
+
/** Divide by `n - 1`, the most neighbours a node can have. Default false. */
|
|
8
|
+
readonly normalized?: boolean | undefined;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Add each node's number of DISTINCT neighbours in `g` to `out`: parallel edges count once and a self-loop
|
|
13
|
+
* counts once, as the legacy `Graph.degree` counts them.
|
|
14
|
+
* @param g - The adjacency
|
|
15
|
+
* @param out - Per-node totals, added to in place
|
|
16
|
+
*/
|
|
17
|
+
function addDistinctNeighbours(g: AdjacencyView, out: F64): void {
|
|
18
|
+
const { rowPtr, colIdx } = g;
|
|
19
|
+
for (let v = 0; v < g.nodeCount; v++) {
|
|
20
|
+
let prev = -1;
|
|
21
|
+
for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
|
|
22
|
+
if (colIdx[a] !== prev) {
|
|
23
|
+
prev = colIdx[a];
|
|
24
|
+
out[v]++;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Degree centrality: each node's number of distinct neighbours (in, out, or both on a directed snapshot),
|
|
32
|
+
* optionally divided by `n - 1`. Equals the legacy `degreeCentrality` on every graph it can hold, and on a
|
|
33
|
+
* multigraph equals it on the same graph with each pair's parallel edges merged.
|
|
34
|
+
* @param s - The snapshot
|
|
35
|
+
* @param options - Direction and normalisation
|
|
36
|
+
* @returns One score per node index
|
|
37
|
+
* @public
|
|
38
|
+
*/
|
|
39
|
+
export function degreeCentrality(s: GraphSnapshot, options: DegreeCentralityOptions = {}): F64 {
|
|
40
|
+
const scores = new Float64Array(s.nodeCount);
|
|
41
|
+
const mode = s.directed ? (options.mode ?? "total") : "out";
|
|
42
|
+
if (mode !== "in") {
|
|
43
|
+
addDistinctNeighbours(s, scores);
|
|
44
|
+
}
|
|
45
|
+
if (mode !== "out") {
|
|
46
|
+
addDistinctNeighbours(s.reverse(), scores);
|
|
47
|
+
}
|
|
48
|
+
const max = s.nodeCount - 1;
|
|
49
|
+
if (options.normalized === true && max > 0) {
|
|
50
|
+
for (let v = 0; v < scores.length; v++) {
|
|
51
|
+
scores[v] /= max;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return scores;
|
|
55
|
+
}
|