@graphty/algorithms 1.2.0 → 1.4.2
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 +375 -276
- package/dist/algorithms.d.ts +42 -2
- package/dist/algorithms.js +11402 -2
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +14 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +144 -169
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +35 -1
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +70 -149
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.d.ts +7 -0
- package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +12 -9
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +57 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +304 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +106 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js +320 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
- package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -3
- package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/eigenvector.js +11 -4
- package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
- package/dist/src/algorithms/centrality/hits.d.ts +10 -3
- package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/hits.js +11 -4
- package/dist/src/algorithms/centrality/hits.js.map +1 -1
- package/dist/src/algorithms/centrality/index.d.ts +3 -1
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +2 -1
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/katz.d.ts +10 -3
- package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/katz.js +13 -8
- package/dist/src/algorithms/centrality/katz.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +31 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +61 -1
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts +2 -3
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +67 -57
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/index.d.ts +2 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -1
- package/dist/src/algorithms/community/index.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +11 -4
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +60 -38
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +3 -3
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +70 -44
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +149 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain-optimized.js +338 -0
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts +2 -3
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +35 -81
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +55 -0
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
- package/dist/src/algorithms/community/modularity-utils.js +108 -0
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
- package/dist/src/algorithms/components/connected.d.ts +23 -0
- package/dist/src/algorithms/components/connected.d.ts.map +1 -1
- package/dist/src/algorithms/components/connected.js +29 -3
- package/dist/src/algorithms/components/connected.js.map +1 -1
- package/dist/src/algorithms/matching/bipartite.d.ts +10 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +18 -40
- package/dist/src/algorithms/matching/bipartite.js.map +1 -1
- package/dist/src/algorithms/matching/index.d.ts.map +1 -1
- package/dist/src/algorithms/matching/index.js.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.d.ts +8 -0
- package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -1
- package/dist/src/algorithms/matching/isomorphism.js +36 -2
- package/dist/src/algorithms/matching/isomorphism.js.map +1 -1
- package/dist/src/algorithms/mst/index.d.ts.map +1 -1
- package/dist/src/algorithms/mst/index.js.map +1 -1
- package/dist/src/algorithms/mst/kruskal.d.ts +14 -0
- package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
- package/dist/src/algorithms/mst/kruskal.js +17 -3
- package/dist/src/algorithms/mst/kruskal.js.map +1 -1
- package/dist/src/algorithms/mst/prim.d.ts +9 -0
- package/dist/src/algorithms/mst/prim.d.ts.map +1 -1
- package/dist/src/algorithms/mst/prim.js +9 -0
- package/dist/src/algorithms/mst/prim.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +11 -12
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +44 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +195 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +20 -2
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +30 -14
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/floyd-warshall.js +17 -0
- package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
- package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/index.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +42 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-unified.js +370 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +77 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.js +402 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +5 -182
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/algorithms/traversal/dfs.d.ts +10 -0
- package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/dfs.js +48 -0
- package/dist/src/algorithms/traversal/dfs.js.map +1 -1
- package/dist/src/algorithms/traversal/index.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/index.js.map +1 -1
- package/dist/src/benchmark-all-algorithms.d.ts +25 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
- package/dist/src/benchmark-all-algorithms.js +491 -0
- package/dist/src/benchmark-all-algorithms.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +23 -15
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +226 -224
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts +4 -2
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js +2 -2
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +17 -19
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +68 -30
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/mcl.d.ts +6 -0
- package/dist/src/clustering/mcl.d.ts.map +1 -1
- package/dist/src/clustering/mcl.js +34 -3
- package/dist/src/clustering/mcl.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +11 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +99 -47
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.d.ts +45 -0
- package/dist/src/core/graph.d.ts.map +1 -1
- package/dist/src/core/graph.js +49 -5
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/data-structures/index.d.ts.map +1 -1
- package/dist/src/data-structures/index.js.map +1 -1
- package/dist/src/data-structures/priority-queue.d.ts +18 -1
- package/dist/src/data-structures/priority-queue.d.ts.map +1 -1
- package/dist/src/data-structures/priority-queue.js +26 -5
- package/dist/src/data-structures/priority-queue.js.map +1 -1
- package/dist/src/data-structures/union-find.d.ts +21 -0
- package/dist/src/data-structures/union-find.d.ts.map +1 -1
- package/dist/src/data-structures/union-find.js +21 -0
- package/dist/src/data-structures/union-find.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +20 -12
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +170 -217
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +6 -8
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +34 -12
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +3 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts +26 -0
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +47 -30
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts +21 -0
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +31 -14
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/link-prediction/index.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +193 -0
- package/dist/src/optimized/bit-packed.d.ts.map +1 -0
- package/dist/src/optimized/bit-packed.js +342 -0
- package/dist/src/optimized/bit-packed.js.map +1 -0
- package/dist/src/optimized/csr-graph.d.ts +149 -0
- package/dist/src/optimized/csr-graph.d.ts.map +1 -0
- package/dist/src/optimized/csr-graph.js +404 -0
- package/dist/src/optimized/csr-graph.js.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts +95 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.js +242 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
- package/dist/src/optimized/graph-adapter.d.ts +111 -0
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
- package/dist/src/optimized/graph-adapter.js +194 -0
- package/dist/src/optimized/graph-adapter.js.map +1 -0
- package/dist/src/optimized/index.d.ts +14 -0
- package/dist/src/optimized/index.d.ts.map +1 -0
- package/dist/src/optimized/index.js +30 -0
- package/dist/src/optimized/index.js.map +1 -0
- package/dist/src/pathfinding/astar.d.ts +25 -9
- package/dist/src/pathfinding/astar.d.ts.map +1 -1
- package/dist/src/pathfinding/astar.js +25 -10
- package/dist/src/pathfinding/astar.js.map +1 -1
- package/dist/src/pathfinding/utils.d.ts +15 -6
- package/dist/src/pathfinding/utils.d.ts.map +1 -1
- package/dist/src/pathfinding/utils.js +19 -7
- package/dist/src/pathfinding/utils.js.map +1 -1
- package/dist/src/research/grsbm.d.ts +0 -1
- package/dist/src/research/grsbm.d.ts.map +1 -1
- package/dist/src/research/grsbm.js +28 -23
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/index.d.ts.map +1 -1
- package/dist/src/research/index.js.map +1 -1
- package/dist/src/research/sync.d.ts +0 -1
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +31 -36
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +2 -1
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +39 -7
- package/dist/src/research/terahac.js.map +1 -1
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/algorithm-utilities.d.ts +2 -0
- package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
- package/dist/src/utils/algorithm-utilities.js +2 -0
- package/dist/src/utils/algorithm-utilities.js.map +1 -0
- package/dist/src/utils/graph-converters.d.ts +110 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -0
- package/dist/src/utils/graph-converters.js +227 -0
- package/dist/src/utils/graph-converters.js.map +1 -0
- package/dist/src/utils/graph-utilities.d.ts +62 -0
- package/dist/src/utils/graph-utilities.d.ts.map +1 -0
- package/dist/src/utils/graph-utilities.js +148 -0
- package/dist/src/utils/graph-utilities.js.map +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +5 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/math-utilities.d.ts +63 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -0
- package/dist/src/utils/math-utilities.js +132 -0
- package/dist/src/utils/math-utilities.js.map +1 -0
- package/dist/src/utils/matrix-utilities.d.ts +2 -0
- package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
- package/dist/src/utils/matrix-utilities.js +2 -0
- package/dist/src/utils/matrix-utilities.js.map +1 -0
- package/dist/src/utils/optimization-helpers.d.ts +42 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
- package/dist/src/utils/optimization-helpers.js +59 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -0
- package/dist/src/utils/priorityQueue.d.ts +24 -0
- package/dist/src/utils/priorityQueue.d.ts.map +1 -1
- package/dist/src/utils/priorityQueue.js +34 -4
- package/dist/src/utils/priorityQueue.js.map +1 -1
- package/package.json +58 -39
- package/src/algorithms/centrality/betweenness.ts +185 -200
- package/src/algorithms/centrality/closeness.ts +93 -194
- package/src/algorithms/centrality/degree.ts +18 -22
- package/src/algorithms/centrality/delta-pagerank-simple.ts +374 -0
- package/src/algorithms/centrality/delta-pagerank.ts +458 -0
- package/src/algorithms/centrality/eigenvector.ts +14 -15
- package/src/algorithms/centrality/hits.ts +17 -17
- package/src/algorithms/centrality/index.ts +20 -13
- package/src/algorithms/centrality/katz.ts +17 -25
- package/src/algorithms/centrality/pagerank.ts +91 -16
- package/src/algorithms/community/girvan-newman.ts +90 -86
- package/src/algorithms/community/index.ts +6 -5
- package/src/algorithms/community/label-propagation.ts +69 -48
- package/src/algorithms/community/leiden.ts +88 -65
- package/src/algorithms/community/louvain-optimized.ts +474 -0
- package/src/algorithms/community/louvain.ts +52 -122
- package/src/algorithms/community/modularity-utils.ts +134 -0
- package/src/algorithms/components/connected.ts +40 -17
- package/src/algorithms/matching/bipartite.ts +24 -53
- package/src/algorithms/matching/index.ts +4 -4
- package/src/algorithms/matching/isomorphism.ts +53 -23
- package/src/algorithms/mst/index.ts +3 -4
- package/src/algorithms/mst/kruskal.ts +21 -6
- package/src/algorithms/mst/prim.ts +13 -4
- package/src/algorithms/shortest-path/bellman-ford.ts +16 -28
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
- package/src/algorithms/shortest-path/dijkstra.ts +38 -30
- package/src/algorithms/shortest-path/floyd-warshall.ts +21 -4
- package/src/algorithms/shortest-path/index.ts +5 -5
- package/src/algorithms/traversal/bfs-unified.ts +437 -0
- package/src/algorithms/traversal/bfs-variants.ts +520 -0
- package/src/algorithms/traversal/bfs.ts +5 -229
- package/src/algorithms/traversal/dfs.ts +61 -42
- package/src/algorithms/traversal/index.ts +3 -3
- package/src/benchmark-all-algorithms.ts +580 -0
- package/src/clustering/hierarchical.ts +268 -272
- package/src/clustering/index.ts +8 -6
- package/src/clustering/k-core.ts +93 -61
- package/src/clustering/mcl.ts +53 -23
- package/src/clustering/spectral.ts +130 -72
- package/src/core/graph.ts +58 -18
- package/src/data-structures/index.ts +2 -2
- package/src/data-structures/priority-queue.ts +36 -11
- package/src/data-structures/union-find.ts +22 -1
- package/src/flow/ford-fulkerson.ts +221 -262
- package/src/flow/min-cut.ts +59 -51
- package/src/index.ts +9 -2
- package/src/link-prediction/adamic-adar.ts +75 -74
- package/src/link-prediction/common-neighbors.ts +49 -49
- package/src/link-prediction/index.ts +1 -1
- package/src/optimized/bit-packed.ts +390 -0
- package/src/optimized/csr-graph.ts +518 -0
- package/src/optimized/direction-optimized-bfs.ts +310 -0
- package/src/optimized/graph-adapter.ts +248 -0
- package/src/optimized/index.ts +34 -0
- package/src/pathfinding/astar.ts +40 -25
- package/src/pathfinding/utils.ts +20 -13
- package/src/research/grsbm.ts +36 -39
- package/src/research/index.ts +4 -4
- package/src/research/sync.ts +41 -54
- package/src/research/terahac.ts +63 -21
- package/src/types/index.ts +8 -0
- package/src/utils/algorithm-utilities.ts +2 -0
- package/src/utils/graph-converters.ts +269 -0
- package/src/utils/graph-utilities.ts +173 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/math-utilities.ts +149 -0
- package/src/utils/matrix-utilities.ts +2 -0
- package/src/utils/optimization-helpers.ts +87 -0
- package/src/utils/priorityQueue.ts +40 -6
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {Graph} from "../../core/graph.js";
|
|
2
|
-
import type {CommunityResult, LouvainOptions, NodeId} from "../../types/index.js";
|
|
1
|
+
import { Graph } from "../../core/graph.js";
|
|
2
|
+
import type { CommunityResult, LouvainOptions, NodeId } from "../../types/index.js";
|
|
3
|
+
import { louvainOptimized } from "./louvain-optimized.js";
|
|
4
|
+
import { calculateModularity, getNeighborCommunities, getNodeDegree, getTotalEdgeWeight } from "./modularity-utils.js";
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Louvain community detection algorithm
|
|
@@ -9,21 +11,34 @@ import type {CommunityResult, LouvainOptions, NodeId} from "../../types/index.js
|
|
|
9
11
|
*
|
|
10
12
|
* References:
|
|
11
13
|
* - Blondel, V. D., Guillaume, J. L., Lambiotte, R., & Lefebvre, E. (2008).
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Fast unfolding of communities in large networks.
|
|
15
|
+
* Journal of statistical mechanics: theory and experiment, 2008(10), P10008.
|
|
15
16
|
* @param graph - The input graph
|
|
16
17
|
* @param options - Algorithm options
|
|
17
18
|
* @returns Community detection result with communities, modularity, and iterations
|
|
18
19
|
*/
|
|
19
|
-
export function louvain(
|
|
20
|
-
graph: Graph,
|
|
21
|
-
options: LouvainOptions = {},
|
|
22
|
-
): CommunityResult {
|
|
20
|
+
export function louvain(graph: Graph, options: LouvainOptions = {}): CommunityResult {
|
|
23
21
|
const resolution = options.resolution ?? 1.0;
|
|
24
22
|
const maxIterations = options.maxIterations ?? 100;
|
|
25
23
|
const tolerance = options.tolerance ?? 1e-6;
|
|
26
24
|
|
|
25
|
+
// Use optimized version by default for larger graphs
|
|
26
|
+
// Only disable if explicitly requested or graph is very small
|
|
27
|
+
const shouldUseOptimized = options.useOptimized !== false && graph.nodeCount > 50;
|
|
28
|
+
|
|
29
|
+
if (shouldUseOptimized) {
|
|
30
|
+
return louvainOptimized(graph, {
|
|
31
|
+
resolution,
|
|
32
|
+
maxIterations,
|
|
33
|
+
tolerance,
|
|
34
|
+
// Enable all optimizations by default
|
|
35
|
+
pruneLeaves: true,
|
|
36
|
+
importanceOrdering: true,
|
|
37
|
+
thresholdCycling: true,
|
|
38
|
+
pruningThreshold: 0.01,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
27
42
|
// Initialize: each node in its own community
|
|
28
43
|
const communities = initializeCommunities(graph);
|
|
29
44
|
let modularity = calculateModularity(graph, communities, resolution);
|
|
@@ -38,7 +53,7 @@ export function louvain(
|
|
|
38
53
|
const newModularity = calculateModularity(graph, communities, resolution);
|
|
39
54
|
|
|
40
55
|
// Check for convergence
|
|
41
|
-
if (
|
|
56
|
+
if (newModularity - modularity < tolerance) {
|
|
42
57
|
break;
|
|
43
58
|
}
|
|
44
59
|
|
|
@@ -56,6 +71,8 @@ export function louvain(
|
|
|
56
71
|
|
|
57
72
|
/**
|
|
58
73
|
* Initialize communities: each node in its own community
|
|
74
|
+
* @param graph - The input graph
|
|
75
|
+
* @returns Map of node IDs to their initial community IDs
|
|
59
76
|
*/
|
|
60
77
|
function initializeCommunities(graph: Graph): Map<NodeId, number> {
|
|
61
78
|
const communities = new Map<NodeId, number>();
|
|
@@ -72,12 +89,12 @@ function initializeCommunities(graph: Graph): Map<NodeId, number> {
|
|
|
72
89
|
* Phase 1 of Louvain algorithm: Local optimization
|
|
73
90
|
* For each node, try moving to neighboring communities and keep the move
|
|
74
91
|
* that provides the best modularity gain.
|
|
92
|
+
* @param graph - The input graph
|
|
93
|
+
* @param communities - Map of node IDs to community IDs
|
|
94
|
+
* @param resolution - Resolution parameter for modularity calculation
|
|
95
|
+
* @returns True if any improvement was made, false otherwise
|
|
75
96
|
*/
|
|
76
|
-
function louvainPhase1(
|
|
77
|
-
graph: Graph,
|
|
78
|
-
communities: Map<NodeId, number>,
|
|
79
|
-
resolution: number,
|
|
80
|
-
): boolean {
|
|
97
|
+
function louvainPhase1(graph: Graph, communities: Map<NodeId, number>, resolution: number): boolean {
|
|
81
98
|
let globalImprovement = false;
|
|
82
99
|
let localImprovement = true;
|
|
83
100
|
|
|
@@ -94,7 +111,11 @@ function louvainPhase1(
|
|
|
94
111
|
|
|
95
112
|
// Calculate current modularity contribution
|
|
96
113
|
const currentModularity = nodeModularityContribution(
|
|
97
|
-
graph,
|
|
114
|
+
graph,
|
|
115
|
+
nodeId,
|
|
116
|
+
currentCommunity,
|
|
117
|
+
communities,
|
|
118
|
+
resolution,
|
|
98
119
|
);
|
|
99
120
|
|
|
100
121
|
let bestCommunity = currentCommunity;
|
|
@@ -109,7 +130,11 @@ function louvainPhase1(
|
|
|
109
130
|
}
|
|
110
131
|
|
|
111
132
|
const newModularity = nodeModularityContribution(
|
|
112
|
-
graph,
|
|
133
|
+
graph,
|
|
134
|
+
nodeId,
|
|
135
|
+
neighborCommunity,
|
|
136
|
+
communities,
|
|
137
|
+
resolution,
|
|
113
138
|
);
|
|
114
139
|
|
|
115
140
|
if (newModularity > bestModularity) {
|
|
@@ -132,6 +157,12 @@ function louvainPhase1(
|
|
|
132
157
|
|
|
133
158
|
/**
|
|
134
159
|
* Calculate modularity contribution of a node to a specific community
|
|
160
|
+
* @param graph - The input graph
|
|
161
|
+
* @param nodeId - The node ID to evaluate
|
|
162
|
+
* @param community - The community to measure contribution to
|
|
163
|
+
* @param communities - Map of all node IDs to community IDs
|
|
164
|
+
* @param resolution - Resolution parameter for modularity calculation
|
|
165
|
+
* @returns The modularity contribution of the node to the specified community
|
|
135
166
|
*/
|
|
136
167
|
function nodeModularityContribution(
|
|
137
168
|
graph: Graph,
|
|
@@ -168,90 +199,16 @@ function nodeModularityContribution(
|
|
|
168
199
|
}
|
|
169
200
|
|
|
170
201
|
// Modularity formula: Q = (1/2m) * Σ[A_ij - (k_i * k_j)/(2m)] * δ(c_i, c_j)
|
|
171
|
-
const modularityIncrease =
|
|
202
|
+
const modularityIncrease =
|
|
203
|
+
(internalLinks - (resolution * nodeDegree * communityDegree) / (2 * totalEdgeWeight)) / totalEdgeWeight;
|
|
172
204
|
|
|
173
205
|
return modularityIncrease;
|
|
174
206
|
}
|
|
175
207
|
|
|
176
|
-
/**
|
|
177
|
-
* Get communities of neighboring nodes
|
|
178
|
-
*/
|
|
179
|
-
function getNeighborCommunities(
|
|
180
|
-
graph: Graph,
|
|
181
|
-
nodeId: NodeId,
|
|
182
|
-
communities: Map<NodeId, number>,
|
|
183
|
-
): Set<number> {
|
|
184
|
-
const neighborCommunities = new Set<number>();
|
|
185
|
-
|
|
186
|
-
for (const neighbor of graph.neighbors(nodeId)) {
|
|
187
|
-
const community = communities.get(neighbor);
|
|
188
|
-
if (community !== undefined) {
|
|
189
|
-
neighborCommunities.add(community);
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return neighborCommunities;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* Calculate total modularity of the current community assignment
|
|
198
|
-
*/
|
|
199
|
-
function calculateModularity(
|
|
200
|
-
graph: Graph,
|
|
201
|
-
communities: Map<NodeId, number>,
|
|
202
|
-
resolution: number,
|
|
203
|
-
): number {
|
|
204
|
-
const totalEdgeWeight = getTotalEdgeWeight(graph);
|
|
205
|
-
if (totalEdgeWeight === 0) {
|
|
206
|
-
return 0;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
let modularity = 0;
|
|
210
|
-
|
|
211
|
-
// For undirected graphs, we need to be careful not to double-count edges
|
|
212
|
-
const countedEdges = new Set<string>();
|
|
213
|
-
|
|
214
|
-
// Calculate modularity: Q = (1/2m) * Σ[A_ij - γ(k_i * k_j)/(2m)] * δ(c_i, c_j)
|
|
215
|
-
for (const nodeI of graph.nodes()) {
|
|
216
|
-
for (const nodeJ of graph.nodes()) {
|
|
217
|
-
// Skip if already counted this pair in undirected graph
|
|
218
|
-
if (!graph.isDirected) {
|
|
219
|
-
const nodeIStr = String(nodeI.id);
|
|
220
|
-
const nodeJStr = String(nodeJ.id);
|
|
221
|
-
const edgeKey = nodeIStr <= nodeJStr ? `${nodeIStr}-${nodeJStr}` : `${nodeJStr}-${nodeIStr}`;
|
|
222
|
-
if (countedEdges.has(edgeKey)) {
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
countedEdges.add(edgeKey);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (communities.get(nodeI.id) === communities.get(nodeJ.id)) {
|
|
230
|
-
const edge = graph.getEdge(nodeI.id, nodeJ.id);
|
|
231
|
-
const reverseEdge = !graph.isDirected ? graph.getEdge(nodeJ.id, nodeI.id) : null;
|
|
232
|
-
|
|
233
|
-
let edgeWeight = 0;
|
|
234
|
-
if (edge) {
|
|
235
|
-
edgeWeight += edge.weight ?? 1;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
if (reverseEdge && nodeI.id !== nodeJ.id) {
|
|
239
|
-
edgeWeight += reverseEdge.weight ?? 1;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
const degreeI = getNodeDegree(graph, nodeI.id);
|
|
243
|
-
const degreeJ = getNodeDegree(graph, nodeJ.id);
|
|
244
|
-
|
|
245
|
-
modularity += edgeWeight - ((resolution * degreeI * degreeJ) / (2 * totalEdgeWeight));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
return modularity / (2 * totalEdgeWeight);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
208
|
/**
|
|
254
209
|
* Extract final community structure
|
|
210
|
+
* @param communities - Map of node IDs to community IDs
|
|
211
|
+
* @returns Array of communities (each is an array of node IDs)
|
|
255
212
|
*/
|
|
256
213
|
function extractCommunities(communities: Map<NodeId, number>): NodeId[][] {
|
|
257
214
|
const communityMap = new Map<number, NodeId[]>();
|
|
@@ -269,30 +226,3 @@ function extractCommunities(communities: Map<NodeId, number>): NodeId[][] {
|
|
|
269
226
|
|
|
270
227
|
return Array.from(communityMap.values());
|
|
271
228
|
}
|
|
272
|
-
|
|
273
|
-
/**
|
|
274
|
-
* Calculate total edge weight in the graph
|
|
275
|
-
*/
|
|
276
|
-
function getTotalEdgeWeight(graph: Graph): number {
|
|
277
|
-
let totalWeight = 0;
|
|
278
|
-
|
|
279
|
-
for (const edge of graph.edges()) {
|
|
280
|
-
totalWeight += edge.weight ?? 1;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return totalWeight;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Get the total degree (sum of edge weights) for a node
|
|
288
|
-
*/
|
|
289
|
-
function getNodeDegree(graph: Graph, nodeId: NodeId): number {
|
|
290
|
-
let degree = 0;
|
|
291
|
-
|
|
292
|
-
for (const neighbor of graph.neighbors(nodeId)) {
|
|
293
|
-
const edge = graph.getEdge(nodeId, neighbor);
|
|
294
|
-
degree += edge?.weight ?? 1;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return degree;
|
|
298
|
-
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared modularity calculation utilities for community detection algorithms
|
|
3
|
+
*
|
|
4
|
+
* Used by: Louvain, Leiden, Girvan-Newman
|
|
5
|
+
*/
|
|
6
|
+
import type { Graph } from "../../core/graph.js";
|
|
7
|
+
import type { NodeId } from "../../types/index.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Calculate total edge weight in the graph
|
|
11
|
+
*
|
|
12
|
+
* For undirected graphs, each edge is counted once.
|
|
13
|
+
* For directed graphs, each directed edge is counted once.
|
|
14
|
+
* @param graph - The input graph
|
|
15
|
+
* @returns Total sum of edge weights (default weight is 1)
|
|
16
|
+
*/
|
|
17
|
+
export function getTotalEdgeWeight(graph: Graph): number {
|
|
18
|
+
let totalWeight = 0;
|
|
19
|
+
|
|
20
|
+
for (const edge of graph.edges()) {
|
|
21
|
+
totalWeight += edge.weight ?? 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return totalWeight;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get the total degree (sum of edge weights) for a node
|
|
29
|
+
*
|
|
30
|
+
* Computes the weighted degree by summing all edge weights incident to the node.
|
|
31
|
+
* @param graph - The input graph
|
|
32
|
+
* @param nodeId - The node ID to compute degree for
|
|
33
|
+
* @returns The weighted degree of the node
|
|
34
|
+
*/
|
|
35
|
+
export function getNodeDegree(graph: Graph, nodeId: NodeId): number {
|
|
36
|
+
let degree = 0;
|
|
37
|
+
|
|
38
|
+
for (const neighbor of graph.neighbors(nodeId)) {
|
|
39
|
+
const edge = graph.getEdge(nodeId, neighbor);
|
|
40
|
+
degree += edge?.weight ?? 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return degree;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get communities of neighboring nodes
|
|
48
|
+
*
|
|
49
|
+
* Returns the set of community IDs that neighbors of the given node belong to.
|
|
50
|
+
* @param graph - The input graph
|
|
51
|
+
* @param nodeId - The node to get neighbor communities for
|
|
52
|
+
* @param communities - Map from node IDs to community IDs
|
|
53
|
+
* @returns Set of community IDs that neighbors belong to
|
|
54
|
+
*/
|
|
55
|
+
export function getNeighborCommunities(graph: Graph, nodeId: NodeId, communities: Map<NodeId, number>): Set<number> {
|
|
56
|
+
const neighborCommunities = new Set<number>();
|
|
57
|
+
|
|
58
|
+
for (const neighbor of graph.neighbors(nodeId)) {
|
|
59
|
+
const community = communities.get(neighbor);
|
|
60
|
+
if (community !== undefined) {
|
|
61
|
+
neighborCommunities.add(community);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return neighborCommunities;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Calculate modularity of a partition
|
|
70
|
+
*
|
|
71
|
+
* Modularity measures the quality of a community partition. It compares the
|
|
72
|
+
* number of edges within communities to what would be expected in a random graph.
|
|
73
|
+
*
|
|
74
|
+
* Formula: Q = (1/2m) * Σ[A_ij - γ(k_i * k_j)/(2m)] * δ(c_i, c_j)
|
|
75
|
+
* where:
|
|
76
|
+
* - m = total edge weight
|
|
77
|
+
* - A_ij = adjacency matrix entry (edge weight between i and j)
|
|
78
|
+
* - k_i, k_j = degrees of nodes i and j
|
|
79
|
+
* - γ = resolution parameter (higher values favor smaller communities)
|
|
80
|
+
* - δ = Kronecker delta (1 if same community, 0 otherwise)
|
|
81
|
+
* @param graph - The input graph
|
|
82
|
+
* @param communities - Map from node IDs to community IDs
|
|
83
|
+
* @param resolution - Resolution parameter (default: 1.0)
|
|
84
|
+
* @returns Modularity score (range typically -0.5 to 1.0)
|
|
85
|
+
*/
|
|
86
|
+
export function calculateModularity(graph: Graph, communities: Map<NodeId, number>, resolution = 1.0): number {
|
|
87
|
+
const totalEdgeWeight = getTotalEdgeWeight(graph);
|
|
88
|
+
if (totalEdgeWeight === 0) {
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let modularity = 0;
|
|
93
|
+
|
|
94
|
+
// For undirected graphs, we need to be careful not to double-count edges
|
|
95
|
+
const countedEdges = new Set<string>();
|
|
96
|
+
|
|
97
|
+
// Calculate modularity: Q = (1/2m) * Σ[A_ij - γ(k_i * k_j)/(2m)] * δ(c_i, c_j)
|
|
98
|
+
for (const nodeI of graph.nodes()) {
|
|
99
|
+
for (const nodeJ of graph.nodes()) {
|
|
100
|
+
// Skip if already counted this pair in undirected graph
|
|
101
|
+
if (!graph.isDirected) {
|
|
102
|
+
const nodeIStr = String(nodeI.id);
|
|
103
|
+
const nodeJStr = String(nodeJ.id);
|
|
104
|
+
const edgeKey = nodeIStr <= nodeJStr ? `${nodeIStr}-${nodeJStr}` : `${nodeJStr}-${nodeIStr}`;
|
|
105
|
+
if (countedEdges.has(edgeKey)) {
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
countedEdges.add(edgeKey);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (communities.get(nodeI.id) === communities.get(nodeJ.id)) {
|
|
113
|
+
const edge = graph.getEdge(nodeI.id, nodeJ.id);
|
|
114
|
+
const reverseEdge = !graph.isDirected ? graph.getEdge(nodeJ.id, nodeI.id) : null;
|
|
115
|
+
|
|
116
|
+
let edgeWeight = 0;
|
|
117
|
+
if (edge) {
|
|
118
|
+
edgeWeight += edge.weight ?? 1;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (reverseEdge && nodeI.id !== nodeJ.id) {
|
|
122
|
+
edgeWeight += reverseEdge.weight ?? 1;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const degreeI = getNodeDegree(graph, nodeI.id);
|
|
126
|
+
const degreeJ = getNodeDegree(graph, nodeJ.id);
|
|
127
|
+
|
|
128
|
+
modularity += edgeWeight - (resolution * degreeI * degreeJ) / (2 * totalEdgeWeight);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return modularity / (2 * totalEdgeWeight);
|
|
134
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {Graph} from "../../core/graph.js";
|
|
2
|
-
import {UnionFind} from "../../data-structures/union-find.js";
|
|
3
|
-
import type {NodeId} from "../../types/index.js";
|
|
1
|
+
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import { UnionFind } from "../../data-structures/union-find.js";
|
|
3
|
+
import type { NodeId } from "../../types/index.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Connected components algorithms
|
|
@@ -11,10 +11,14 @@ import type {NodeId} from "../../types/index.js";
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Find connected components in an undirected graph using Union-Find
|
|
14
|
+
* @param graph - The input graph (must be undirected)
|
|
15
|
+
* @returns Array of connected components, each containing an array of node IDs
|
|
14
16
|
*/
|
|
15
17
|
export function connectedComponents(graph: Graph): NodeId[][] {
|
|
16
18
|
if (graph.isDirected) {
|
|
17
|
-
throw new Error(
|
|
19
|
+
throw new Error(
|
|
20
|
+
"Connected components algorithm requires an undirected graph. Use stronglyConnectedComponents for directed graphs.",
|
|
21
|
+
);
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
const nodes = Array.from(graph.nodes()).map((node) => node.id);
|
|
@@ -35,6 +39,8 @@ export function connectedComponents(graph: Graph): NodeId[][] {
|
|
|
35
39
|
|
|
36
40
|
/**
|
|
37
41
|
* Find connected components using DFS (alternative implementation)
|
|
42
|
+
* @param graph - The input graph (must be undirected)
|
|
43
|
+
* @returns Array of connected components, each containing an array of node IDs
|
|
38
44
|
*/
|
|
39
45
|
export function connectedComponentsDFS(graph: Graph): NodeId[][] {
|
|
40
46
|
if (graph.isDirected) {
|
|
@@ -57,13 +63,12 @@ export function connectedComponentsDFS(graph: Graph): NodeId[][] {
|
|
|
57
63
|
|
|
58
64
|
/**
|
|
59
65
|
* DFS helper for connected components
|
|
66
|
+
* @param graph - The input graph
|
|
67
|
+
* @param nodeId - The starting node ID
|
|
68
|
+
* @param visited - Set of already visited node IDs
|
|
69
|
+
* @param component - Array to accumulate component members
|
|
60
70
|
*/
|
|
61
|
-
function dfsComponent(
|
|
62
|
-
graph: Graph,
|
|
63
|
-
nodeId: NodeId,
|
|
64
|
-
visited: Set<NodeId>,
|
|
65
|
-
component: NodeId[],
|
|
66
|
-
): void {
|
|
71
|
+
function dfsComponent(graph: Graph, nodeId: NodeId, visited: Set<NodeId>, component: NodeId[]): void {
|
|
67
72
|
visited.add(nodeId);
|
|
68
73
|
component.push(nodeId);
|
|
69
74
|
|
|
@@ -76,6 +81,8 @@ function dfsComponent(
|
|
|
76
81
|
|
|
77
82
|
/**
|
|
78
83
|
* Find the number of connected components
|
|
84
|
+
* @param graph - The input graph (must be undirected)
|
|
85
|
+
* @returns The number of connected components in the graph
|
|
79
86
|
*/
|
|
80
87
|
export function numberOfConnectedComponents(graph: Graph): number {
|
|
81
88
|
return connectedComponents(graph).length;
|
|
@@ -83,6 +90,8 @@ export function numberOfConnectedComponents(graph: Graph): number {
|
|
|
83
90
|
|
|
84
91
|
/**
|
|
85
92
|
* Check if the graph is connected (has exactly one connected component)
|
|
93
|
+
* @param graph - The input graph (must be undirected)
|
|
94
|
+
* @returns True if the graph is connected, false otherwise
|
|
86
95
|
*/
|
|
87
96
|
export function isConnected(graph: Graph): boolean {
|
|
88
97
|
return numberOfConnectedComponents(graph) <= 1;
|
|
@@ -90,6 +99,8 @@ export function isConnected(graph: Graph): boolean {
|
|
|
90
99
|
|
|
91
100
|
/**
|
|
92
101
|
* Find the largest connected component
|
|
102
|
+
* @param graph - The input graph (must be undirected)
|
|
103
|
+
* @returns Array of node IDs in the largest connected component
|
|
93
104
|
*/
|
|
94
105
|
export function largestConnectedComponent(graph: Graph): NodeId[] {
|
|
95
106
|
const components = connectedComponents(graph);
|
|
@@ -98,13 +109,14 @@ export function largestConnectedComponent(graph: Graph): NodeId[] {
|
|
|
98
109
|
return [];
|
|
99
110
|
}
|
|
100
111
|
|
|
101
|
-
return components.reduce((largest, current) =>
|
|
102
|
-
current.length > largest.length ? current : largest,
|
|
103
|
-
);
|
|
112
|
+
return components.reduce((largest, current) => (current.length > largest.length ? current : largest));
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
/**
|
|
107
116
|
* Get the connected component containing a specific node
|
|
117
|
+
* @param graph - The input graph (must be undirected)
|
|
118
|
+
* @param nodeId - The node ID to find the component for
|
|
119
|
+
* @returns Array of node IDs in the same component as the specified node
|
|
108
120
|
*/
|
|
109
121
|
export function getConnectedComponent(graph: Graph, nodeId: NodeId): NodeId[] {
|
|
110
122
|
if (!graph.hasNode(nodeId)) {
|
|
@@ -125,6 +137,8 @@ export function getConnectedComponent(graph: Graph, nodeId: NodeId): NodeId[] {
|
|
|
125
137
|
|
|
126
138
|
/**
|
|
127
139
|
* Find strongly connected components using Tarjan's algorithm
|
|
140
|
+
* @param graph - The input graph (must be directed)
|
|
141
|
+
* @returns Array of strongly connected components, each containing an array of node IDs
|
|
128
142
|
*/
|
|
129
143
|
export function stronglyConnectedComponents(graph: Graph): NodeId[][] {
|
|
130
144
|
if (!graph.isDirected) {
|
|
@@ -198,6 +212,8 @@ export function stronglyConnectedComponents(graph: Graph): NodeId[][] {
|
|
|
198
212
|
|
|
199
213
|
/**
|
|
200
214
|
* Check if a directed graph is strongly connected
|
|
215
|
+
* @param graph - The input graph (must be directed)
|
|
216
|
+
* @returns True if the graph is strongly connected, false otherwise
|
|
201
217
|
*/
|
|
202
218
|
export function isStronglyConnected(graph: Graph): boolean {
|
|
203
219
|
if (!graph.isDirected) {
|
|
@@ -211,10 +227,14 @@ export function isStronglyConnected(graph: Graph): boolean {
|
|
|
211
227
|
/**
|
|
212
228
|
* Find weakly connected components in a directed graph
|
|
213
229
|
* (treat the graph as undirected for connectivity)
|
|
230
|
+
* @param graph - The input graph (must be directed)
|
|
231
|
+
* @returns Array of weakly connected components, each containing an array of node IDs
|
|
214
232
|
*/
|
|
215
233
|
export function weaklyConnectedComponents(graph: Graph): NodeId[][] {
|
|
216
234
|
if (!graph.isDirected) {
|
|
217
|
-
throw new Error(
|
|
235
|
+
throw new Error(
|
|
236
|
+
"Weakly connected components are for directed graphs. Use connectedComponents for undirected graphs.",
|
|
237
|
+
);
|
|
218
238
|
}
|
|
219
239
|
|
|
220
240
|
const nodes = Array.from(graph.nodes()).map((node) => node.id);
|
|
@@ -235,6 +255,8 @@ export function weaklyConnectedComponents(graph: Graph): NodeId[][] {
|
|
|
235
255
|
|
|
236
256
|
/**
|
|
237
257
|
* Check if a directed graph is weakly connected
|
|
258
|
+
* @param graph - The input graph (must be directed)
|
|
259
|
+
* @returns True if the graph is weakly connected, false otherwise
|
|
238
260
|
*/
|
|
239
261
|
export function isWeaklyConnected(graph: Graph): boolean {
|
|
240
262
|
if (!graph.isDirected) {
|
|
@@ -246,6 +268,8 @@ export function isWeaklyConnected(graph: Graph): boolean {
|
|
|
246
268
|
|
|
247
269
|
/**
|
|
248
270
|
* Find condensation graph (quotient graph of strongly connected components)
|
|
271
|
+
* @param graph - The input graph (must be directed)
|
|
272
|
+
* @returns Object containing the condensed graph, component map, and component arrays
|
|
249
273
|
*/
|
|
250
274
|
export function condensationGraph(graph: Graph): {
|
|
251
275
|
condensedGraph: Graph;
|
|
@@ -258,7 +282,7 @@ export function condensationGraph(graph: Graph): {
|
|
|
258
282
|
|
|
259
283
|
const components = stronglyConnectedComponents(graph);
|
|
260
284
|
const componentMap = new Map<NodeId, number>();
|
|
261
|
-
const condensedGraph = new (graph.constructor as new (config: {directed: boolean}) => Graph)({directed: true});
|
|
285
|
+
const condensedGraph = new (graph.constructor as new (config: { directed: boolean }) => Graph)({ directed: true });
|
|
262
286
|
|
|
263
287
|
// Map each node to its component index
|
|
264
288
|
for (let i = 0; i < components.length; i++) {
|
|
@@ -279,8 +303,7 @@ export function condensationGraph(graph: Graph): {
|
|
|
279
303
|
const sourceComponent = componentMap.get(edge.source);
|
|
280
304
|
const targetComponent = componentMap.get(edge.target);
|
|
281
305
|
|
|
282
|
-
if (sourceComponent !== undefined && targetComponent !== undefined &&
|
|
283
|
-
sourceComponent !== targetComponent) {
|
|
306
|
+
if (sourceComponent !== undefined && targetComponent !== undefined && sourceComponent !== targetComponent) {
|
|
284
307
|
const edgeKey = `${String(sourceComponent)}-${String(targetComponent)}`;
|
|
285
308
|
|
|
286
309
|
if (!addedEdges.has(edgeKey)) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {Graph} from "../../core/graph.js";
|
|
2
|
-
import type {NodeId} from "../../types/index.js";
|
|
1
|
+
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import type { NodeId } from "../../types/index.js";
|
|
3
|
+
import { bfsColoringWithPartitions } from "../traversal/bfs-variants.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Maximum Bipartite Matching implementation using Hopcroft-Karp algorithm
|
|
@@ -24,13 +25,17 @@ export interface BipartiteMatchingOptions {
|
|
|
24
25
|
/**
|
|
25
26
|
* Find maximum matching in a bipartite graph using augmenting path algorithm
|
|
26
27
|
* (simplified implementation that's more reliable than Hopcroft-Karp for this context)
|
|
28
|
+
* @param graph - The bipartite graph to find maximum matching for
|
|
29
|
+
* @param options - Optional configuration including left and right node partitions
|
|
30
|
+
* @returns The maximum matching as a map from left nodes to right nodes and the matching size
|
|
31
|
+
* @throws Error if the graph is not bipartite
|
|
27
32
|
*/
|
|
28
33
|
export function maximumBipartiteMatching(
|
|
29
34
|
graph: Graph,
|
|
30
35
|
options: BipartiteMatchingOptions = {},
|
|
31
36
|
): BipartiteMatchingResult {
|
|
32
37
|
// If partitions not provided, try to infer them
|
|
33
|
-
let {leftNodes, rightNodes} = options;
|
|
38
|
+
let { leftNodes, rightNodes } = options;
|
|
34
39
|
|
|
35
40
|
if (!leftNodes || !rightNodes) {
|
|
36
41
|
const partition = bipartitePartition(graph);
|
|
@@ -86,66 +91,32 @@ export function maximumBipartiteMatching(
|
|
|
86
91
|
|
|
87
92
|
/**
|
|
88
93
|
* Check if graph is bipartite and return the partition
|
|
94
|
+
* @param graph - The graph to check for bipartiteness
|
|
95
|
+
* @returns An object with left and right node sets if bipartite, or null if not bipartite
|
|
89
96
|
*/
|
|
90
|
-
export function bipartitePartition(graph: Graph): {left: Set<NodeId
|
|
91
|
-
const
|
|
92
|
-
const left = new Set<NodeId>();
|
|
93
|
-
const right = new Set<NodeId>();
|
|
97
|
+
export function bipartitePartition(graph: Graph): { left: Set<NodeId>; right: Set<NodeId> } | null {
|
|
98
|
+
const result = bfsColoringWithPartitions(graph);
|
|
94
99
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
for (const startNode of nodes) {
|
|
98
|
-
if (color.has(startNode.id)) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// BFS to color the component
|
|
103
|
-
const queue = [startNode.id];
|
|
104
|
-
color.set(startNode.id, true);
|
|
105
|
-
left.add(startNode.id);
|
|
106
|
-
|
|
107
|
-
while (queue.length > 0) {
|
|
108
|
-
const u = queue.shift();
|
|
109
|
-
if (u === undefined) {
|
|
110
|
-
continue;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const uColor = color.get(u);
|
|
114
|
-
if (uColor === undefined) {
|
|
115
|
-
continue;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const neighbors = Array.from(graph.neighbors(u));
|
|
119
|
-
for (const v of neighbors) {
|
|
120
|
-
if (!color.has(v)) {
|
|
121
|
-
color.set(v, !uColor);
|
|
122
|
-
if (!uColor) {
|
|
123
|
-
left.add(v);
|
|
124
|
-
} else {
|
|
125
|
-
right.add(v);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
queue.push(v);
|
|
129
|
-
} else if (color.get(v) === uColor) {
|
|
130
|
-
// Same color as neighbor - not bipartite
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
100
|
+
if (!result.isBipartite || !result.partitions) {
|
|
101
|
+
return null;
|
|
135
102
|
}
|
|
136
103
|
|
|
137
|
-
return {
|
|
104
|
+
return {
|
|
105
|
+
left: result.partitions[0],
|
|
106
|
+
right: result.partitions[1],
|
|
107
|
+
};
|
|
138
108
|
}
|
|
139
109
|
|
|
140
110
|
/**
|
|
141
111
|
* Simple greedy bipartite matching algorithm
|
|
142
112
|
* Useful for comparison or when Hopcroft-Karp is overkill
|
|
113
|
+
* @param graph - The bipartite graph to find matching for
|
|
114
|
+
* @param options - Optional configuration including left and right node partitions
|
|
115
|
+
* @returns The matching as a map from left nodes to right nodes and the matching size
|
|
116
|
+
* @throws Error if the graph is not bipartite
|
|
143
117
|
*/
|
|
144
|
-
export function greedyBipartiteMatching(
|
|
145
|
-
|
|
146
|
-
options: BipartiteMatchingOptions = {},
|
|
147
|
-
): BipartiteMatchingResult {
|
|
148
|
-
let {leftNodes, rightNodes} = options;
|
|
118
|
+
export function greedyBipartiteMatching(graph: Graph, options: BipartiteMatchingOptions = {}): BipartiteMatchingResult {
|
|
119
|
+
let { leftNodes, rightNodes } = options;
|
|
149
120
|
|
|
150
121
|
if (!leftNodes || !rightNodes) {
|
|
151
122
|
const partition = bipartitePartition(graph);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Graph matching algorithms
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
export type {BipartiteMatchingOptions, BipartiteMatchingResult} from "./bipartite.js";
|
|
6
|
-
export {bipartitePartition, greedyBipartiteMatching, maximumBipartiteMatching} from "./bipartite.js";
|
|
7
|
-
export type {IsomorphismOptions, IsomorphismResult} from "./isomorphism.js";
|
|
8
|
-
export {findAllIsomorphisms, isGraphIsomorphic} from "./isomorphism.js";
|
|
5
|
+
export type { BipartiteMatchingOptions, BipartiteMatchingResult } from "./bipartite.js";
|
|
6
|
+
export { bipartitePartition, greedyBipartiteMatching, maximumBipartiteMatching } from "./bipartite.js";
|
|
7
|
+
export type { IsomorphismOptions, IsomorphismResult } from "./isomorphism.js";
|
|
8
|
+
export { findAllIsomorphisms, isGraphIsomorphic } from "./isomorphism.js";
|