@graphty/algorithms 1.3.1 → 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 +358 -413
- package/dist/algorithms.js +564 -261
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +10 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +27 -6
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +11 -2
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +17 -3
- 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 +9 -6
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +21 -3
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +25 -7
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +28 -2
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank.js +30 -4
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -1
- 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 +1 -1
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +1 -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 +14 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +16 -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 +21 -4
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- 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 +0 -3
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +13 -6
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +0 -1
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +37 -6
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +37 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.js +42 -7
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -1
- 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 +19 -6
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +0 -4
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.js +1 -5
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -1
- 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 +10 -0
- 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 +10 -0
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +11 -4
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +18 -16
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +15 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +15 -0
- 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 +13 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.js +37 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +23 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs-variants.js +34 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts +1 -1
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +1 -1
- 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 +2 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -1
- package/dist/src/benchmark-all-algorithms.js +32 -3
- package/dist/src/benchmark-all-algorithms.js.map +1 -1
- package/dist/src/clustering/hierarchical.d.ts +8 -1
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +22 -6
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +2 -5
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +14 -12
- 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 +4 -1
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +61 -28
- 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 +45 -1
- 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 +4 -2
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +31 -2
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +0 -3
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +17 -4
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- 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/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 +36 -10
- 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 +27 -6
- 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 +103 -53
- package/dist/src/optimized/bit-packed.d.ts.map +1 -1
- package/dist/src/optimized/bit-packed.js +104 -54
- package/dist/src/optimized/bit-packed.js.map +1 -1
- package/dist/src/optimized/csr-graph.d.ts +87 -24
- package/dist/src/optimized/csr-graph.d.ts.map +1 -1
- package/dist/src/optimized/csr-graph.js +87 -24
- package/dist/src/optimized/csr-graph.js.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.d.ts +37 -16
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
- package/dist/src/optimized/direction-optimized-bfs.js +39 -20
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
- package/dist/src/optimized/graph-adapter.d.ts +58 -9
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -1
- package/dist/src/optimized/graph-adapter.js +58 -10
- package/dist/src/optimized/graph-adapter.js.map +1 -1
- package/dist/src/optimized/index.d.ts +1 -1
- package/dist/src/optimized/index.d.ts.map +1 -1
- package/dist/src/optimized/index.js +3 -1
- package/dist/src/optimized/index.js.map +1 -1
- 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 +22 -9
- 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 +28 -7
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +0 -1
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +32 -4
- package/dist/src/research/terahac.js.map +1 -1
- package/dist/src/utils/graph-converters.d.ts +31 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -1
- package/dist/src/utils/graph-converters.js +31 -0
- package/dist/src/utils/graph-converters.js.map +1 -1
- package/dist/src/utils/graph-utilities.d.ts.map +1 -1
- package/dist/src/utils/graph-utilities.js.map +1 -1
- package/dist/src/utils/math-utilities.d.ts +11 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -1
- package/dist/src/utils/math-utilities.js +14 -5
- package/dist/src/utils/math-utilities.js.map +1 -1
- package/dist/src/utils/optimization-helpers.d.ts +9 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -1
- package/dist/src/utils/optimization-helpers.js +12 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -1
- 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 +49 -45
- package/src/algorithms/centrality/betweenness.ts +34 -17
- package/src/algorithms/centrality/closeness.ts +34 -17
- package/src/algorithms/centrality/degree.ts +15 -19
- package/src/algorithms/centrality/delta-pagerank-simple.ts +36 -28
- package/src/algorithms/centrality/delta-pagerank.ts +34 -11
- package/src/algorithms/centrality/eigenvector.ts +14 -15
- package/src/algorithms/centrality/hits.ts +17 -17
- package/src/algorithms/centrality/index.ts +20 -15
- package/src/algorithms/centrality/katz.ts +17 -25
- package/src/algorithms/centrality/pagerank.ts +26 -17
- package/src/algorithms/community/girvan-newman.ts +39 -28
- package/src/algorithms/community/index.ts +6 -7
- package/src/algorithms/community/label-propagation.ts +20 -28
- package/src/algorithms/community/leiden.ts +61 -37
- package/src/algorithms/community/louvain-optimized.ts +49 -20
- package/src/algorithms/community/louvain.ts +35 -25
- package/src/algorithms/community/modularity-utils.ts +5 -17
- package/src/algorithms/components/connected.ts +40 -17
- package/src/algorithms/matching/bipartite.ts +17 -10
- 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 -15
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +28 -28
- package/src/algorithms/shortest-path/dijkstra.ts +23 -17
- 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 +64 -60
- package/src/algorithms/traversal/bfs-variants.ts +61 -37
- package/src/algorithms/traversal/bfs.ts +1 -7
- package/src/algorithms/traversal/dfs.ts +61 -42
- package/src/algorithms/traversal/index.ts +3 -3
- package/src/benchmark-all-algorithms.ts +57 -25
- package/src/clustering/hierarchical.ts +40 -34
- package/src/clustering/index.ts +8 -8
- package/src/clustering/k-core.ts +26 -42
- package/src/clustering/mcl.ts +53 -23
- package/src/clustering/spectral.ts +91 -57
- package/src/core/graph.ts +54 -14
- 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 +62 -35
- package/src/flow/min-cut.ts +43 -45
- package/src/index.ts +3 -2
- package/src/link-prediction/adamic-adar.ts +65 -51
- package/src/link-prediction/common-neighbors.ts +45 -36
- package/src/link-prediction/index.ts +1 -1
- package/src/optimized/bit-packed.ts +106 -56
- package/src/optimized/csr-graph.ts +93 -30
- package/src/optimized/direction-optimized-bfs.ts +44 -25
- package/src/optimized/graph-adapter.ts +64 -16
- package/src/optimized/index.ts +7 -9
- package/src/pathfinding/astar.ts +40 -25
- package/src/pathfinding/utils.ts +20 -13
- package/src/research/grsbm.ts +30 -24
- package/src/research/index.ts +4 -4
- package/src/research/sync.ts +39 -23
- package/src/research/terahac.ts +54 -20
- package/src/utils/graph-converters.ts +42 -14
- package/src/utils/graph-utilities.ts +11 -37
- package/src/utils/math-utilities.ts +17 -8
- package/src/utils/optimization-helpers.ts +27 -21
- package/src/utils/priorityQueue.ts +40 -6
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* "From Louvain to Leiden: guaranteeing well-connected communities"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import type {Graph} from "../../core/graph.js";
|
|
12
|
-
import {graphToMap} from "../../utils/graph-converters.js";
|
|
13
|
-
import {SeededRandom, shuffle} from "../../utils/math-utilities.js";
|
|
11
|
+
import type { Graph } from "../../core/graph.js";
|
|
12
|
+
import { graphToMap } from "../../utils/graph-converters.js";
|
|
13
|
+
import { SeededRandom, shuffle } from "../../utils/math-utilities.js";
|
|
14
14
|
|
|
15
15
|
export interface LeidenOptions {
|
|
16
16
|
resolution?: number;
|
|
@@ -28,17 +28,12 @@ export interface LeidenResult {
|
|
|
28
28
|
/**
|
|
29
29
|
* Internal implementation of Leiden algorithm for community detection
|
|
30
30
|
* Improves upon Louvain by ensuring well-connected communities
|
|
31
|
+
* @param inputGraph - Map representation of the graph (node to neighbors with weights)
|
|
32
|
+
* @param options - Algorithm configuration options
|
|
33
|
+
* @returns Community assignments, modularity score, and iteration count
|
|
31
34
|
*/
|
|
32
|
-
function leidenImpl(
|
|
33
|
-
|
|
34
|
-
options: LeidenOptions = {},
|
|
35
|
-
): LeidenResult {
|
|
36
|
-
const {
|
|
37
|
-
resolution = 1.0,
|
|
38
|
-
randomSeed = 42,
|
|
39
|
-
maxIterations = 100,
|
|
40
|
-
threshold = 1e-7,
|
|
41
|
-
} = options;
|
|
35
|
+
function leidenImpl(inputGraph: Map<string, Map<string, number>>, options: LeidenOptions = {}): LeidenResult {
|
|
36
|
+
const { resolution = 1.0, randomSeed = 42, maxIterations = 100, threshold = 1e-7 } = options;
|
|
42
37
|
|
|
43
38
|
// Handle empty graph
|
|
44
39
|
if (inputGraph.size === 0) {
|
|
@@ -85,7 +80,7 @@ function leidenImpl(
|
|
|
85
80
|
let improved = false;
|
|
86
81
|
|
|
87
82
|
// Phase 1: Local moving of nodes (fast)
|
|
88
|
-
const nodeOrder = [...
|
|
83
|
+
const nodeOrder = [...nodes];
|
|
89
84
|
shuffle(nodeOrder, random);
|
|
90
85
|
|
|
91
86
|
for (const node of nodeOrder) {
|
|
@@ -106,7 +101,13 @@ function leidenImpl(
|
|
|
106
101
|
}
|
|
107
102
|
|
|
108
103
|
const gain = calculateModularityGain(
|
|
109
|
-
node,
|
|
104
|
+
node,
|
|
105
|
+
community,
|
|
106
|
+
currentGraph,
|
|
107
|
+
communities,
|
|
108
|
+
degrees,
|
|
109
|
+
totalWeight,
|
|
110
|
+
resolution,
|
|
110
111
|
);
|
|
111
112
|
|
|
112
113
|
if (gain > bestGain) {
|
|
@@ -128,9 +129,7 @@ function leidenImpl(
|
|
|
128
129
|
createAggregateNetwork(currentGraph, communities);
|
|
129
130
|
|
|
130
131
|
// Refine partition using aggregate network
|
|
131
|
-
const subsetPartition = refinePartition(
|
|
132
|
-
currentGraph, communities,
|
|
133
|
-
);
|
|
132
|
+
const subsetPartition = refinePartition(currentGraph, communities);
|
|
134
133
|
|
|
135
134
|
// Apply refined partition
|
|
136
135
|
for (const [node, newCommunity] of subsetPartition) {
|
|
@@ -158,7 +157,7 @@ function leidenImpl(
|
|
|
158
157
|
} // No aggregation possible
|
|
159
158
|
|
|
160
159
|
// Continue with aggregated network
|
|
161
|
-
const {graph: aggregatedGraph} = aggregated;
|
|
160
|
+
const { graph: aggregatedGraph } = aggregated;
|
|
162
161
|
currentGraph = aggregatedGraph;
|
|
163
162
|
communities.clear();
|
|
164
163
|
let communityId = 0;
|
|
@@ -195,6 +194,12 @@ function leidenImpl(
|
|
|
195
194
|
|
|
196
195
|
/**
|
|
197
196
|
* Calculate modularity of a partition
|
|
197
|
+
* @param graph - Map representation of the graph
|
|
198
|
+
* @param communities - Map of node IDs to community IDs
|
|
199
|
+
* @param degrees - Map of node IDs to their weighted degrees
|
|
200
|
+
* @param totalWeight - Total weight of all edges in the graph
|
|
201
|
+
* @param resolution - Resolution parameter for modularity calculation
|
|
202
|
+
* @returns The modularity score of the partition
|
|
198
203
|
*/
|
|
199
204
|
function calculateModularity(
|
|
200
205
|
graph: Map<string, Map<string, number>>,
|
|
@@ -236,11 +241,11 @@ function calculateModularity(
|
|
|
236
241
|
}
|
|
237
242
|
|
|
238
243
|
// Normalize and apply resolution
|
|
239
|
-
modularity /=
|
|
244
|
+
modularity /= 2 * totalWeight;
|
|
240
245
|
|
|
241
246
|
// Subtract expected edges
|
|
242
247
|
for (const weight of communityWeights.values()) {
|
|
243
|
-
modularity -= resolution * (
|
|
248
|
+
modularity -= resolution * (weight / (2 * totalWeight)) ** 2;
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
return modularity;
|
|
@@ -248,6 +253,10 @@ function calculateModularity(
|
|
|
248
253
|
|
|
249
254
|
/**
|
|
250
255
|
* Get communities of neighbors
|
|
256
|
+
* @param node - The node ID to get neighbor communities for
|
|
257
|
+
* @param graph - Map representation of the graph
|
|
258
|
+
* @param communities - Map of node IDs to community IDs
|
|
259
|
+
* @returns Map of community IDs to total edge weight connecting to that community
|
|
251
260
|
*/
|
|
252
261
|
function getNeighborCommunities(
|
|
253
262
|
node: string,
|
|
@@ -271,6 +280,14 @@ function getNeighborCommunities(
|
|
|
271
280
|
|
|
272
281
|
/**
|
|
273
282
|
* Calculate modularity gain from moving a node to a community
|
|
283
|
+
* @param node - The node ID to move
|
|
284
|
+
* @param targetCommunity - The target community ID
|
|
285
|
+
* @param graph - Map representation of the graph
|
|
286
|
+
* @param communities - Map of node IDs to community IDs
|
|
287
|
+
* @param degrees - Map of node IDs to their weighted degrees
|
|
288
|
+
* @param totalWeight - Total weight of all edges in the graph
|
|
289
|
+
* @param resolution - Resolution parameter for modularity calculation
|
|
290
|
+
* @returns The modularity gain from moving the node to the target community
|
|
274
291
|
*/
|
|
275
292
|
function calculateModularityGain(
|
|
276
293
|
node: string,
|
|
@@ -327,22 +344,26 @@ function calculateModularityGain(
|
|
|
327
344
|
|
|
328
345
|
// Modularity gain calculation
|
|
329
346
|
const m2 = 2 * totalWeight;
|
|
330
|
-
const gain =
|
|
331
|
-
|
|
347
|
+
const gain =
|
|
348
|
+
(weightToTarget - weightToCurrent) / totalWeight -
|
|
349
|
+
(resolution * nodeDegree * (targetDegree - currentDegree)) / (m2 * m2);
|
|
332
350
|
|
|
333
351
|
return gain;
|
|
334
352
|
}
|
|
335
353
|
|
|
336
354
|
/**
|
|
337
355
|
* Create aggregate network where each community becomes a super-node
|
|
356
|
+
* @param graph - Map representation of the graph
|
|
357
|
+
* @param communities - Map of node IDs to community IDs
|
|
358
|
+
* @returns Object with aggregate graph and node-to-community mapping
|
|
338
359
|
*/
|
|
339
360
|
function createAggregateNetwork(
|
|
340
361
|
graph: Map<string, Map<string, number>>,
|
|
341
362
|
communities: Map<string, number>,
|
|
342
363
|
): {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
364
|
+
aggregateGraph: Map<number, Map<number, number>>;
|
|
365
|
+
nodeMapping: Map<string, number>;
|
|
366
|
+
} {
|
|
346
367
|
const aggregateGraph = new Map<number, Map<number, number>>();
|
|
347
368
|
const nodeMapping = new Map<string, number>();
|
|
348
369
|
|
|
@@ -375,12 +396,15 @@ function createAggregateNetwork(
|
|
|
375
396
|
}
|
|
376
397
|
}
|
|
377
398
|
|
|
378
|
-
return {aggregateGraph, nodeMapping};
|
|
399
|
+
return { aggregateGraph, nodeMapping };
|
|
379
400
|
}
|
|
380
401
|
|
|
381
402
|
/**
|
|
382
403
|
* Refine partition (Leiden-specific improvement)
|
|
383
404
|
* Ensures well-connected communities by considering subsets
|
|
405
|
+
* @param originalGraph - Map representation of the original graph
|
|
406
|
+
* @param communities - Map of node IDs to community IDs
|
|
407
|
+
* @returns Refined community assignments with well-connected communities
|
|
384
408
|
*/
|
|
385
409
|
function refinePartition(
|
|
386
410
|
originalGraph: Map<string, Map<string, number>>,
|
|
@@ -448,6 +472,8 @@ function refinePartition(
|
|
|
448
472
|
|
|
449
473
|
/**
|
|
450
474
|
* Find connected components in undirected graph
|
|
475
|
+
* @param graph - Map representation of the graph (node to set of neighbors)
|
|
476
|
+
* @returns Array of connected components (each is a set of node IDs)
|
|
451
477
|
*/
|
|
452
478
|
function findConnectedComponents(graph: Map<string, Set<string>>): Set<string>[] {
|
|
453
479
|
const visited = new Set<string>();
|
|
@@ -486,14 +512,17 @@ function findConnectedComponents(graph: Map<string, Set<string>>): Set<string>[]
|
|
|
486
512
|
|
|
487
513
|
/**
|
|
488
514
|
* Aggregate communities into super-nodes
|
|
515
|
+
* @param graph - Map representation of the graph
|
|
516
|
+
* @param communities - Map of node IDs to community IDs
|
|
517
|
+
* @returns Object with aggregated graph and node-to-supernode mapping
|
|
489
518
|
*/
|
|
490
519
|
function aggregateCommunities(
|
|
491
520
|
graph: Map<string, Map<string, number>>,
|
|
492
521
|
communities: Map<string, number>,
|
|
493
522
|
): {
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
523
|
+
graph: Map<string, Map<string, number>>;
|
|
524
|
+
mapping: Map<string, string>;
|
|
525
|
+
} {
|
|
497
526
|
const aggregated = new Map<string, Map<string, number>>();
|
|
498
527
|
const mapping = new Map<string, string>();
|
|
499
528
|
|
|
@@ -546,13 +575,12 @@ function aggregateCommunities(
|
|
|
546
575
|
}
|
|
547
576
|
}
|
|
548
577
|
|
|
549
|
-
return {graph: aggregated, mapping};
|
|
578
|
+
return { graph: aggregated, mapping };
|
|
550
579
|
}
|
|
551
580
|
|
|
552
581
|
/**
|
|
553
582
|
* Leiden algorithm for community detection
|
|
554
583
|
* Improves upon Louvain by ensuring well-connected communities
|
|
555
|
-
*
|
|
556
584
|
* @param graph - Undirected weighted graph - accepts Graph class or Map representation
|
|
557
585
|
* @param options - Algorithm options
|
|
558
586
|
* @returns Community assignments and modularity
|
|
@@ -560,12 +588,8 @@ function aggregateCommunities(
|
|
|
560
588
|
* Time Complexity: O(m) per iteration, typically O(m log m) total
|
|
561
589
|
* Space Complexity: O(n + m)
|
|
562
590
|
*/
|
|
563
|
-
export function leiden(
|
|
564
|
-
graph: Graph,
|
|
565
|
-
options: LeidenOptions = {},
|
|
566
|
-
): LeidenResult {
|
|
591
|
+
export function leiden(graph: Graph, options: LeidenOptions = {}): LeidenResult {
|
|
567
592
|
// Convert Graph to Map representation
|
|
568
593
|
const graphMap = graphToMap(graph);
|
|
569
594
|
return leidenImpl(graphMap, options);
|
|
570
595
|
}
|
|
571
|
-
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {Graph} from "../../core/graph.js";
|
|
2
|
-
import type {CommunityResult, NodeId} from "../../types/index.js";
|
|
1
|
+
import type { Graph } from "../../core/graph.js";
|
|
2
|
+
import type { CommunityResult, NodeId } from "../../types/index.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Optimized Louvain community detection algorithm with early pruning and threshold cycling
|
|
@@ -50,6 +50,9 @@ interface PruningStats {
|
|
|
50
50
|
stableNodesPruned: number;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Optimized Louvain implementation with early pruning and threshold cycling
|
|
55
|
+
*/
|
|
53
56
|
export class OptimizedLouvain {
|
|
54
57
|
private graph: Graph;
|
|
55
58
|
private communities: Map<NodeId, number>;
|
|
@@ -59,6 +62,10 @@ export class OptimizedLouvain {
|
|
|
59
62
|
private totalWeight: number;
|
|
60
63
|
private pruningStats: PruningStats;
|
|
61
64
|
|
|
65
|
+
/**
|
|
66
|
+
* Create an optimized Louvain detector for the given graph
|
|
67
|
+
* @param graph - The input graph to detect communities in
|
|
68
|
+
*/
|
|
62
69
|
constructor(graph: Graph) {
|
|
63
70
|
this.graph = graph;
|
|
64
71
|
this.communities = new Map();
|
|
@@ -75,6 +82,8 @@ export class OptimizedLouvain {
|
|
|
75
82
|
|
|
76
83
|
/**
|
|
77
84
|
* Run optimized Louvain algorithm
|
|
85
|
+
* @param options - Algorithm configuration options
|
|
86
|
+
* @returns Community detection result with communities, modularity, and iterations
|
|
78
87
|
*/
|
|
79
88
|
public detectCommunities(options: OptimizedLouvainOptions = {}): CommunityResult {
|
|
80
89
|
const {
|
|
@@ -95,14 +104,12 @@ export class OptimizedLouvain {
|
|
|
95
104
|
|
|
96
105
|
while (iteration < maxIterations && improved) {
|
|
97
106
|
// Get nodes in optimal processing order
|
|
98
|
-
const orderedNodes = importanceOrdering
|
|
99
|
-
this.getNodesInImportanceOrder()
|
|
100
|
-
Array.from(this.graph.nodes()).map((n) => n.id);
|
|
107
|
+
const orderedNodes = importanceOrdering
|
|
108
|
+
? this.getNodesInImportanceOrder()
|
|
109
|
+
: Array.from(this.graph.nodes()).map((n) => n.id);
|
|
101
110
|
|
|
102
111
|
// Apply adaptive threshold
|
|
103
|
-
const threshold = thresholdCycling ?
|
|
104
|
-
this.getAdaptiveThreshold(iteration, pruningThreshold) :
|
|
105
|
-
0;
|
|
112
|
+
const threshold = thresholdCycling ? this.getAdaptiveThreshold(iteration, pruningThreshold) : 0;
|
|
106
113
|
|
|
107
114
|
// Perform local optimization
|
|
108
115
|
improved = this.performLocalMoving(orderedNodes, {
|
|
@@ -193,6 +200,7 @@ export class OptimizedLouvain {
|
|
|
193
200
|
|
|
194
201
|
/**
|
|
195
202
|
* Get nodes ordered by importance (degree * log(weight))
|
|
203
|
+
* @returns Array of node IDs sorted by descending importance
|
|
196
204
|
*/
|
|
197
205
|
private getNodesInImportanceOrder(): NodeId[] {
|
|
198
206
|
const nodeImportance = new Map<NodeId, number>();
|
|
@@ -213,6 +221,12 @@ export class OptimizedLouvain {
|
|
|
213
221
|
|
|
214
222
|
/**
|
|
215
223
|
* Perform local moving phase with optimizations
|
|
224
|
+
* @param nodes - Array of node IDs to process
|
|
225
|
+
* @param options - Local moving options
|
|
226
|
+
* @param options.pruneLeaves - Whether to skip leaf nodes
|
|
227
|
+
* @param options.threshold - Minimum gain threshold for moves
|
|
228
|
+
* @param options.resolution - Resolution parameter for modularity
|
|
229
|
+
* @returns True if any improvement was made, false otherwise
|
|
216
230
|
*/
|
|
217
231
|
private performLocalMoving(
|
|
218
232
|
nodes: NodeId[],
|
|
@@ -222,7 +236,7 @@ export class OptimizedLouvain {
|
|
|
222
236
|
resolution: number;
|
|
223
237
|
},
|
|
224
238
|
): boolean {
|
|
225
|
-
const {pruneLeaves, threshold, resolution} = options;
|
|
239
|
+
const { pruneLeaves, threshold, resolution } = options;
|
|
226
240
|
let improvement = false;
|
|
227
241
|
let hasChanged = true;
|
|
228
242
|
|
|
@@ -284,6 +298,8 @@ export class OptimizedLouvain {
|
|
|
284
298
|
|
|
285
299
|
/**
|
|
286
300
|
* Check if node is a leaf (degree 1)
|
|
301
|
+
* @param nodeId - The node ID to check
|
|
302
|
+
* @returns True if the node has degree 1, false otherwise
|
|
287
303
|
*/
|
|
288
304
|
private isLeafNode(nodeId: NodeId): boolean {
|
|
289
305
|
const degree = this.nodeDegrees.get(nodeId) ?? 0;
|
|
@@ -292,6 +308,9 @@ export class OptimizedLouvain {
|
|
|
292
308
|
|
|
293
309
|
/**
|
|
294
310
|
* Get adaptive threshold that decreases with iterations
|
|
311
|
+
* @param iteration - Current iteration number
|
|
312
|
+
* @param baseThreshold - Base threshold value to scale
|
|
313
|
+
* @returns Adaptive threshold value that decays over iterations
|
|
295
314
|
*/
|
|
296
315
|
private getAdaptiveThreshold(iteration: number, baseThreshold: number): number {
|
|
297
316
|
// Exponentially decay threshold with iterations
|
|
@@ -301,12 +320,12 @@ export class OptimizedLouvain {
|
|
|
301
320
|
|
|
302
321
|
/**
|
|
303
322
|
* Calculate modularity gain from moving a node to a community
|
|
323
|
+
* @param nodeId - The node ID to move
|
|
324
|
+
* @param targetCommunity - The target community ID
|
|
325
|
+
* @param resolution - Resolution parameter for modularity calculation
|
|
326
|
+
* @returns The modularity gain from moving the node
|
|
304
327
|
*/
|
|
305
|
-
private calculateModularityGain(
|
|
306
|
-
nodeId: NodeId,
|
|
307
|
-
targetCommunity: number,
|
|
308
|
-
resolution: number,
|
|
309
|
-
): number {
|
|
328
|
+
private calculateModularityGain(nodeId: NodeId, targetCommunity: number, resolution: number): number {
|
|
310
329
|
const nodeWeight = this.nodeWeights.get(nodeId) ?? 0;
|
|
311
330
|
|
|
312
331
|
// Sum of weights from node to target community
|
|
@@ -333,13 +352,16 @@ export class OptimizedLouvain {
|
|
|
333
352
|
const targetWeight = this.communityWeights.get(targetCommunity) ?? 0;
|
|
334
353
|
|
|
335
354
|
// Modularity gain formula
|
|
336
|
-
const gain =
|
|
355
|
+
const gain =
|
|
356
|
+
(weightToTarget - (resolution * nodeWeight * targetWeight) / (2 * this.totalWeight)) / this.totalWeight;
|
|
337
357
|
|
|
338
358
|
return gain;
|
|
339
359
|
}
|
|
340
360
|
|
|
341
361
|
/**
|
|
342
362
|
* Remove node from community (for gain calculation)
|
|
363
|
+
* @param nodeId - The node ID to remove
|
|
364
|
+
* @param community - The community ID to remove from
|
|
343
365
|
*/
|
|
344
366
|
private removeNodeFromCommunity(nodeId: NodeId, community: number): void {
|
|
345
367
|
const nodeWeight = this.nodeWeights.get(nodeId) ?? 0;
|
|
@@ -349,6 +371,8 @@ export class OptimizedLouvain {
|
|
|
349
371
|
|
|
350
372
|
/**
|
|
351
373
|
* Add node to community
|
|
374
|
+
* @param nodeId - The node ID to add
|
|
375
|
+
* @param community - The community ID to add to
|
|
352
376
|
*/
|
|
353
377
|
private addNodeToCommunity(nodeId: NodeId, community: number): void {
|
|
354
378
|
const nodeWeight = this.nodeWeights.get(nodeId) ?? 0;
|
|
@@ -358,6 +382,8 @@ export class OptimizedLouvain {
|
|
|
358
382
|
|
|
359
383
|
/**
|
|
360
384
|
* Get neighboring communities of a node
|
|
385
|
+
* @param nodeId - The node ID to find neighbor communities for
|
|
386
|
+
* @returns Set of community IDs that neighbors belong to
|
|
361
387
|
*/
|
|
362
388
|
private getNeighborCommunities(nodeId: NodeId): Set<number> {
|
|
363
389
|
const communities = new Set<number>();
|
|
@@ -384,6 +410,8 @@ export class OptimizedLouvain {
|
|
|
384
410
|
|
|
385
411
|
/**
|
|
386
412
|
* Calculate total modularity
|
|
413
|
+
* @param resolution - Resolution parameter for modularity calculation
|
|
414
|
+
* @returns The modularity score of the current partition
|
|
387
415
|
*/
|
|
388
416
|
private calculateModularity(resolution: number): number {
|
|
389
417
|
if (this.totalWeight === 0) {
|
|
@@ -427,19 +455,20 @@ export class OptimizedLouvain {
|
|
|
427
455
|
|
|
428
456
|
/**
|
|
429
457
|
* Get pruning statistics
|
|
458
|
+
* @returns Statistics about nodes pruned during optimization
|
|
430
459
|
*/
|
|
431
460
|
public getPruningStats(): PruningStats {
|
|
432
|
-
return {...
|
|
461
|
+
return { ...this.pruningStats };
|
|
433
462
|
}
|
|
434
463
|
}
|
|
435
464
|
|
|
436
465
|
/**
|
|
437
466
|
* Optimized Louvain algorithm with automatic optimization selection
|
|
467
|
+
* @param graph - The input graph to detect communities in
|
|
468
|
+
* @param options - Algorithm configuration options
|
|
469
|
+
* @returns Community detection result with communities, modularity, and iterations
|
|
438
470
|
*/
|
|
439
|
-
export function louvainOptimized(
|
|
440
|
-
graph: Graph,
|
|
441
|
-
options: OptimizedLouvainOptions = {},
|
|
442
|
-
): CommunityResult {
|
|
471
|
+
export function louvainOptimized(graph: Graph, options: OptimizedLouvainOptions = {}): CommunityResult {
|
|
443
472
|
const optimizer = new OptimizedLouvain(graph);
|
|
444
473
|
return optimizer.detectCommunities(options);
|
|
445
474
|
}
|
|
@@ -1,12 +1,7 @@
|
|
|
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 {
|
|
5
|
-
calculateModularity,
|
|
6
|
-
getNeighborCommunities,
|
|
7
|
-
getNodeDegree,
|
|
8
|
-
getTotalEdgeWeight,
|
|
9
|
-
} from "./modularity-utils.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";
|
|
10
5
|
|
|
11
6
|
/**
|
|
12
7
|
* Louvain community detection algorithm
|
|
@@ -16,17 +11,13 @@ import {
|
|
|
16
11
|
*
|
|
17
12
|
* References:
|
|
18
13
|
* - Blondel, V. D., Guillaume, J. L., Lambiotte, R., & Lefebvre, E. (2008).
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
14
|
+
* Fast unfolding of communities in large networks.
|
|
15
|
+
* Journal of statistical mechanics: theory and experiment, 2008(10), P10008.
|
|
22
16
|
* @param graph - The input graph
|
|
23
17
|
* @param options - Algorithm options
|
|
24
18
|
* @returns Community detection result with communities, modularity, and iterations
|
|
25
19
|
*/
|
|
26
|
-
export function louvain(
|
|
27
|
-
graph: Graph,
|
|
28
|
-
options: LouvainOptions = {},
|
|
29
|
-
): CommunityResult {
|
|
20
|
+
export function louvain(graph: Graph, options: LouvainOptions = {}): CommunityResult {
|
|
30
21
|
const resolution = options.resolution ?? 1.0;
|
|
31
22
|
const maxIterations = options.maxIterations ?? 100;
|
|
32
23
|
const tolerance = options.tolerance ?? 1e-6;
|
|
@@ -62,7 +53,7 @@ export function louvain(
|
|
|
62
53
|
const newModularity = calculateModularity(graph, communities, resolution);
|
|
63
54
|
|
|
64
55
|
// Check for convergence
|
|
65
|
-
if (
|
|
56
|
+
if (newModularity - modularity < tolerance) {
|
|
66
57
|
break;
|
|
67
58
|
}
|
|
68
59
|
|
|
@@ -80,6 +71,8 @@ export function louvain(
|
|
|
80
71
|
|
|
81
72
|
/**
|
|
82
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
|
|
83
76
|
*/
|
|
84
77
|
function initializeCommunities(graph: Graph): Map<NodeId, number> {
|
|
85
78
|
const communities = new Map<NodeId, number>();
|
|
@@ -96,12 +89,12 @@ function initializeCommunities(graph: Graph): Map<NodeId, number> {
|
|
|
96
89
|
* Phase 1 of Louvain algorithm: Local optimization
|
|
97
90
|
* For each node, try moving to neighboring communities and keep the move
|
|
98
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
|
|
99
96
|
*/
|
|
100
|
-
function louvainPhase1(
|
|
101
|
-
graph: Graph,
|
|
102
|
-
communities: Map<NodeId, number>,
|
|
103
|
-
resolution: number,
|
|
104
|
-
): boolean {
|
|
97
|
+
function louvainPhase1(graph: Graph, communities: Map<NodeId, number>, resolution: number): boolean {
|
|
105
98
|
let globalImprovement = false;
|
|
106
99
|
let localImprovement = true;
|
|
107
100
|
|
|
@@ -118,7 +111,11 @@ function louvainPhase1(
|
|
|
118
111
|
|
|
119
112
|
// Calculate current modularity contribution
|
|
120
113
|
const currentModularity = nodeModularityContribution(
|
|
121
|
-
graph,
|
|
114
|
+
graph,
|
|
115
|
+
nodeId,
|
|
116
|
+
currentCommunity,
|
|
117
|
+
communities,
|
|
118
|
+
resolution,
|
|
122
119
|
);
|
|
123
120
|
|
|
124
121
|
let bestCommunity = currentCommunity;
|
|
@@ -133,7 +130,11 @@ function louvainPhase1(
|
|
|
133
130
|
}
|
|
134
131
|
|
|
135
132
|
const newModularity = nodeModularityContribution(
|
|
136
|
-
graph,
|
|
133
|
+
graph,
|
|
134
|
+
nodeId,
|
|
135
|
+
neighborCommunity,
|
|
136
|
+
communities,
|
|
137
|
+
resolution,
|
|
137
138
|
);
|
|
138
139
|
|
|
139
140
|
if (newModularity > bestModularity) {
|
|
@@ -156,6 +157,12 @@ function louvainPhase1(
|
|
|
156
157
|
|
|
157
158
|
/**
|
|
158
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
|
|
159
166
|
*/
|
|
160
167
|
function nodeModularityContribution(
|
|
161
168
|
graph: Graph,
|
|
@@ -192,13 +199,16 @@ function nodeModularityContribution(
|
|
|
192
199
|
}
|
|
193
200
|
|
|
194
201
|
// Modularity formula: Q = (1/2m) * Σ[A_ij - (k_i * k_j)/(2m)] * δ(c_i, c_j)
|
|
195
|
-
const modularityIncrease =
|
|
202
|
+
const modularityIncrease =
|
|
203
|
+
(internalLinks - (resolution * nodeDegree * communityDegree) / (2 * totalEdgeWeight)) / totalEdgeWeight;
|
|
196
204
|
|
|
197
205
|
return modularityIncrease;
|
|
198
206
|
}
|
|
199
207
|
|
|
200
208
|
/**
|
|
201
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)
|
|
202
212
|
*/
|
|
203
213
|
function extractCommunities(communities: Map<NodeId, number>): NodeId[][] {
|
|
204
214
|
const communityMap = new Map<number, NodeId[]>();
|
|
@@ -3,15 +3,14 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Used by: Louvain, Leiden, Girvan-Newman
|
|
5
5
|
*/
|
|
6
|
-
import type {Graph} from "../../core/graph.js";
|
|
7
|
-
import type {NodeId} from "../../types/index.js";
|
|
6
|
+
import type { Graph } from "../../core/graph.js";
|
|
7
|
+
import type { NodeId } from "../../types/index.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Calculate total edge weight in the graph
|
|
11
11
|
*
|
|
12
12
|
* For undirected graphs, each edge is counted once.
|
|
13
13
|
* For directed graphs, each directed edge is counted once.
|
|
14
|
-
*
|
|
15
14
|
* @param graph - The input graph
|
|
16
15
|
* @returns Total sum of edge weights (default weight is 1)
|
|
17
16
|
*/
|
|
@@ -29,7 +28,6 @@ export function getTotalEdgeWeight(graph: Graph): number {
|
|
|
29
28
|
* Get the total degree (sum of edge weights) for a node
|
|
30
29
|
*
|
|
31
30
|
* Computes the weighted degree by summing all edge weights incident to the node.
|
|
32
|
-
*
|
|
33
31
|
* @param graph - The input graph
|
|
34
32
|
* @param nodeId - The node ID to compute degree for
|
|
35
33
|
* @returns The weighted degree of the node
|
|
@@ -49,17 +47,12 @@ export function getNodeDegree(graph: Graph, nodeId: NodeId): number {
|
|
|
49
47
|
* Get communities of neighboring nodes
|
|
50
48
|
*
|
|
51
49
|
* Returns the set of community IDs that neighbors of the given node belong to.
|
|
52
|
-
*
|
|
53
50
|
* @param graph - The input graph
|
|
54
51
|
* @param nodeId - The node to get neighbor communities for
|
|
55
52
|
* @param communities - Map from node IDs to community IDs
|
|
56
53
|
* @returns Set of community IDs that neighbors belong to
|
|
57
54
|
*/
|
|
58
|
-
export function getNeighborCommunities(
|
|
59
|
-
graph: Graph,
|
|
60
|
-
nodeId: NodeId,
|
|
61
|
-
communities: Map<NodeId, number>,
|
|
62
|
-
): Set<number> {
|
|
55
|
+
export function getNeighborCommunities(graph: Graph, nodeId: NodeId, communities: Map<NodeId, number>): Set<number> {
|
|
63
56
|
const neighborCommunities = new Set<number>();
|
|
64
57
|
|
|
65
58
|
for (const neighbor of graph.neighbors(nodeId)) {
|
|
@@ -85,17 +78,12 @@ export function getNeighborCommunities(
|
|
|
85
78
|
* - k_i, k_j = degrees of nodes i and j
|
|
86
79
|
* - γ = resolution parameter (higher values favor smaller communities)
|
|
87
80
|
* - δ = Kronecker delta (1 if same community, 0 otherwise)
|
|
88
|
-
*
|
|
89
81
|
* @param graph - The input graph
|
|
90
82
|
* @param communities - Map from node IDs to community IDs
|
|
91
83
|
* @param resolution - Resolution parameter (default: 1.0)
|
|
92
84
|
* @returns Modularity score (range typically -0.5 to 1.0)
|
|
93
85
|
*/
|
|
94
|
-
export function calculateModularity(
|
|
95
|
-
graph: Graph,
|
|
96
|
-
communities: Map<NodeId, number>,
|
|
97
|
-
resolution = 1.0,
|
|
98
|
-
): number {
|
|
86
|
+
export function calculateModularity(graph: Graph, communities: Map<NodeId, number>, resolution = 1.0): number {
|
|
99
87
|
const totalEdgeWeight = getTotalEdgeWeight(graph);
|
|
100
88
|
if (totalEdgeWeight === 0) {
|
|
101
89
|
return 0;
|
|
@@ -137,7 +125,7 @@ export function calculateModularity(
|
|
|
137
125
|
const degreeI = getNodeDegree(graph, nodeI.id);
|
|
138
126
|
const degreeJ = getNodeDegree(graph, nodeJ.id);
|
|
139
127
|
|
|
140
|
-
modularity += edgeWeight - (
|
|
128
|
+
modularity += edgeWeight - (resolution * degreeI * degreeJ) / (2 * totalEdgeWeight);
|
|
141
129
|
}
|
|
142
130
|
}
|
|
143
131
|
}
|