@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,324 @@
|
|
|
1
|
+
import type { AdjacencyView, NumericVector } from "@graphty/graph-format";
|
|
2
|
+
|
|
3
|
+
import { type LabelResult, withGroups } from "./components.js";
|
|
4
|
+
|
|
5
|
+
/** Options of the index-based Markov clustering, with the legacy `markovClustering` defaults. @public */
|
|
6
|
+
export interface MarkovOptions {
|
|
7
|
+
/** Matrix power of each expansion step, an integer of at least 1; default 2. */
|
|
8
|
+
readonly expansion?: number | undefined;
|
|
9
|
+
/** Element-wise power of each inflation step, above 0; default 2. */
|
|
10
|
+
readonly inflation?: number | undefined;
|
|
11
|
+
/** Cap on expansion-inflation rounds; default 100. */
|
|
12
|
+
readonly maxIterations?: number | undefined;
|
|
13
|
+
/** Stop when no matrix entry moved by more than this in a round; default 1e-6. */
|
|
14
|
+
readonly tolerance?: number | undefined;
|
|
15
|
+
/** Entries below this are dropped after each inflation; default 1e-5. */
|
|
16
|
+
readonly pruningThreshold?: number | undefined;
|
|
17
|
+
/** Give every node a self-loop of weight 1 before the first round; default true. */
|
|
18
|
+
readonly selfLoops?: boolean | undefined;
|
|
19
|
+
/** Per-arc weight override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`. */
|
|
20
|
+
readonly weights?: NumericVector | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Result of the index-based Markov clustering. @public */
|
|
24
|
+
export interface MarkovResult extends LabelResult {
|
|
25
|
+
/** Nodes whose own flow survived (a positive diagonal entry), in index order. */
|
|
26
|
+
readonly attractors: Uint32Array;
|
|
27
|
+
/** Expansion-inflation rounds run. */
|
|
28
|
+
readonly iterations: number;
|
|
29
|
+
/** True when the last round moved no entry by more than `tolerance`. */
|
|
30
|
+
readonly converged: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A column-stochastic sparse matrix: column j holds rows `row[ptr[j]] .. row[ptr[j+1] - 1]`, ascending. */
|
|
34
|
+
interface Csc {
|
|
35
|
+
readonly ptr: Uint32Array;
|
|
36
|
+
readonly row: Uint32Array;
|
|
37
|
+
readonly val: Float64Array;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Divide every column by its sum, summed in ascending row order as the legacy dense loop sums it.
|
|
42
|
+
* @param m - The matrix, changed in place
|
|
43
|
+
*/
|
|
44
|
+
function normaliseColumns(m: Csc): void {
|
|
45
|
+
for (let j = 0; j + 1 < m.ptr.length; j++) {
|
|
46
|
+
let sum = 0;
|
|
47
|
+
for (let p = m.ptr[j]; p < m.ptr[j + 1]; p++) {
|
|
48
|
+
sum += m.val[p];
|
|
49
|
+
}
|
|
50
|
+
if (sum > 0) {
|
|
51
|
+
for (let p = m.ptr[j]; p < m.ptr[j + 1]; p++) {
|
|
52
|
+
m.val[p] /= sum;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The product `a * b`. For every entry the products are added in ascending k, the order the legacy
|
|
60
|
+
* dense triple loop adds them in; the zero products it adds as well leave a sum unchanged, so the
|
|
61
|
+
* result is bit-identical to the dense one.
|
|
62
|
+
* @param a - Left factor
|
|
63
|
+
* @param b - Right factor
|
|
64
|
+
* @param acc - n-long scratch, all zero on entry and on exit
|
|
65
|
+
* @param mark - n-long scratch, all zero on entry and on exit
|
|
66
|
+
* @returns The product
|
|
67
|
+
*/
|
|
68
|
+
function multiply(a: Csc, b: Csc, acc: Float64Array, mark: Uint8Array): Csc {
|
|
69
|
+
const n = acc.length;
|
|
70
|
+
const ptr = new Uint32Array(n + 1);
|
|
71
|
+
const rows: number[] = [];
|
|
72
|
+
const vals: number[] = [];
|
|
73
|
+
const touched: number[] = [];
|
|
74
|
+
for (let j = 0; j < n; j++) {
|
|
75
|
+
touched.length = 0;
|
|
76
|
+
for (let q = b.ptr[j]; q < b.ptr[j + 1]; q++) {
|
|
77
|
+
const k = b.row[q];
|
|
78
|
+
const bkj = b.val[q];
|
|
79
|
+
for (let p = a.ptr[k]; p < a.ptr[k + 1]; p++) {
|
|
80
|
+
const i = a.row[p];
|
|
81
|
+
if (mark[i] === 0) {
|
|
82
|
+
mark[i] = 1;
|
|
83
|
+
touched.push(i);
|
|
84
|
+
}
|
|
85
|
+
acc[i] += a.val[p] * bkj;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
touched.sort((x, y) => x - y);
|
|
89
|
+
for (const i of touched) {
|
|
90
|
+
rows.push(i);
|
|
91
|
+
vals.push(acc[i]);
|
|
92
|
+
acc[i] = 0;
|
|
93
|
+
mark[i] = 0;
|
|
94
|
+
}
|
|
95
|
+
ptr[j + 1] = rows.length;
|
|
96
|
+
}
|
|
97
|
+
return { ptr, row: Uint32Array.from(rows), val: Float64Array.from(vals) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Raise every entry to `inflation`, renormalise, drop the entries below `threshold` and renormalise
|
|
102
|
+
* again: the legacy inflate and prune steps.
|
|
103
|
+
* @param m - The expanded matrix
|
|
104
|
+
* @param inflation - The element-wise power
|
|
105
|
+
* @param threshold - The pruning threshold
|
|
106
|
+
* @returns The new matrix
|
|
107
|
+
*/
|
|
108
|
+
function inflateAndPrune(m: Csc, inflation: number, threshold: number): Csc {
|
|
109
|
+
const powered: Csc = { ptr: m.ptr, row: m.row, val: m.val.map((v) => Math.pow(v, inflation)) };
|
|
110
|
+
normaliseColumns(powered);
|
|
111
|
+
const n = m.ptr.length - 1;
|
|
112
|
+
const ptr = new Uint32Array(n + 1);
|
|
113
|
+
const rows: number[] = [];
|
|
114
|
+
const vals: number[] = [];
|
|
115
|
+
for (let j = 0; j < n; j++) {
|
|
116
|
+
for (let p = powered.ptr[j]; p < powered.ptr[j + 1]; p++) {
|
|
117
|
+
if (powered.val[p] >= threshold) {
|
|
118
|
+
rows.push(powered.row[p]);
|
|
119
|
+
vals.push(powered.val[p]);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
ptr[j + 1] = rows.length;
|
|
123
|
+
}
|
|
124
|
+
const out = { ptr, row: Uint32Array.from(rows), val: Float64Array.from(vals) };
|
|
125
|
+
normaliseColumns(out);
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Whether no entry differs by more than `tolerance` between two matrices, an absent entry being 0.
|
|
131
|
+
* @param a - The previous matrix
|
|
132
|
+
* @param b - The new matrix
|
|
133
|
+
* @param tolerance - The tolerance
|
|
134
|
+
* @returns True when every entry is within tolerance
|
|
135
|
+
*/
|
|
136
|
+
function withinTolerance(a: Csc, b: Csc, tolerance: number): boolean {
|
|
137
|
+
for (let j = 0; j + 1 < a.ptr.length; j++) {
|
|
138
|
+
let p = a.ptr[j];
|
|
139
|
+
let q = b.ptr[j];
|
|
140
|
+
const pe = a.ptr[j + 1];
|
|
141
|
+
const qe = b.ptr[j + 1];
|
|
142
|
+
while (p < pe || q < qe) {
|
|
143
|
+
let diff: number;
|
|
144
|
+
if (q >= qe || (p < pe && a.row[p] < b.row[q])) {
|
|
145
|
+
diff = a.val[p++];
|
|
146
|
+
} else if (p >= pe || b.row[q] < a.row[p]) {
|
|
147
|
+
diff = b.val[q++];
|
|
148
|
+
} else {
|
|
149
|
+
diff = a.val[p++] - b.val[q++];
|
|
150
|
+
}
|
|
151
|
+
if (Math.abs(diff) > tolerance) {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The starting matrix: entry (i, j) is the weight of the arc i -> j (parallel arcs summed), the
|
|
161
|
+
* diagonal set to 1 when `selfLoops`, every column normalised.
|
|
162
|
+
* @param s - The adjacency
|
|
163
|
+
* @param weights - Per-arc weights, or null for all ones
|
|
164
|
+
* @param selfLoops - Whether to set the diagonal to 1
|
|
165
|
+
* @returns The column-stochastic matrix
|
|
166
|
+
*/
|
|
167
|
+
function transitionMatrix(s: AdjacencyView, weights: NumericVector | null, selfLoops: boolean): Csc {
|
|
168
|
+
const n = s.nodeCount;
|
|
169
|
+
// Column j of the matrix is the set of arcs INTO j: a counting sort of the arcs by target, which
|
|
170
|
+
// keeps each column's rows ascending because the rows are visited in order.
|
|
171
|
+
const ptr = new Uint32Array(n + 1);
|
|
172
|
+
for (let a = 0; a < s.arcCount; a++) {
|
|
173
|
+
ptr[s.colIdx[a] + 1]++;
|
|
174
|
+
}
|
|
175
|
+
if (selfLoops) {
|
|
176
|
+
for (let j = 0; j < n; j++) {
|
|
177
|
+
ptr[j + 1]++;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
for (let j = 0; j < n; j++) {
|
|
181
|
+
ptr[j + 1] += ptr[j];
|
|
182
|
+
}
|
|
183
|
+
const fill = ptr.slice(0, n);
|
|
184
|
+
const row = new Uint32Array(ptr[n]);
|
|
185
|
+
const val = new Float64Array(ptr[n]);
|
|
186
|
+
const put = (i: number, j: number, w: number, replace: boolean): void => {
|
|
187
|
+
const last = fill[j] - 1;
|
|
188
|
+
if (last >= ptr[j] && row[last] === i) {
|
|
189
|
+
val[last] = replace ? w : val[last] + w;
|
|
190
|
+
} else {
|
|
191
|
+
row[fill[j]] = i;
|
|
192
|
+
val[fill[j]++] = w;
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
for (let i = 0; i < n; i++) {
|
|
196
|
+
// The diagonal goes in its place in row order: the arcs i -> j with j < i were placed while
|
|
197
|
+
// visiting earlier rows, so for column i this is where row i belongs.
|
|
198
|
+
for (let a = s.rowPtr[i]; a < s.rowPtr[i + 1]; a++) {
|
|
199
|
+
const j = s.colIdx[a];
|
|
200
|
+
const w = weights === null ? 1 : weights[a];
|
|
201
|
+
if (!(w >= 0) || w === Infinity) {
|
|
202
|
+
throw new RangeError(`arc ${a} has weight ${w}; Markov clustering needs finite, non-negative weights`);
|
|
203
|
+
}
|
|
204
|
+
put(i, j, w, false);
|
|
205
|
+
}
|
|
206
|
+
if (selfLoops) {
|
|
207
|
+
put(i, i, 1, true);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Compact: merged parallel arcs and a self-loop folded into the diagonal left gaps.
|
|
211
|
+
const outPtr = new Uint32Array(n + 1);
|
|
212
|
+
const rows: number[] = [];
|
|
213
|
+
const vals: number[] = [];
|
|
214
|
+
for (let j = 0; j < n; j++) {
|
|
215
|
+
for (let p = ptr[j]; p < fill[j]; p++) {
|
|
216
|
+
rows.push(row[p]);
|
|
217
|
+
vals.push(val[p]);
|
|
218
|
+
}
|
|
219
|
+
outPtr[j + 1] = rows.length;
|
|
220
|
+
}
|
|
221
|
+
const m = { ptr: outPtr, row: Uint32Array.from(rows), val: Float64Array.from(vals) };
|
|
222
|
+
normaliseColumns(m);
|
|
223
|
+
return m;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Markov clustering (MCL; van Dongen, 2000), the index-based port of the legacy `markovClustering`.
|
|
228
|
+
*
|
|
229
|
+
* The flow matrix starts as the column-normalised adjacency: entry (i, j) is the weight of the arc
|
|
230
|
+
* i -> j, so column j spreads j's flow over the nodes with an arc into it (on an undirected
|
|
231
|
+
* snapshot, its neighbours). With `selfLoops` every diagonal entry is first set to 1, replacing a
|
|
232
|
+
* self-loop's weight. Each round raises the matrix to the `expansion` power, raises every entry to
|
|
233
|
+
* the `inflation` power, renormalises the columns, drops the entries below `pruningThreshold` and
|
|
234
|
+
* renormalises again, until no entry moves by more than `tolerance` or `maxIterations` rounds ran.
|
|
235
|
+
* Node j then joins the attractor with the largest entry in its column, the lowest index on a tie;
|
|
236
|
+
* a node whose column emptied is a community of its own.
|
|
237
|
+
*
|
|
238
|
+
* The matrix is kept sparse, and every sum is taken in the order the legacy dense loops take it, so
|
|
239
|
+
* the rounds are bit-identical to the legacy function's and so are the communities, their order and
|
|
240
|
+
* the attractors. Communities are labelled in the legacy function's order: the flow clusters by
|
|
241
|
+
* their lowest member, then the nodes whose column emptied. Two differences: parallel arcs are
|
|
242
|
+
* summed (a legacy graph has none), and `iterations` counts the rounds run, where the legacy
|
|
243
|
+
* function reports one more than `maxIterations` when it stops at the cap. A negative, NaN or
|
|
244
|
+
* infinite weight throws.
|
|
245
|
+
* @param s - Any snapshot or adjacency view
|
|
246
|
+
* @param options - MCL parameters and the weight override
|
|
247
|
+
* @returns The partition, the attractors, the rounds run and whether they converged
|
|
248
|
+
* @public
|
|
249
|
+
*/
|
|
250
|
+
export function markovClustering(s: AdjacencyView, options: MarkovOptions = {}): MarkovResult {
|
|
251
|
+
const expansion = options.expansion ?? 2;
|
|
252
|
+
const inflation = options.inflation ?? 2;
|
|
253
|
+
const maxIterations = options.maxIterations ?? 100;
|
|
254
|
+
const tolerance = options.tolerance ?? 1e-6;
|
|
255
|
+
const pruningThreshold = options.pruningThreshold ?? 1e-5;
|
|
256
|
+
if (!Number.isInteger(expansion) || expansion < 1) {
|
|
257
|
+
throw new RangeError(`expansion must be an integer of at least 1, got ${expansion}`);
|
|
258
|
+
}
|
|
259
|
+
if (!(inflation > 0) || inflation === Infinity) {
|
|
260
|
+
throw new RangeError(`inflation must be finite and above 0, got ${inflation}`);
|
|
261
|
+
}
|
|
262
|
+
if (!Number.isInteger(maxIterations) || maxIterations < 0) {
|
|
263
|
+
throw new RangeError(`maxIterations must be a non-negative integer, got ${maxIterations}`);
|
|
264
|
+
}
|
|
265
|
+
if (!(tolerance >= 0) || !(pruningThreshold >= 0)) {
|
|
266
|
+
throw new RangeError("tolerance and pruningThreshold must be non-negative");
|
|
267
|
+
}
|
|
268
|
+
const weights = options.weights ?? s.weights;
|
|
269
|
+
if (weights !== null && weights.length !== s.arcCount) {
|
|
270
|
+
throw new RangeError(`weights has ${weights.length} entries; the snapshot has ${s.arcCount} arcs`);
|
|
271
|
+
}
|
|
272
|
+
const n = s.nodeCount;
|
|
273
|
+
let m = transitionMatrix(s, weights, options.selfLoops ?? true);
|
|
274
|
+
const acc = new Float64Array(n);
|
|
275
|
+
const mark = new Uint8Array(n);
|
|
276
|
+
let converged = n === 0;
|
|
277
|
+
let iterations = 0;
|
|
278
|
+
while (!converged && iterations < maxIterations) {
|
|
279
|
+
let expanded = m;
|
|
280
|
+
for (let t = 1; t < expansion; t++) {
|
|
281
|
+
expanded = multiply(expanded, m, acc, mark);
|
|
282
|
+
}
|
|
283
|
+
const next = inflateAndPrune(expanded, inflation, pruningThreshold);
|
|
284
|
+
iterations++;
|
|
285
|
+
converged = withinTolerance(m, next, tolerance);
|
|
286
|
+
m = next;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const attractors: number[] = [];
|
|
290
|
+
// Column j's attractor: the row of its largest positive entry, the first on a tie.
|
|
291
|
+
const attractorOf = new Int32Array(n).fill(-1);
|
|
292
|
+
for (let j = 0; j < n; j++) {
|
|
293
|
+
let best = 0;
|
|
294
|
+
for (let p = m.ptr[j]; p < m.ptr[j + 1]; p++) {
|
|
295
|
+
if (m.row[p] === j && m.val[p] > 0) {
|
|
296
|
+
attractors.push(j);
|
|
297
|
+
}
|
|
298
|
+
if (m.val[p] > best) {
|
|
299
|
+
best = m.val[p];
|
|
300
|
+
attractorOf[j] = m.row[p];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const labels = new Uint32Array(n);
|
|
305
|
+
const labelOfAttractor = new Map<number, number>();
|
|
306
|
+
for (let j = 0; j < n; j++) {
|
|
307
|
+
const a = attractorOf[j];
|
|
308
|
+
if (a >= 0) {
|
|
309
|
+
let label = labelOfAttractor.get(a);
|
|
310
|
+
if (label === undefined) {
|
|
311
|
+
label = labelOfAttractor.size;
|
|
312
|
+
labelOfAttractor.set(a, label);
|
|
313
|
+
}
|
|
314
|
+
labels[j] = label;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
let count = labelOfAttractor.size;
|
|
318
|
+
for (let j = 0; j < n; j++) {
|
|
319
|
+
if (attractorOf[j] < 0) {
|
|
320
|
+
labels[j] = count++;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { ...withGroups(labels, count), attractors: Uint32Array.from(attractors), iterations, converged };
|
|
324
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AdjacencyView,
|
|
3
|
+
type GraphSnapshot,
|
|
4
|
+
INVALID_INDEX,
|
|
5
|
+
makeMask,
|
|
6
|
+
maskTest,
|
|
7
|
+
type NodeMask,
|
|
8
|
+
type U32,
|
|
9
|
+
} from "@graphty/graph-format";
|
|
10
|
+
|
|
11
|
+
import { isBipartite } from "./bipartite.js";
|
|
12
|
+
|
|
13
|
+
/** Options of {@link maximumBipartiteMatching} and {@link greedyBipartiteMatching}. @public */
|
|
14
|
+
export interface BipartiteMatchingOptions {
|
|
15
|
+
/**
|
|
16
|
+
* The left side. Given together with `right`, the two sides are used as they are and the graph
|
|
17
|
+
* is not tested for bipartiteness; a node on neither side is never matched. When either is
|
|
18
|
+
* missing both come from `isBipartite`: its first side is the left one.
|
|
19
|
+
*/
|
|
20
|
+
readonly left?: NodeMask | undefined;
|
|
21
|
+
/** The right side; see `left`. */
|
|
22
|
+
readonly right?: NodeMask | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Which arcs of a directed snapshot join a left node to a right one. "both" (default) ignores
|
|
25
|
+
* direction, as a matching does. "out" follows out-arcs of left nodes only, as the legacy
|
|
26
|
+
* functions do, and is also passed to `isBipartite` when the sides are inferred. Ignored on an
|
|
27
|
+
* undirected snapshot.
|
|
28
|
+
*/
|
|
29
|
+
readonly arcs?: "both" | "out" | undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Result of the index-based bipartite matchings. @public */
|
|
33
|
+
export interface BipartiteMatchingResult {
|
|
34
|
+
/**
|
|
35
|
+
* The right partner of every matched left node; INVALID_INDEX for an unmatched left node and
|
|
36
|
+
* for every node that is not on the left side.
|
|
37
|
+
*/
|
|
38
|
+
readonly matching: U32;
|
|
39
|
+
/** The number of matched pairs. */
|
|
40
|
+
readonly size: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Sides {
|
|
44
|
+
readonly left: NodeMask;
|
|
45
|
+
readonly right: NodeMask;
|
|
46
|
+
readonly views: readonly AdjacencyView[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveSides(s: GraphSnapshot, options: BipartiteMatchingOptions): Sides {
|
|
50
|
+
const views: AdjacencyView[] = s.directed && options.arcs !== "out" ? [s, s.reverse()] : [s];
|
|
51
|
+
if (options.left !== undefined && options.right !== undefined) {
|
|
52
|
+
return { left: options.left, right: options.right, views };
|
|
53
|
+
}
|
|
54
|
+
const { sides } = isBipartite(s, { arcs: options.arcs });
|
|
55
|
+
if (sides === null) {
|
|
56
|
+
throw new Error("Graph is not bipartite");
|
|
57
|
+
}
|
|
58
|
+
const left = makeMask(s.nodeCount);
|
|
59
|
+
for (let w = 0; w < left.length; w++) {
|
|
60
|
+
left[w] = ~sides[w];
|
|
61
|
+
}
|
|
62
|
+
// Clear the padding bits past nodeCount so the mask stays well formed.
|
|
63
|
+
const tail = s.nodeCount % 32;
|
|
64
|
+
if (tail !== 0) {
|
|
65
|
+
left[left.length - 1] &= (1 << tail) - 1;
|
|
66
|
+
}
|
|
67
|
+
return { left, right: sides, views };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Maximum matching of a bipartite graph by augmenting paths (Kuhn's algorithm, O(V E)): every left
|
|
72
|
+
* node in index order searches depth-first, iteratively, for an augmenting path. Parallel arcs and
|
|
73
|
+
* arc direction (by default) make no difference to the matching size.
|
|
74
|
+
* @param s - The snapshot
|
|
75
|
+
* @param options - The two sides, and which arcs to follow on a directed snapshot
|
|
76
|
+
* @returns The partner of every matched left node and the number of pairs
|
|
77
|
+
* @throws Error when the sides are inferred and the graph is not bipartite
|
|
78
|
+
* @public
|
|
79
|
+
*/
|
|
80
|
+
export function maximumBipartiteMatching(
|
|
81
|
+
s: GraphSnapshot,
|
|
82
|
+
options: BipartiteMatchingOptions = {},
|
|
83
|
+
): BipartiteMatchingResult {
|
|
84
|
+
// ponytail: Kuhn is O(V E) like the legacy function; Hopcroft-Karp (O(sqrt(V) E)) if a caller
|
|
85
|
+
// needs large graphs with long augmenting paths.
|
|
86
|
+
const { left, right, views } = resolveSides(s, options);
|
|
87
|
+
const { nodeCount } = s;
|
|
88
|
+
const matching = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
89
|
+
const mateOfRight = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
90
|
+
const seen = new Uint32Array(nodeCount);
|
|
91
|
+
// The DFS stack of left nodes; view and arc cursors per stacked node; the right node taken to
|
|
92
|
+
// descend from each stacked node.
|
|
93
|
+
const stack = new Uint32Array(nodeCount);
|
|
94
|
+
const viewAt = new Uint8Array(nodeCount);
|
|
95
|
+
const arcAt = new Uint32Array(nodeCount);
|
|
96
|
+
const via = new Uint32Array(nodeCount);
|
|
97
|
+
let size = 0;
|
|
98
|
+
for (let root = 0; root < nodeCount; root++) {
|
|
99
|
+
if (!maskTest(left, root)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
const stamp = root + 1;
|
|
103
|
+
let top = 0;
|
|
104
|
+
stack[top++] = root;
|
|
105
|
+
viewAt[root] = 0;
|
|
106
|
+
arcAt[root] = 0;
|
|
107
|
+
let free = INVALID_INDEX;
|
|
108
|
+
search: while (top > 0) {
|
|
109
|
+
const x = stack[top - 1];
|
|
110
|
+
for (; viewAt[x] < views.length; viewAt[x]++) {
|
|
111
|
+
const { rowPtr, colIdx } = views[viewAt[x]];
|
|
112
|
+
const end = rowPtr[x + 1];
|
|
113
|
+
// A cursor of 0 means "not started in this view".
|
|
114
|
+
if (arcAt[x] < rowPtr[x]) {
|
|
115
|
+
arcAt[x] = rowPtr[x];
|
|
116
|
+
}
|
|
117
|
+
while (arcAt[x] < end) {
|
|
118
|
+
const v = colIdx[arcAt[x]++];
|
|
119
|
+
if (seen[v] === stamp || !maskTest(right, v)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
seen[v] = stamp;
|
|
123
|
+
const next = mateOfRight[v];
|
|
124
|
+
if (next === INVALID_INDEX) {
|
|
125
|
+
free = v;
|
|
126
|
+
break search;
|
|
127
|
+
}
|
|
128
|
+
via[x] = v;
|
|
129
|
+
stack[top++] = next;
|
|
130
|
+
viewAt[next] = 0;
|
|
131
|
+
arcAt[next] = 0;
|
|
132
|
+
continue search;
|
|
133
|
+
}
|
|
134
|
+
arcAt[x] = 0;
|
|
135
|
+
}
|
|
136
|
+
top--;
|
|
137
|
+
}
|
|
138
|
+
if (free === INVALID_INDEX) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// Flip the path: the top node takes the free right node, every node below takes the right
|
|
142
|
+
// node it descended through.
|
|
143
|
+
for (let k = top - 1; k >= 0; k--) {
|
|
144
|
+
const x = stack[k];
|
|
145
|
+
const v = k === top - 1 ? free : via[x];
|
|
146
|
+
matching[x] = v;
|
|
147
|
+
mateOfRight[v] = x;
|
|
148
|
+
}
|
|
149
|
+
size++;
|
|
150
|
+
}
|
|
151
|
+
return { matching, size };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Greedy maximal bipartite matching: every left node in index order takes its first unmatched
|
|
156
|
+
* right neighbour in row order (the lowest index; out-arcs before in-arcs). The size depends on
|
|
157
|
+
* those orders, so it can differ from the legacy function, which visits left nodes in its
|
|
158
|
+
* colouring BFS order and neighbours in insertion order; it is at least half the maximum.
|
|
159
|
+
* @param s - The snapshot
|
|
160
|
+
* @param options - The two sides, and which arcs to follow on a directed snapshot
|
|
161
|
+
* @returns The partner of every matched left node and the number of pairs
|
|
162
|
+
* @throws Error when the sides are inferred and the graph is not bipartite
|
|
163
|
+
* @public
|
|
164
|
+
*/
|
|
165
|
+
export function greedyBipartiteMatching(
|
|
166
|
+
s: GraphSnapshot,
|
|
167
|
+
options: BipartiteMatchingOptions = {},
|
|
168
|
+
): BipartiteMatchingResult {
|
|
169
|
+
const { left, right, views } = resolveSides(s, options);
|
|
170
|
+
const { nodeCount } = s;
|
|
171
|
+
const matching = new Uint32Array(nodeCount).fill(INVALID_INDEX);
|
|
172
|
+
const taken = new Uint8Array(nodeCount);
|
|
173
|
+
let size = 0;
|
|
174
|
+
for (let u = 0; u < nodeCount; u++) {
|
|
175
|
+
if (!maskTest(left, u)) {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
pick: for (const { rowPtr, colIdx } of views) {
|
|
179
|
+
const end = rowPtr[u + 1];
|
|
180
|
+
for (let a = rowPtr[u]; a < end; a++) {
|
|
181
|
+
const v = colIdx[a];
|
|
182
|
+
if (taken[v] === 0 && maskTest(right, v)) {
|
|
183
|
+
taken[v] = 1;
|
|
184
|
+
matching[u] = v;
|
|
185
|
+
size++;
|
|
186
|
+
break pick;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { matching, size };
|
|
192
|
+
}
|