@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,500 @@
|
|
|
1
|
+
import {
|
|
2
|
+
fromEdgeArrays,
|
|
3
|
+
GraphBuilder,
|
|
4
|
+
type GraphSnapshot,
|
|
5
|
+
INVALID_INDEX,
|
|
6
|
+
makeMask,
|
|
7
|
+
maskSet,
|
|
8
|
+
maskTest,
|
|
9
|
+
type NodeId,
|
|
10
|
+
type NodeMask,
|
|
11
|
+
type NumericVector,
|
|
12
|
+
type U32,
|
|
13
|
+
} from "@graphty/graph-format";
|
|
14
|
+
|
|
15
|
+
/** Options of {@link maxFlow} and {@link minSTCut}. @public */
|
|
16
|
+
export interface MaxFlowOptions {
|
|
17
|
+
/**
|
|
18
|
+
* How augmenting paths are found: `"edmonds-karp"` (breadth-first, shortest paths first,
|
|
19
|
+
* O(V E^2)) or `"ford-fulkerson"` (depth-first, O(E f)). Default `"edmonds-karp"` for
|
|
20
|
+
* {@link maxFlow} and `"ford-fulkerson"` for {@link minSTCut}. Both give the same source side
|
|
21
|
+
* and the same flow value up to floating-point rounding; the per-edge flows can differ where
|
|
22
|
+
* the maximum flow is not unique.
|
|
23
|
+
*/
|
|
24
|
+
readonly algorithm?: "edmonds-karp" | "ford-fulkerson" | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Per-arc capacity override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`
|
|
27
|
+
* for exact f64 capacities. Without it the snapshot's arc weights are the capacities, and an
|
|
28
|
+
* unweighted snapshot gives every edge capacity 1.
|
|
29
|
+
*/
|
|
30
|
+
readonly weights?: NumericVector | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Result of {@link maxFlow}. @public */
|
|
34
|
+
export interface MaxFlowResult {
|
|
35
|
+
/** The value of the maximum flow. */
|
|
36
|
+
readonly maxFlow: number;
|
|
37
|
+
/**
|
|
38
|
+
* Net flow per logical edge in its declared orientation. On an undirected snapshot a negative
|
|
39
|
+
* value is flow from the declared target to the declared source.
|
|
40
|
+
*/
|
|
41
|
+
readonly flow: Float64Array;
|
|
42
|
+
/** The nodes reachable from the source in the final residual graph: the source side of a minimum cut. */
|
|
43
|
+
readonly sourceSide: NodeMask;
|
|
44
|
+
/**
|
|
45
|
+
* Logical edges with positive capacity that cross the cut, in edge order: from the source side
|
|
46
|
+
* to the other on a directed snapshot, with exactly one endpoint on the source side on an
|
|
47
|
+
* undirected one.
|
|
48
|
+
*/
|
|
49
|
+
readonly cutEdges: U32;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Result of the index-based minimum cuts ({@link minSTCut}, `stoerWagner`, `kargerMinCut`). @public */
|
|
53
|
+
export interface MinCutResult {
|
|
54
|
+
/** The total weight of the cut. */
|
|
55
|
+
readonly cutValue: number;
|
|
56
|
+
/** One side of the cut; every other node is on the other side. */
|
|
57
|
+
readonly side: NodeMask;
|
|
58
|
+
/** Logical edges with one endpoint on each side, in edge order. */
|
|
59
|
+
readonly cutEdges: U32;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The capacity of every logical edge: the override's value at the edge's declared arc, else the
|
|
64
|
+
* snapshot's per-edge weight, else 1.
|
|
65
|
+
* @param s - The snapshot
|
|
66
|
+
* @param weights - The optional per-arc override
|
|
67
|
+
* @returns One capacity per logical edge
|
|
68
|
+
*/
|
|
69
|
+
export function edgeCapacities(s: GraphSnapshot, weights: NumericVector | undefined): Float64Array {
|
|
70
|
+
const el = s.edgeList();
|
|
71
|
+
const out = new Float64Array(s.edgeCount);
|
|
72
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
73
|
+
out[e] = weights !== undefined ? weights[el.arc[e]] : (el.weights?.[e] ?? 1);
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* The logical edges whose endpoints lie on different sides of `side` -- leaving it, when
|
|
80
|
+
* `directed` -- with positive capacity when `positiveOnly` is set.
|
|
81
|
+
* @param s - The snapshot
|
|
82
|
+
* @param side - One side of the cut
|
|
83
|
+
* @param directed - Whether only edges from `side` to the other side count
|
|
84
|
+
* @param capacity - Per-edge capacities, consulted only with `positiveOnly`
|
|
85
|
+
* @param positiveOnly - Whether to leave out edges of capacity zero or less
|
|
86
|
+
* @returns Edge indices in edge order
|
|
87
|
+
*/
|
|
88
|
+
export function crossingEdges(
|
|
89
|
+
s: GraphSnapshot,
|
|
90
|
+
side: NodeMask,
|
|
91
|
+
directed: boolean,
|
|
92
|
+
capacity: Float64Array,
|
|
93
|
+
positiveOnly: boolean,
|
|
94
|
+
): U32 {
|
|
95
|
+
const { src, dst } = s.edgeList();
|
|
96
|
+
const out: number[] = [];
|
|
97
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
98
|
+
const inSrc = maskTest(side, src[e]);
|
|
99
|
+
const crosses = directed ? inSrc && !maskTest(side, dst[e]) : inSrc !== maskTest(side, dst[e]);
|
|
100
|
+
if (crosses && (!positiveOnly || capacity[e] > 0)) {
|
|
101
|
+
out.push(e);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return Uint32Array.from(out);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The residual graph, kept per NODE PAIR: every arc from u to v is one entry.
|
|
109
|
+
*
|
|
110
|
+
* The residual snapshot is built with `fromEdgeArrays` over `[edges ++ reversed edges]`, so the
|
|
111
|
+
* twin of residual edge `r` is `r + E` below E and `r - E` above it (graph-format design 14.2's
|
|
112
|
+
* flow rule). A directed edge's reversed copy has capacity 0; an undirected edge carries its
|
|
113
|
+
* capacity both ways, so both copies are real. Rows are sorted by target, so the arcs from u to v
|
|
114
|
+
* are one contiguous GROUP, named by its first arc, and all residual state is per group: the
|
|
115
|
+
* capacities of parallel arcs, and of an undirected edge's two directions, add up. That is the
|
|
116
|
+
* legacy Map-of-Maps residual exactly, where `residual.get(u).get(v)` is one number per pair, and
|
|
117
|
+
* it is why the design's four-copy undirected layout is not needed.
|
|
118
|
+
*/
|
|
119
|
+
interface Residual {
|
|
120
|
+
readonly rowPtr: U32;
|
|
121
|
+
readonly colIdx: U32;
|
|
122
|
+
/** Residual edge of every arc. */
|
|
123
|
+
readonly arcToEdge: U32;
|
|
124
|
+
/** Residual capacity per group, at the group's first arc. */
|
|
125
|
+
readonly capacity: Float64Array;
|
|
126
|
+
/** The group of every arc. */
|
|
127
|
+
readonly groupOf: U32;
|
|
128
|
+
/** The group of the opposite pair (v to u), per group. */
|
|
129
|
+
readonly twin: U32;
|
|
130
|
+
/**
|
|
131
|
+
* Per node, the groups in the order a search visits them, in the node's own arc range:
|
|
132
|
+
* `order[rowPtr[u] .. rowPtr[u] + placed[u])`.
|
|
133
|
+
*/
|
|
134
|
+
readonly order: U32;
|
|
135
|
+
readonly placed: U32;
|
|
136
|
+
/** Whether a group is in its row's visiting order yet. */
|
|
137
|
+
readonly inOrder: Uint8Array;
|
|
138
|
+
/** Whether a group holds at least one real (non-reverse) arc. */
|
|
139
|
+
readonly real: Uint8Array;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build the pair-keyed residual graph of `s`. The visiting order starts as the legacy
|
|
144
|
+
* `graphToMap` row order -- each neighbour at the first edge that joins it -- and a group with no
|
|
145
|
+
* real arc is appended to its row when flow first reaches it, where the legacy residual Map
|
|
146
|
+
* inserts the key.
|
|
147
|
+
* @param s - The snapshot
|
|
148
|
+
* @param capacity - Per-edge capacities
|
|
149
|
+
* @returns The residual graph
|
|
150
|
+
*/
|
|
151
|
+
function buildResidual(s: GraphSnapshot, capacity: Float64Array): Residual {
|
|
152
|
+
const n = s.nodeCount;
|
|
153
|
+
const E = s.edgeCount;
|
|
154
|
+
const { src, dst } = s.edgeList();
|
|
155
|
+
const rSrc = new Uint32Array(2 * E);
|
|
156
|
+
const rDst = new Uint32Array(2 * E);
|
|
157
|
+
rSrc.set(src);
|
|
158
|
+
rSrc.set(dst, E);
|
|
159
|
+
rDst.set(dst);
|
|
160
|
+
rDst.set(src, E);
|
|
161
|
+
const r = fromEdgeArrays({ directed: true, nodeCount: n, src: rSrc, dst: rDst });
|
|
162
|
+
const { rowPtr, colIdx, arcToEdge, edgeToArc } = r;
|
|
163
|
+
const A = r.arcCount;
|
|
164
|
+
const residual = new Float64Array(A);
|
|
165
|
+
const groupOf = new Uint32Array(A);
|
|
166
|
+
const real = new Uint8Array(A);
|
|
167
|
+
for (let u = 0; u < n; u++) {
|
|
168
|
+
for (let a = rowPtr[u]; a < rowPtr[u + 1]; a++) {
|
|
169
|
+
const g = a > rowPtr[u] && colIdx[a] === colIdx[a - 1] ? groupOf[a - 1] : a;
|
|
170
|
+
groupOf[a] = g;
|
|
171
|
+
const re = arcToEdge[a];
|
|
172
|
+
if (re < E || !s.directed) {
|
|
173
|
+
real[g] = 1;
|
|
174
|
+
residual[g] += Math.max(capacity[re < E ? re : re - E], 0);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const twin = new Uint32Array(A);
|
|
179
|
+
for (let a = 0; a < A; a++) {
|
|
180
|
+
if (groupOf[a] === a) {
|
|
181
|
+
const re = arcToEdge[a];
|
|
182
|
+
twin[a] = groupOf[edgeToArc[re < E ? re + E : re - E]];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const order = new Uint32Array(A);
|
|
186
|
+
const placed = new Uint32Array(n);
|
|
187
|
+
const inOrder = new Uint8Array(A);
|
|
188
|
+
const place = (u: number, g: number): void => {
|
|
189
|
+
if (inOrder[g] === 0) {
|
|
190
|
+
inOrder[g] = 1;
|
|
191
|
+
order[rowPtr[u] + placed[u]++] = g;
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
for (let e = 0; e < E; e++) {
|
|
195
|
+
place(src[e], groupOf[edgeToArc[e]]);
|
|
196
|
+
if (!s.directed) {
|
|
197
|
+
place(dst[e], groupOf[edgeToArc[e + E]]);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { rowPtr, colIdx, arcToEdge, capacity: residual, groupOf, twin, order, placed, inOrder, real };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Maximum flow from `source` to `sink` by augmenting paths.
|
|
205
|
+
*
|
|
206
|
+
* Paths, bottlenecks and per-edge flows equal the legacy `edmondsKarp` / `fordFulkerson` on a
|
|
207
|
+
* graph with no parallel edges and no two opposite directed edges: the residual is kept per node
|
|
208
|
+
* pair and searched in the legacy row order (see {@link buildResidual}). Where two opposite
|
|
209
|
+
* directed edges carry flow, legacy records every push on the edge in the push's direction, so
|
|
210
|
+
* one edge can show more flow than its capacity; this port reports the net flow of the pair on
|
|
211
|
+
* the edges in its direction instead, each within its capacity.
|
|
212
|
+
* @param s - The snapshot
|
|
213
|
+
* @param source - The source node index
|
|
214
|
+
* @param sink - The sink node index
|
|
215
|
+
* @param options - The path search and the capacity override
|
|
216
|
+
* @returns The flow value, the per-edge flows, the source side and the cut edges
|
|
217
|
+
* @throws RangeError when `source` or `sink` is not a node index, or they are the same node
|
|
218
|
+
* @public
|
|
219
|
+
*/
|
|
220
|
+
export function maxFlow(s: GraphSnapshot, source: number, sink: number, options: MaxFlowOptions = {}): MaxFlowResult {
|
|
221
|
+
const n = s.nodeCount;
|
|
222
|
+
if (!(Number.isInteger(source) && source >= 0 && source < n && Number.isInteger(sink) && sink >= 0 && sink < n)) {
|
|
223
|
+
throw new RangeError(`source ${String(source)} and sink ${String(sink)} must be node indices below ${n}`);
|
|
224
|
+
}
|
|
225
|
+
if (source === sink) {
|
|
226
|
+
throw new RangeError(`source and sink are the same node (${source})`);
|
|
227
|
+
}
|
|
228
|
+
const capacity = edgeCapacities(s, options.weights);
|
|
229
|
+
const r = buildResidual(s, capacity);
|
|
230
|
+
const { colIdx, twin } = r;
|
|
231
|
+
// Flow pushed per real group, accumulated as the legacy flow Map accumulates it: a push along
|
|
232
|
+
// a pair with a real arc adds to that pair, a push along a reverse-only pair subtracts from
|
|
233
|
+
// the opposite one.
|
|
234
|
+
const pushed = new Float64Array(colIdx.length);
|
|
235
|
+
const predGroup = new Uint32Array(n);
|
|
236
|
+
const findPath = options.algorithm === "ford-fulkerson" ? depthFirstPath : breadthFirstPath;
|
|
237
|
+
let total = 0;
|
|
238
|
+
while (findPath(r, source, sink, predGroup)) {
|
|
239
|
+
let bottleneck = Infinity;
|
|
240
|
+
for (let v = sink; v !== source; v = colIdx[twin[predGroup[v]]]) {
|
|
241
|
+
bottleneck = Math.min(bottleneck, r.capacity[predGroup[v]]);
|
|
242
|
+
}
|
|
243
|
+
for (let v = sink; v !== source; ) {
|
|
244
|
+
const g = predGroup[v];
|
|
245
|
+
const t = twin[g];
|
|
246
|
+
r.capacity[g] -= bottleneck;
|
|
247
|
+
r.capacity[t] += bottleneck;
|
|
248
|
+
if (r.inOrder[t] === 0) {
|
|
249
|
+
r.inOrder[t] = 1;
|
|
250
|
+
r.order[r.rowPtr[v] + r.placed[v]++] = t;
|
|
251
|
+
}
|
|
252
|
+
if (r.real[g] === 1) {
|
|
253
|
+
pushed[g] += bottleneck;
|
|
254
|
+
} else {
|
|
255
|
+
pushed[t] -= bottleneck;
|
|
256
|
+
}
|
|
257
|
+
v = colIdx[t];
|
|
258
|
+
}
|
|
259
|
+
total += bottleneck;
|
|
260
|
+
}
|
|
261
|
+
const sourceSide = reachable(r, source);
|
|
262
|
+
return {
|
|
263
|
+
maxFlow: total,
|
|
264
|
+
flow: edgeFlows(s, r, capacity, pushed),
|
|
265
|
+
sourceSide,
|
|
266
|
+
cutEdges: crossingEdges(s, sourceSide, s.directed, capacity, true),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Spread each pair's net flow over its logical edges: all of it on the pair's only edge in the
|
|
272
|
+
* flow's direction, and in arc order up to capacity where there are several (the last one takes
|
|
273
|
+
* the remainder, so rounding never drops flow).
|
|
274
|
+
* @param s - The snapshot
|
|
275
|
+
* @param r - The residual graph after the last augmentation
|
|
276
|
+
* @param capacity - Per-edge capacities
|
|
277
|
+
* @param pushed - Flow pushed per real group
|
|
278
|
+
* @returns Net flow per logical edge in its declared orientation
|
|
279
|
+
*/
|
|
280
|
+
function edgeFlows(s: GraphSnapshot, r: Residual, capacity: Float64Array, pushed: Float64Array): Float64Array {
|
|
281
|
+
const E = s.edgeCount;
|
|
282
|
+
const flow = new Float64Array(E);
|
|
283
|
+
const { rowPtr, colIdx, arcToEdge, groupOf } = r;
|
|
284
|
+
for (let u = 0; u < s.nodeCount; u++) {
|
|
285
|
+
for (let g = rowPtr[u]; g < rowPtr[u + 1]; g++) {
|
|
286
|
+
const v = colIdx[g];
|
|
287
|
+
if (groupOf[g] !== g || v <= u) {
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
const net = pushed[g] - pushed[r.twin[g]];
|
|
291
|
+
if (net === 0) {
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
// Walk the arcs of the group the flow runs along: u to v, or v to u.
|
|
295
|
+
const along = net > 0 ? g : r.twin[g];
|
|
296
|
+
const end = rowPtr[colIdx[r.twin[along]] + 1];
|
|
297
|
+
let remaining = Math.abs(net);
|
|
298
|
+
let lastEdge = -1;
|
|
299
|
+
let lastSign = 1;
|
|
300
|
+
for (let a = along; a < end && groupOf[a] === along; a++) {
|
|
301
|
+
const re = arcToEdge[a];
|
|
302
|
+
if (re >= E && s.directed) {
|
|
303
|
+
continue; // a reverse arc carries no edge
|
|
304
|
+
}
|
|
305
|
+
if (lastEdge >= 0) {
|
|
306
|
+
const x = Math.min(remaining, Math.max(capacity[lastEdge], 0));
|
|
307
|
+
flow[lastEdge] += lastSign * x;
|
|
308
|
+
remaining -= x;
|
|
309
|
+
}
|
|
310
|
+
lastEdge = re < E ? re : re - E;
|
|
311
|
+
lastSign = re < E ? 1 : -1;
|
|
312
|
+
}
|
|
313
|
+
if (lastEdge >= 0) {
|
|
314
|
+
flow[lastEdge] += lastSign * remaining;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return flow;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Minimum s-t cut: the source side and value of a maximum flow (max-flow min-cut theorem). The
|
|
323
|
+
* side is the set reachable from the source in the final residual graph, which is the same for
|
|
324
|
+
* every maximum flow, so the path search does not change it. The search defaults to
|
|
325
|
+
* `"ford-fulkerson"`, as the legacy `minSTCut` uses: on weights that are not binary fractions
|
|
326
|
+
* another search adds the bottlenecks in another order and the value can differ in its last bits.
|
|
327
|
+
* @param s - The snapshot
|
|
328
|
+
* @param source - The source node index
|
|
329
|
+
* @param sink - The sink node index
|
|
330
|
+
* @param options - The path search and the capacity override
|
|
331
|
+
* @returns The cut value, the source side and the cut edges
|
|
332
|
+
* @throws RangeError as {@link maxFlow} does
|
|
333
|
+
* @public
|
|
334
|
+
*/
|
|
335
|
+
export function minSTCut(s: GraphSnapshot, source: number, sink: number, options: MaxFlowOptions = {}): MinCutResult {
|
|
336
|
+
const r = maxFlow(s, source, sink, { ...options, algorithm: options.algorithm ?? "ford-fulkerson" });
|
|
337
|
+
return { cutValue: r.maxFlow, side: r.sourceSide, cutEdges: r.cutEdges };
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Breadth-first search for a source-to-sink path over groups with residual capacity, stopping when
|
|
342
|
+
* the sink is discovered (its parent is fixed then, as in the legacy search that stops when it
|
|
343
|
+
* dequeues the sink).
|
|
344
|
+
* @param r - The residual graph
|
|
345
|
+
* @param source - The source node index
|
|
346
|
+
* @param sink - The sink node index
|
|
347
|
+
* @param predGroup - Receives the group that discovered each node on the path
|
|
348
|
+
* @returns Whether the sink was reached
|
|
349
|
+
*/
|
|
350
|
+
function breadthFirstPath(r: Residual, source: number, sink: number, predGroup: U32): boolean {
|
|
351
|
+
const { rowPtr, colIdx, order, placed, capacity } = r;
|
|
352
|
+
predGroup.fill(INVALID_INDEX);
|
|
353
|
+
const queue = new Uint32Array(predGroup.length);
|
|
354
|
+
let head = 0;
|
|
355
|
+
let tail = 0;
|
|
356
|
+
queue[tail++] = source;
|
|
357
|
+
while (head < tail) {
|
|
358
|
+
const u = queue[head++];
|
|
359
|
+
const end = rowPtr[u] + placed[u];
|
|
360
|
+
for (let k = rowPtr[u]; k < end; k++) {
|
|
361
|
+
const g = order[k];
|
|
362
|
+
const v = colIdx[g];
|
|
363
|
+
if (capacity[g] > 0 && v !== source && predGroup[v] === INVALID_INDEX) {
|
|
364
|
+
predGroup[v] = g;
|
|
365
|
+
if (v === sink) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
queue[tail++] = v;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Depth-first search for a source-to-sink path over groups with residual capacity, in the order a
|
|
377
|
+
* recursive search takes: the first group with capacity to an unvisited node is followed to its
|
|
378
|
+
* end before the next is tried.
|
|
379
|
+
* @param r - The residual graph
|
|
380
|
+
* @param source - The source node index
|
|
381
|
+
* @param sink - The sink node index
|
|
382
|
+
* @param predGroup - Receives the group that entered each node on the path
|
|
383
|
+
* @returns Whether the sink was reached
|
|
384
|
+
*/
|
|
385
|
+
function depthFirstPath(r: Residual, source: number, sink: number, predGroup: U32): boolean {
|
|
386
|
+
const { rowPtr, colIdx, order, placed, capacity } = r;
|
|
387
|
+
const n = predGroup.length;
|
|
388
|
+
predGroup.fill(INVALID_INDEX);
|
|
389
|
+
const visited = new Uint8Array(n);
|
|
390
|
+
const stack = new Uint32Array(n);
|
|
391
|
+
const cursor = new Uint32Array(n);
|
|
392
|
+
let depth = 0;
|
|
393
|
+
stack[depth++] = source;
|
|
394
|
+
visited[source] = 1;
|
|
395
|
+
cursor[source] = rowPtr[source];
|
|
396
|
+
while (depth > 0) {
|
|
397
|
+
const u = stack[depth - 1];
|
|
398
|
+
const k = cursor[u]++;
|
|
399
|
+
if (k >= rowPtr[u] + placed[u]) {
|
|
400
|
+
depth--;
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const g = order[k];
|
|
404
|
+
const v = colIdx[g];
|
|
405
|
+
if (capacity[g] > 0 && visited[v] === 0) {
|
|
406
|
+
predGroup[v] = g;
|
|
407
|
+
if (v === sink) {
|
|
408
|
+
return true;
|
|
409
|
+
}
|
|
410
|
+
visited[v] = 1;
|
|
411
|
+
cursor[v] = rowPtr[v];
|
|
412
|
+
stack[depth++] = v;
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return false;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* The nodes reachable from `source` over groups with residual capacity.
|
|
420
|
+
* @param r - The residual graph
|
|
421
|
+
* @param source - The source node index
|
|
422
|
+
* @returns The reachable set as a node mask
|
|
423
|
+
*/
|
|
424
|
+
function reachable(r: Residual, source: number): NodeMask {
|
|
425
|
+
const { rowPtr, colIdx, order, placed, capacity } = r;
|
|
426
|
+
const n = rowPtr.length - 1;
|
|
427
|
+
const mask = makeMask(n);
|
|
428
|
+
const queue = new Uint32Array(n);
|
|
429
|
+
let head = 0;
|
|
430
|
+
let tail = 0;
|
|
431
|
+
queue[tail++] = source;
|
|
432
|
+
maskSet(mask, source, true);
|
|
433
|
+
while (head < tail) {
|
|
434
|
+
const u = queue[head++];
|
|
435
|
+
const end = rowPtr[u] + placed[u];
|
|
436
|
+
for (let k = rowPtr[u]; k < end; k++) {
|
|
437
|
+
const v = colIdx[order[k]];
|
|
438
|
+
if (capacity[order[k]] > 0 && !maskTest(mask, v)) {
|
|
439
|
+
maskSet(mask, v, true);
|
|
440
|
+
queue[tail++] = v;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
return mask;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** Result of {@link bipartiteFlowNetwork}. @public */
|
|
448
|
+
export interface BipartiteFlowNetwork {
|
|
449
|
+
/** The directed, unit-capacity (unweighted) flow network. */
|
|
450
|
+
readonly snapshot: GraphSnapshot;
|
|
451
|
+
/** The index of the added source node, id `"__source__"`. */
|
|
452
|
+
readonly source: number;
|
|
453
|
+
/** The index of the added sink node, id `"__sink__"`. */
|
|
454
|
+
readonly sink: number;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const SOURCE_ID = "__source__";
|
|
458
|
+
const SINK_ID = "__sink__";
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* The unit-capacity flow network whose maximum flow is a maximum bipartite matching: a source with
|
|
462
|
+
* an edge to every left node, every given left-right edge, and an edge from every right node to a
|
|
463
|
+
* sink. Nodes are numbered source, left nodes, left endpoints of `edges` not already listed, right
|
|
464
|
+
* nodes, right endpoints not already listed, sink; a repeated edge is kept once.
|
|
465
|
+
* @param left - The left side's node ids
|
|
466
|
+
* @param right - The right side's node ids
|
|
467
|
+
* @param edges - Left-to-right edges as `[left id, right id]`
|
|
468
|
+
* @returns The network and the indices of its source and sink
|
|
469
|
+
* @throws RangeError when a given id is `"__source__"` or `"__sink__"`
|
|
470
|
+
* @public
|
|
471
|
+
*/
|
|
472
|
+
export function bipartiteFlowNetwork(
|
|
473
|
+
left: readonly NodeId[],
|
|
474
|
+
right: readonly NodeId[],
|
|
475
|
+
edges: readonly (readonly [NodeId, NodeId])[],
|
|
476
|
+
): BipartiteFlowNetwork {
|
|
477
|
+
const b = new GraphBuilder({ directed: true, duplicateEdges: "first" });
|
|
478
|
+
const add = (id: NodeId): number => {
|
|
479
|
+
if (id === SOURCE_ID || id === SINK_ID) {
|
|
480
|
+
throw new RangeError(`node id "${id}" is reserved for the flow network's source or sink`);
|
|
481
|
+
}
|
|
482
|
+
return b.addNode(id);
|
|
483
|
+
};
|
|
484
|
+
const source = b.addNode(SOURCE_ID);
|
|
485
|
+
const leftIndex = left.map(add);
|
|
486
|
+
const edgeLeft = edges.map(([u]) => add(u));
|
|
487
|
+
const rightIndex = right.map(add);
|
|
488
|
+
const edgeRight = edges.map(([, v]) => add(v));
|
|
489
|
+
const sink = b.addNode(SINK_ID);
|
|
490
|
+
for (const u of leftIndex) {
|
|
491
|
+
b.addEdgeByIndex(source, u);
|
|
492
|
+
}
|
|
493
|
+
for (let e = 0; e < edges.length; e++) {
|
|
494
|
+
b.addEdgeByIndex(edgeLeft[e], edgeRight[e]);
|
|
495
|
+
}
|
|
496
|
+
for (const v of rightIndex) {
|
|
497
|
+
b.addEdgeByIndex(v, sink);
|
|
498
|
+
}
|
|
499
|
+
return { snapshot: b.freeze({ label: "algorithms.bipartiteFlowNetwork" }), source, sink };
|
|
500
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type EdgeMask,
|
|
3
|
+
type F64,
|
|
4
|
+
type GraphSnapshot,
|
|
5
|
+
makeMask,
|
|
6
|
+
maskCount,
|
|
7
|
+
maskSet,
|
|
8
|
+
maskTest,
|
|
9
|
+
type U32,
|
|
10
|
+
} from "@graphty/graph-format";
|
|
11
|
+
|
|
12
|
+
import { edgeBetweennessCentrality } from "./betweenness.js";
|
|
13
|
+
import { arcWeightsOf, exactEdgeWeights } from "./label-propagation.js";
|
|
14
|
+
import { modularity } from "./modularity.js";
|
|
15
|
+
import { IntUnionFind } from "./structures/union-find.js";
|
|
16
|
+
|
|
17
|
+
/** Options of the index-based Girvan-Newman, matching the legacy `girvanNewman`. @public */
|
|
18
|
+
export interface GirvanNewmanOptions {
|
|
19
|
+
/** Stop once a level has at least this many communities of `minCommunitySize` or more nodes. */
|
|
20
|
+
readonly maxCommunities?: number | undefined;
|
|
21
|
+
/** Communities smaller than this do not count towards `maxCommunities`; default 1. */
|
|
22
|
+
readonly minCommunitySize?: number | undefined;
|
|
23
|
+
/** Cap on rounds of edge removal; default 100. */
|
|
24
|
+
readonly maxIterations?: number | undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** The Girvan-Newman dendrogram: one partition per level, the uncut graph first. @public */
|
|
28
|
+
export interface GirvanNewmanResult {
|
|
29
|
+
/** Dense community label per node index at each level, in first-seen order. */
|
|
30
|
+
readonly levels: U32[];
|
|
31
|
+
/** Modularity of each level's partition over the original graph. */
|
|
32
|
+
readonly modularity: F64;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Scores this close to the maximum are removed together, as the legacy function does. */
|
|
36
|
+
const TIE = 1e-10;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Connected components over the edges still alive.
|
|
40
|
+
* @param s - The snapshot
|
|
41
|
+
* @param alive - Kept edges
|
|
42
|
+
* @returns Dense labels and their count
|
|
43
|
+
*/
|
|
44
|
+
function aliveComponents(s: GraphSnapshot, alive: EdgeMask): { labels: U32; count: number } {
|
|
45
|
+
const uf = new IntUnionFind(s.nodeCount);
|
|
46
|
+
const el = s.edgeList();
|
|
47
|
+
for (let e = 0; e < s.edgeCount; e++) {
|
|
48
|
+
if (maskTest(alive, e)) {
|
|
49
|
+
uf.union(el.src[e], el.dst[e]);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return uf.toLabels();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Girvan-Newman divisive community detection (PNAS 99:7821, 2002) over an undirected snapshot:
|
|
57
|
+
* repeatedly delete every edge whose betweenness is the maximum (within 1e-10), recording the
|
|
58
|
+
* connected components after each round as one level of a dendrogram.
|
|
59
|
+
*
|
|
60
|
+
* Edges are deleted by clearing bits of an alive mask over logical edges that the edge betweenness
|
|
61
|
+
* reads, so no graph is rebuilt. Betweenness is unweighted, as in the legacy function; a pair of
|
|
62
|
+
* nodes joined by parallel edges is one neighbour relation whose share goes to the first alive
|
|
63
|
+
* parallel, so the parallels are deleted one round at a time. Modularity reads the edge weights,
|
|
64
|
+
* the exact f64 ones when the snapshot keeps them, with graph-format's `weightedDegree()` rule (a
|
|
65
|
+
* self-loop counts twice); on a graph without self-loops it equals the legacy function's.
|
|
66
|
+
*
|
|
67
|
+
* The run stops when no edge is left, after `maxIterations` rounds, when a level has
|
|
68
|
+
* `maxCommunities` communities of at least `minCommunitySize` nodes, or when that count reaches
|
|
69
|
+
* the node count. Every level is returned whole: the legacy function drops communities below
|
|
70
|
+
* `minCommunitySize` from the lists it returns, which a caller does with `levels[i]` and a size
|
|
71
|
+
* count.
|
|
72
|
+
* @param s - An undirected snapshot
|
|
73
|
+
* @param options - Stopping rules
|
|
74
|
+
* @returns The levels and their modularity
|
|
75
|
+
* @throws Error on a directed snapshot
|
|
76
|
+
* @public
|
|
77
|
+
*/
|
|
78
|
+
export function girvanNewman(s: GraphSnapshot, options: GirvanNewmanOptions = {}): GirvanNewmanResult {
|
|
79
|
+
if (s.directed) {
|
|
80
|
+
throw new Error("Girvan-Newman requires an undirected graph. Pass s.toUndirected().snapshot.");
|
|
81
|
+
}
|
|
82
|
+
const maxCommunities = options.maxCommunities ?? 0;
|
|
83
|
+
const minCommunitySize = options.minCommunitySize ?? 1;
|
|
84
|
+
const maxIterations = options.maxIterations ?? 100;
|
|
85
|
+
const { nodeCount: n, edgeCount } = s;
|
|
86
|
+
const weights = arcWeightsOf(s, exactEdgeWeights(s));
|
|
87
|
+
const alive = makeMask(edgeCount, true);
|
|
88
|
+
const levels: U32[] = [];
|
|
89
|
+
const scores: number[] = [];
|
|
90
|
+
const record = (): number => {
|
|
91
|
+
const { labels, count } = aliveComponents(s, alive);
|
|
92
|
+
levels.push(labels);
|
|
93
|
+
scores.push(modularity(s, labels, weights === null ? {} : { weights }));
|
|
94
|
+
const sizes = new Uint32Array(count);
|
|
95
|
+
for (let u = 0; u < n; u++) {
|
|
96
|
+
sizes[labels[u]]++;
|
|
97
|
+
}
|
|
98
|
+
return sizes.filter((size) => size >= minCommunitySize).length;
|
|
99
|
+
};
|
|
100
|
+
record();
|
|
101
|
+
for (let round = 0; round < maxIterations && maskCount(alive, edgeCount) > 0; round++) {
|
|
102
|
+
const betweenness = edgeBetweennessCentrality(s, { alive }).scores;
|
|
103
|
+
let max = -Infinity;
|
|
104
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
105
|
+
if (maskTest(alive, e) && betweenness[e] > max) {
|
|
106
|
+
max = betweenness[e];
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
for (let e = 0; e < edgeCount; e++) {
|
|
110
|
+
if (maskTest(alive, e) && Math.abs(betweenness[e] - max) < TIE) {
|
|
111
|
+
maskSet(alive, e, false);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const valid = record();
|
|
115
|
+
if ((maxCommunities > 0 && valid >= maxCommunities) || valid === n) {
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return { levels, modularity: Float64Array.from(scores) };
|
|
120
|
+
}
|