@graphty/algorithms 1.0.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/README.md +1259 -61
  2. package/dist/algorithms.d.ts +2 -0
  3. package/dist/algorithms.d.ts.map +1 -0
  4. package/dist/algorithms.js +2 -0
  5. package/dist/algorithms.js.map +1 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +22 -7
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.js +7 -7
  10. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  11. package/dist/src/algorithms/centrality/degree.js +1 -1
  12. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  13. package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
  14. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
  15. package/dist/src/algorithms/centrality/eigenvector.js +113 -0
  16. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
  17. package/dist/src/algorithms/centrality/hits.d.ts +35 -0
  18. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
  19. package/dist/src/algorithms/centrality/hits.js +146 -0
  20. package/dist/src/algorithms/centrality/hits.js.map +1 -0
  21. package/dist/src/algorithms/centrality/index.d.ts +6 -0
  22. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  23. package/dist/src/algorithms/centrality/index.js +3 -0
  24. package/dist/src/algorithms/centrality/index.js.map +1 -1
  25. package/dist/src/algorithms/centrality/katz.d.ts +30 -0
  26. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
  27. package/dist/src/algorithms/centrality/katz.js +85 -0
  28. package/dist/src/algorithms/centrality/katz.js.map +1 -0
  29. package/dist/src/algorithms/centrality/pagerank.js +3 -3
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
  32. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
  33. package/dist/src/algorithms/community/girvan-newman.js +297 -0
  34. package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
  35. package/dist/src/algorithms/community/index.d.ts +12 -0
  36. package/dist/src/algorithms/community/index.d.ts.map +1 -0
  37. package/dist/src/algorithms/community/index.js +12 -0
  38. package/dist/src/algorithms/community/index.js.map +1 -0
  39. package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
  40. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
  41. package/dist/src/algorithms/community/label-propagation.js +307 -0
  42. package/dist/src/algorithms/community/label-propagation.js.map +1 -0
  43. package/dist/src/algorithms/community/leiden.d.ts +33 -0
  44. package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
  45. package/dist/src/algorithms/community/leiden.js +434 -0
  46. package/dist/src/algorithms/community/leiden.js.map +1 -0
  47. package/dist/src/algorithms/community/louvain.d.ts +19 -0
  48. package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
  49. package/dist/src/algorithms/community/louvain.js +219 -0
  50. package/dist/src/algorithms/community/louvain.js.map +1 -0
  51. package/dist/src/algorithms/components/connected.js +7 -7
  52. package/dist/src/algorithms/components/connected.js.map +1 -1
  53. package/dist/src/algorithms/index.d.ts +7 -0
  54. package/dist/src/algorithms/index.d.ts.map +1 -1
  55. package/dist/src/algorithms/index.js +14 -0
  56. package/dist/src/algorithms/index.js.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
  58. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
  59. package/dist/src/algorithms/matching/bipartite.js +127 -0
  60. package/dist/src/algorithms/matching/bipartite.js.map +1 -0
  61. package/dist/src/algorithms/matching/index.d.ts +8 -0
  62. package/dist/src/algorithms/matching/index.d.ts.map +1 -0
  63. package/dist/src/algorithms/matching/index.js +6 -0
  64. package/dist/src/algorithms/matching/index.js.map +1 -0
  65. package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
  66. package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
  67. package/dist/src/algorithms/matching/isomorphism.js +291 -0
  68. package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
  69. package/dist/src/algorithms/mst/index.d.ts +4 -0
  70. package/dist/src/algorithms/mst/index.d.ts.map +1 -0
  71. package/dist/src/algorithms/mst/index.js +3 -0
  72. package/dist/src/algorithms/mst/index.js.map +1 -0
  73. package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
  74. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
  75. package/dist/src/algorithms/mst/kruskal.js +43 -0
  76. package/dist/src/algorithms/mst/kruskal.js.map +1 -0
  77. package/dist/src/algorithms/mst/prim.d.ts +5 -0
  78. package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
  79. package/dist/src/algorithms/mst/prim.js +63 -0
  80. package/dist/src/algorithms/mst/prim.js.map +1 -0
  81. package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
  82. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  83. package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
  84. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  85. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
  86. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
  87. package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
  88. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
  89. package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
  90. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
  91. package/dist/src/algorithms/shortest-path/index.js +1 -0
  92. package/dist/src/algorithms/shortest-path/index.js.map +1 -1
  93. package/dist/src/algorithms/traversal/bfs.js +2 -2
  94. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  95. package/dist/src/algorithms/traversal/dfs.js +11 -11
  96. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  97. package/dist/src/clustering/hierarchical.d.ts +48 -0
  98. package/dist/src/clustering/hierarchical.d.ts.map +1 -0
  99. package/dist/src/clustering/hierarchical.js +439 -0
  100. package/dist/src/clustering/hierarchical.js.map +1 -0
  101. package/dist/src/clustering/index.d.ts +7 -0
  102. package/dist/src/clustering/index.d.ts.map +1 -0
  103. package/dist/src/clustering/index.js +5 -0
  104. package/dist/src/clustering/index.js.map +1 -0
  105. package/dist/src/clustering/k-core.d.ts +63 -0
  106. package/dist/src/clustering/k-core.d.ts.map +1 -0
  107. package/dist/src/clustering/k-core.js +377 -0
  108. package/dist/src/clustering/k-core.js.map +1 -0
  109. package/dist/src/clustering/mcl.d.ts +36 -0
  110. package/dist/src/clustering/mcl.d.ts.map +1 -0
  111. package/dist/src/clustering/mcl.js +343 -0
  112. package/dist/src/clustering/mcl.js.map +1 -0
  113. package/dist/src/clustering/spectral.d.ts +28 -0
  114. package/dist/src/clustering/spectral.d.ts.map +1 -0
  115. package/dist/src/clustering/spectral.js +551 -0
  116. package/dist/src/clustering/spectral.js.map +1 -0
  117. package/dist/src/core/graph.d.ts +9 -0
  118. package/dist/src/core/graph.d.ts.map +1 -1
  119. package/dist/src/core/graph.js +31 -11
  120. package/dist/src/core/graph.js.map +1 -1
  121. package/dist/src/data-structures/priority-queue.js +0 -2
  122. package/dist/src/data-structures/priority-queue.js.map +1 -1
  123. package/dist/src/data-structures/union-find.js +2 -5
  124. package/dist/src/data-structures/union-find.js.map +1 -1
  125. package/dist/src/flow/ford-fulkerson.d.ts +53 -0
  126. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
  127. package/dist/src/flow/ford-fulkerson.js +355 -0
  128. package/dist/src/flow/ford-fulkerson.js.map +1 -0
  129. package/dist/src/flow/index.d.ts +3 -0
  130. package/dist/src/flow/index.d.ts.map +1 -0
  131. package/dist/src/flow/index.js +3 -0
  132. package/dist/src/flow/index.js.map +1 -0
  133. package/dist/src/flow/min-cut.d.ts +49 -0
  134. package/dist/src/flow/min-cut.d.ts.map +1 -0
  135. package/dist/src/flow/min-cut.js +383 -0
  136. package/dist/src/flow/min-cut.js.map +1 -0
  137. package/dist/src/index.d.ts +2 -1
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +2 -0
  140. package/dist/src/index.js.map +1 -1
  141. package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
  142. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
  143. package/dist/src/link-prediction/adamic-adar.js +256 -0
  144. package/dist/src/link-prediction/adamic-adar.js.map +1 -0
  145. package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
  146. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
  147. package/dist/src/link-prediction/common-neighbors.js +155 -0
  148. package/dist/src/link-prediction/common-neighbors.js.map +1 -0
  149. package/dist/src/link-prediction/index.d.ts +7 -0
  150. package/dist/src/link-prediction/index.d.ts.map +1 -0
  151. package/dist/src/link-prediction/index.js +6 -0
  152. package/dist/src/link-prediction/index.js.map +1 -0
  153. package/dist/src/pathfinding/astar.d.ts +50 -0
  154. package/dist/src/pathfinding/astar.d.ts.map +1 -0
  155. package/dist/src/pathfinding/astar.js +180 -0
  156. package/dist/src/pathfinding/astar.js.map +1 -0
  157. package/dist/src/pathfinding/index.d.ts +3 -0
  158. package/dist/src/pathfinding/index.d.ts.map +1 -0
  159. package/dist/src/pathfinding/index.js +3 -0
  160. package/dist/src/pathfinding/index.js.map +1 -0
  161. package/dist/src/pathfinding/utils.d.ts +18 -0
  162. package/dist/src/pathfinding/utils.d.ts.map +1 -0
  163. package/dist/src/pathfinding/utils.js +68 -0
  164. package/dist/src/pathfinding/utils.js.map +1 -0
  165. package/dist/src/research/grsbm.d.ts +83 -0
  166. package/dist/src/research/grsbm.d.ts.map +1 -0
  167. package/dist/src/research/grsbm.js +411 -0
  168. package/dist/src/research/grsbm.js.map +1 -0
  169. package/dist/src/research/index.d.ts +12 -0
  170. package/dist/src/research/index.d.ts.map +1 -0
  171. package/dist/src/research/index.js +14 -0
  172. package/dist/src/research/index.js.map +1 -0
  173. package/dist/src/research/sync.d.ts +49 -0
  174. package/dist/src/research/sync.d.ts.map +1 -0
  175. package/dist/src/research/sync.js +342 -0
  176. package/dist/src/research/sync.js.map +1 -0
  177. package/dist/src/research/terahac.d.ts +63 -0
  178. package/dist/src/research/terahac.d.ts.map +1 -0
  179. package/dist/src/research/terahac.js +375 -0
  180. package/dist/src/research/terahac.js.map +1 -0
  181. package/dist/src/types/index.d.ts +9 -0
  182. package/dist/src/types/index.d.ts.map +1 -1
  183. package/dist/src/utils/graphNode.d.ts +8 -0
  184. package/dist/src/utils/graphNode.d.ts.map +1 -0
  185. package/dist/src/utils/graphNode.js +2 -0
  186. package/dist/src/utils/graphNode.js.map +1 -0
  187. package/dist/src/utils/priorityQueue.d.ts +21 -0
  188. package/dist/src/utils/priorityQueue.d.ts.map +1 -0
  189. package/dist/src/utils/priorityQueue.js +101 -0
  190. package/dist/src/utils/priorityQueue.js.map +1 -0
  191. package/package.json +13 -2
  192. package/src/algorithms/centrality/betweenness.ts +25 -7
  193. package/src/algorithms/centrality/closeness.ts +7 -7
  194. package/src/algorithms/centrality/degree.ts +1 -1
  195. package/src/algorithms/centrality/eigenvector.ts +162 -0
  196. package/src/algorithms/centrality/hits.ts +210 -0
  197. package/src/algorithms/centrality/index.ts +6 -0
  198. package/src/algorithms/centrality/katz.ts +139 -0
  199. package/src/algorithms/centrality/pagerank.ts +3 -3
  200. package/src/algorithms/community/girvan-newman.ts +372 -0
  201. package/src/algorithms/community/index.ts +13 -0
  202. package/src/algorithms/community/label-propagation.ts +392 -0
  203. package/src/algorithms/community/leiden.ts +572 -0
  204. package/src/algorithms/community/louvain.ts +298 -0
  205. package/src/algorithms/components/connected.ts +7 -7
  206. package/src/algorithms/index.ts +21 -0
  207. package/src/algorithms/matching/bipartite.ts +179 -0
  208. package/src/algorithms/matching/index.ts +8 -0
  209. package/src/algorithms/matching/isomorphism.ts +392 -0
  210. package/src/algorithms/mst/index.ts +4 -0
  211. package/src/algorithms/mst/kruskal.ts +61 -0
  212. package/src/algorithms/mst/prim.ts +80 -0
  213. package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
  214. package/src/algorithms/shortest-path/dijkstra.ts +5 -5
  215. package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
  216. package/src/algorithms/shortest-path/index.ts +2 -0
  217. package/src/algorithms/traversal/bfs.ts +2 -2
  218. package/src/algorithms/traversal/dfs.ts +11 -11
  219. package/src/clustering/hierarchical.ts +555 -0
  220. package/src/clustering/index.ts +6 -0
  221. package/src/clustering/k-core.ts +460 -0
  222. package/src/clustering/mcl.ts +454 -0
  223. package/src/clustering/spectral.ts +672 -0
  224. package/src/core/graph.ts +34 -6
  225. package/src/data-structures/union-find.ts +2 -2
  226. package/src/flow/ford-fulkerson.ts +459 -0
  227. package/src/flow/index.ts +2 -0
  228. package/src/flow/min-cut.ts +482 -0
  229. package/src/index.ts +5 -0
  230. package/src/link-prediction/adamic-adar.ts +353 -0
  231. package/src/link-prediction/common-neighbors.ts +252 -0
  232. package/src/link-prediction/index.ts +20 -0
  233. package/src/pathfinding/astar.ts +229 -0
  234. package/src/pathfinding/index.ts +2 -0
  235. package/src/pathfinding/utils.ts +87 -0
  236. package/src/research/grsbm.ts +589 -0
  237. package/src/research/index.ts +17 -0
  238. package/src/research/sync.ts +469 -0
  239. package/src/research/terahac.ts +506 -0
  240. package/src/types/index.ts +11 -0
  241. package/src/utils/graphNode.ts +7 -0
  242. package/src/utils/priorityQueue.ts +120 -0
  243. package/dist/test/browser/basic.test.d.ts +0 -2
  244. package/dist/test/browser/basic.test.d.ts.map +0 -1
  245. package/dist/test/browser/basic.test.js +0 -70
  246. package/dist/test/browser/basic.test.js.map +0 -1
  247. package/dist/test/unit/bellman-ford.test.d.ts +0 -2
  248. package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
  249. package/dist/test/unit/bellman-ford.test.js +0 -231
  250. package/dist/test/unit/bellman-ford.test.js.map +0 -1
  251. package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
  252. package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
  253. package/dist/test/unit/betweenness-centrality.test.js +0 -311
  254. package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
  255. package/dist/test/unit/bfs.test.d.ts +0 -2
  256. package/dist/test/unit/bfs.test.d.ts.map +0 -1
  257. package/dist/test/unit/bfs.test.js +0 -277
  258. package/dist/test/unit/bfs.test.js.map +0 -1
  259. package/dist/test/unit/closeness.test.d.ts +0 -2
  260. package/dist/test/unit/closeness.test.d.ts.map +0 -1
  261. package/dist/test/unit/closeness.test.js +0 -266
  262. package/dist/test/unit/closeness.test.js.map +0 -1
  263. package/dist/test/unit/connected-components.test.d.ts +0 -2
  264. package/dist/test/unit/connected-components.test.d.ts.map +0 -1
  265. package/dist/test/unit/connected-components.test.js +0 -355
  266. package/dist/test/unit/connected-components.test.js.map +0 -1
  267. package/dist/test/unit/degree-centrality.test.d.ts +0 -2
  268. package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
  269. package/dist/test/unit/degree-centrality.test.js +0 -260
  270. package/dist/test/unit/degree-centrality.test.js.map +0 -1
  271. package/dist/test/unit/dfs.test.d.ts +0 -2
  272. package/dist/test/unit/dfs.test.d.ts.map +0 -1
  273. package/dist/test/unit/dfs.test.js +0 -350
  274. package/dist/test/unit/dfs.test.js.map +0 -1
  275. package/dist/test/unit/dijkstra.test.d.ts +0 -2
  276. package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
  277. package/dist/test/unit/dijkstra.test.js +0 -311
  278. package/dist/test/unit/dijkstra.test.js.map +0 -1
  279. package/dist/test/unit/graph.test.d.ts +0 -2
  280. package/dist/test/unit/graph.test.d.ts.map +0 -1
  281. package/dist/test/unit/graph.test.js +0 -175
  282. package/dist/test/unit/graph.test.js.map +0 -1
  283. package/dist/test/unit/pagerank.test.d.ts +0 -2
  284. package/dist/test/unit/pagerank.test.d.ts.map +0 -1
  285. package/dist/test/unit/pagerank.test.js +0 -281
  286. package/dist/test/unit/pagerank.test.js.map +0 -1
  287. package/dist/test/unit/priority-queue.test.d.ts +0 -2
  288. package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
  289. package/dist/test/unit/priority-queue.test.js +0 -189
  290. package/dist/test/unit/priority-queue.test.js.map +0 -1
  291. package/dist/test/unit/union-find.test.d.ts +0 -2
  292. package/dist/test/unit/union-find.test.d.ts.map +0 -1
  293. package/dist/test/unit/union-find.test.js +0 -245
  294. package/dist/test/unit/union-find.test.js.map +0 -1
@@ -111,7 +111,7 @@ export function pageRank(
111
111
  let outDegree = 0;
112
112
  let totalOutWeight = 0;
113
113
 
114
- for (const neighbor of graph.neighbors(nodeId)) {
114
+ for (const neighbor of Array.from(graph.neighbors(nodeId))) {
115
115
  outDegree++;
116
116
  if (weight) {
117
117
  const edge = graph.getEdge(nodeId, neighbor);
@@ -178,7 +178,7 @@ export function pageRank(
178
178
  const nodeOutWeight = outWeights.get(nodeId) ?? 0;
179
179
 
180
180
  if (nodeOutWeight > 0) {
181
- for (const neighbor of graph.neighbors(nodeId)) {
181
+ for (const neighbor of Array.from(graph.neighbors(nodeId))) {
182
182
  let edgeWeight = 1;
183
183
  if (weight) {
184
184
  const edge = graph.getEdge(nodeId, neighbor);
@@ -295,7 +295,7 @@ function normalizeRanks(ranks: Map<NodeId, number>): void {
295
295
  }
296
296
 
297
297
  if (sum > 0) {
298
- for (const [nodeId, rank] of ranks) {
298
+ for (const [nodeId, rank] of Array.from(ranks)) {
299
299
  ranks.set(nodeId, rank / sum);
300
300
  }
301
301
  }
@@ -0,0 +1,372 @@
1
+ import {Graph} from "../../core/graph.js";
2
+ import type {CommunityResult, ComponentResult, GirvanNewmanOptions, NodeId} from "../../types/index.js";
3
+ import {connectedComponents} from "../components/connected.js";
4
+
5
+ /**
6
+ * Girvan-Newman community detection algorithm
7
+ *
8
+ * Implements the Girvan-Newman method for community detection by iteratively
9
+ * removing edges with the highest betweenness centrality until the graph
10
+ * splits into disconnected components.
11
+ *
12
+ * This is a divisive hierarchical clustering algorithm that produces a
13
+ * dendrogram of community structures.
14
+ *
15
+ * References:
16
+ * - Girvan, M., & Newman, M. E. J. (2002). Community structure in social
17
+ * and biological networks. Proceedings of the National Academy of Sciences,
18
+ * 99(12), 7821-7826.
19
+ *
20
+ * @param graph - The input graph
21
+ * @param options - Algorithm options
22
+ * @returns Array of community detection results representing the dendrogram
23
+ */
24
+ export function girvanNewman(
25
+ graph: Graph,
26
+ options: GirvanNewmanOptions = {},
27
+ ): CommunityResult[] {
28
+ const {maxCommunities} = options;
29
+ const minCommunitySize = options.minCommunitySize ?? 1;
30
+
31
+ const dendrogram: CommunityResult[] = [];
32
+ const workingGraph = cloneGraph(graph);
33
+
34
+ // Initial state: one large component
35
+ let components = getConnectedComponentsResult(workingGraph);
36
+ dendrogram.push({
37
+ communities: components.components.filter((community) => community.length >= minCommunitySize),
38
+ modularity: calculateModularity(graph, components.componentMap),
39
+ });
40
+
41
+ while (Array.from(workingGraph.edges()).length > 0) {
42
+ // Calculate edge betweenness centrality
43
+ const edgeBetweenness = calculateEdgeBetweenness(workingGraph);
44
+
45
+ if (edgeBetweenness.size === 0) {
46
+ break;
47
+ }
48
+
49
+ // Find edges with maximum betweenness
50
+ const maxBetweenness = Math.max(... edgeBetweenness.values());
51
+ const edgesToRemove: {source: NodeId, target: NodeId}[] = [];
52
+
53
+ for (const [edgeKey, centrality] of edgeBetweenness) {
54
+ if (Math.abs(centrality - maxBetweenness) < 1e-10) {
55
+ const [source, target] = edgeKey.split("|");
56
+ if (source && target) {
57
+ edgesToRemove.push({source, target});
58
+ }
59
+ }
60
+ }
61
+
62
+ // Remove edges with highest betweenness
63
+ for (const {source, target} of edgesToRemove) {
64
+ workingGraph.removeEdge(source, target);
65
+ }
66
+
67
+ // Find new connected components (communities)
68
+ components = getConnectedComponentsResult(workingGraph);
69
+
70
+ // Filter communities by minimum size
71
+ const validCommunities = components.components.filter(
72
+ (community) => community.length >= minCommunitySize,
73
+ );
74
+
75
+ // Calculate modularity for the new community structure
76
+ const modularity = calculateModularity(graph, components.componentMap);
77
+
78
+ dendrogram.push({
79
+ communities: validCommunities,
80
+ modularity,
81
+ });
82
+
83
+ // Stop if desired number of communities reached
84
+ if (maxCommunities && validCommunities.length >= maxCommunities) {
85
+ break;
86
+ }
87
+
88
+ // Stop if no more meaningful communities can be formed
89
+ if (validCommunities.length === workingGraph.nodeCount) {
90
+ break;
91
+ }
92
+ }
93
+
94
+ return dendrogram;
95
+ }
96
+
97
+ /**
98
+ * Calculate edge betweenness centrality for all edges in the graph
99
+ *
100
+ * Edge betweenness is the fraction of shortest paths that pass through the edge.
101
+ * We adapt node betweenness centrality calculation to work with edges.
102
+ */
103
+ function calculateEdgeBetweenness(graph: Graph): Map<string, number> {
104
+ const edgeBetweenness = new Map<string, number>();
105
+
106
+ // Initialize all edges with 0 betweenness
107
+ for (const edge of graph.edges()) {
108
+ const edgeKey = getEdgeKey(edge.source, edge.target);
109
+ edgeBetweenness.set(edgeKey, 0);
110
+ }
111
+
112
+ // For each node as source, calculate shortest paths and accumulate edge betweenness
113
+ for (const sourceNode of graph.nodes()) {
114
+ const source = sourceNode.id;
115
+
116
+ // Run BFS to find all shortest paths from source
117
+ const {distances, predecessors, pathCounts} = findAllShortestPaths(graph, source);
118
+
119
+ // Calculate dependency for each node and accumulate edge betweenness
120
+ const dependency = new Map<NodeId, number>();
121
+
122
+ // Initialize dependency
123
+ for (const node of graph.nodes()) {
124
+ dependency.set(node.id, 0);
125
+ }
126
+
127
+ // Process nodes in order of decreasing distance
128
+ const sortedNodes = Array.from(distances.keys())
129
+ .filter((node) => {
130
+ const distance = distances.get(node);
131
+ return distance !== undefined && distance < Infinity;
132
+ })
133
+ .sort((a, b) => {
134
+ const distanceA = distances.get(a);
135
+ const distanceB = distances.get(b);
136
+ if (distanceA === undefined || distanceB === undefined) {
137
+ return 0;
138
+ }
139
+
140
+ return distanceB - distanceA;
141
+ });
142
+
143
+ for (const node of sortedNodes) {
144
+ if (node === source) {
145
+ continue;
146
+ }
147
+
148
+ const nodeDependency = dependency.get(node);
149
+ const nodePathCount = pathCounts.get(node);
150
+
151
+ if (nodeDependency === undefined || nodePathCount === undefined) {
152
+ continue;
153
+ }
154
+
155
+ // Distribute dependency to predecessors
156
+ const nodePredecessors = predecessors.get(node) ?? [];
157
+ for (const predecessor of nodePredecessors) {
158
+ const predPathCount = pathCounts.get(predecessor);
159
+ if (predPathCount === undefined) {
160
+ continue;
161
+ }
162
+
163
+ const edgeDependency = (predPathCount / nodePathCount) * (1 + nodeDependency);
164
+
165
+ // Update predecessor dependency
166
+ const currentDependency = dependency.get(predecessor);
167
+ if (currentDependency !== undefined) {
168
+ dependency.set(predecessor, currentDependency + edgeDependency);
169
+ }
170
+
171
+ // Update edge betweenness
172
+ const edgeKey = getEdgeKey(predecessor, node);
173
+ const currentBetweenness = edgeBetweenness.get(edgeKey) ?? 0;
174
+ edgeBetweenness.set(edgeKey, currentBetweenness + edgeDependency);
175
+ }
176
+ }
177
+ }
178
+
179
+ // Normalize: divide by 2 for undirected graphs (each edge counted twice)
180
+ if (!graph.isDirected) {
181
+ for (const [edgeKey, betweenness] of edgeBetweenness) {
182
+ edgeBetweenness.set(edgeKey, betweenness / 2);
183
+ }
184
+ }
185
+
186
+ return edgeBetweenness;
187
+ }
188
+
189
+ /**
190
+ * Find all shortest paths from a source node using BFS
191
+ */
192
+ function findAllShortestPaths(graph: Graph, source: NodeId): {
193
+ distances: Map<NodeId, number>;
194
+ predecessors: Map<NodeId, NodeId[]>;
195
+ pathCounts: Map<NodeId, number>;
196
+ } {
197
+ const distances = new Map<NodeId, number>();
198
+ const predecessors = new Map<NodeId, NodeId[]>();
199
+ const pathCounts = new Map<NodeId, number>();
200
+ const queue: NodeId[] = [];
201
+
202
+ // Initialize
203
+ for (const node of graph.nodes()) {
204
+ distances.set(node.id, Infinity);
205
+ predecessors.set(node.id, []);
206
+ pathCounts.set(node.id, 0);
207
+ }
208
+
209
+ distances.set(source, 0);
210
+ pathCounts.set(source, 1);
211
+ queue.push(source);
212
+
213
+ // BFS
214
+ while (queue.length > 0) {
215
+ const current = queue.shift();
216
+ if (current === undefined) {
217
+ break;
218
+ }
219
+
220
+ const currentDistance = distances.get(current);
221
+ if (currentDistance === undefined) {
222
+ continue;
223
+ }
224
+
225
+ for (const neighbor of graph.neighbors(current)) {
226
+ const edge = graph.getEdge(current, neighbor);
227
+ const edgeWeight = edge?.weight ?? 1;
228
+ const newDistance = currentDistance + edgeWeight;
229
+ const neighborDistance = distances.get(neighbor);
230
+ if (neighborDistance === undefined) {
231
+ continue;
232
+ }
233
+
234
+ if (newDistance < neighborDistance) {
235
+ // Found shorter path
236
+ distances.set(neighbor, newDistance);
237
+ predecessors.set(neighbor, [current]);
238
+ const currentPathCount = pathCounts.get(current);
239
+ if (currentPathCount !== undefined) {
240
+ pathCounts.set(neighbor, currentPathCount);
241
+ }
242
+
243
+ queue.push(neighbor);
244
+ } else if (Math.abs(newDistance - neighborDistance) < 1e-10) {
245
+ // Found alternative shortest path
246
+ const neighborPredecessors = predecessors.get(neighbor);
247
+ const neighborPathCount = pathCounts.get(neighbor);
248
+ const currentPathCount = pathCounts.get(current);
249
+
250
+ if (neighborPredecessors && neighborPathCount !== undefined && currentPathCount !== undefined) {
251
+ neighborPredecessors.push(current);
252
+ pathCounts.set(neighbor, neighborPathCount + currentPathCount);
253
+ }
254
+ }
255
+ }
256
+ }
257
+
258
+ return {distances, predecessors, pathCounts};
259
+ }
260
+
261
+ /**
262
+ * Generate a consistent edge key for undirected graphs
263
+ */
264
+ function getEdgeKey(source: NodeId, target: NodeId): string {
265
+ // For undirected graphs, ensure consistent ordering
266
+ const sourceStr = String(source);
267
+ const targetStr = String(target);
268
+
269
+ if (sourceStr <= targetStr) {
270
+ return `${sourceStr}|${targetStr}`;
271
+ }
272
+
273
+ return `${targetStr}|${sourceStr}`;
274
+ }
275
+
276
+ /**
277
+ * Calculate modularity for a given community structure
278
+ */
279
+ function calculateModularity(
280
+ graph: Graph,
281
+ communityMap: Map<NodeId, number>,
282
+ ): number {
283
+ const totalEdgeWeight = getTotalEdgeWeight(graph);
284
+ if (totalEdgeWeight === 0) {
285
+ return 0;
286
+ }
287
+
288
+ let modularity = 0;
289
+
290
+ // Calculate modularity: Q = (1/2m) * Σ[A_ij - (k_i * k_j)/(2m)] * δ(c_i, c_j)
291
+ for (const nodeI of graph.nodes()) {
292
+ for (const nodeJ of graph.nodes()) {
293
+ if (communityMap.get(nodeI.id) === communityMap.get(nodeJ.id)) {
294
+ const edge = graph.getEdge(nodeI.id, nodeJ.id);
295
+ const edgeWeight = edge?.weight ?? 0;
296
+
297
+ const degreeI = getNodeDegree(graph, nodeI.id);
298
+ const degreeJ = getNodeDegree(graph, nodeJ.id);
299
+
300
+ modularity += edgeWeight - ((degreeI * degreeJ) / (2 * totalEdgeWeight));
301
+ }
302
+ }
303
+ }
304
+
305
+ return modularity / (2 * totalEdgeWeight);
306
+ }
307
+
308
+ /**
309
+ * Calculate total edge weight in the graph
310
+ */
311
+ function getTotalEdgeWeight(graph: Graph): number {
312
+ let totalWeight = 0;
313
+
314
+ for (const edge of graph.edges()) {
315
+ totalWeight += edge.weight ?? 1;
316
+ }
317
+
318
+ return totalWeight;
319
+ }
320
+
321
+ /**
322
+ * Get the total degree (sum of edge weights) for a node
323
+ */
324
+ function getNodeDegree(graph: Graph, nodeId: NodeId): number {
325
+ let degree = 0;
326
+
327
+ for (const neighbor of graph.neighbors(nodeId)) {
328
+ const edge = graph.getEdge(nodeId, neighbor);
329
+ degree += edge?.weight ?? 1;
330
+ }
331
+
332
+ return degree;
333
+ }
334
+
335
+ /**
336
+ * Convert connected components result to ComponentResult format
337
+ */
338
+ function getConnectedComponentsResult(graph: Graph): ComponentResult {
339
+ const components = connectedComponents(graph);
340
+ const componentMap = new Map<NodeId, number>();
341
+
342
+ components.forEach((component, index) => {
343
+ component.forEach((nodeId) => {
344
+ componentMap.set(nodeId, index);
345
+ });
346
+ });
347
+
348
+ return {components, componentMap};
349
+ }
350
+
351
+ /**
352
+ * Create a deep clone of the graph
353
+ */
354
+ function cloneGraph(graph: Graph): Graph {
355
+ const clone = new Graph({
356
+ directed: graph.isDirected,
357
+ allowSelfLoops: true,
358
+ allowParallelEdges: false,
359
+ });
360
+
361
+ // Add all nodes
362
+ for (const node of graph.nodes()) {
363
+ clone.addNode(node.id, node.data);
364
+ }
365
+
366
+ // Add all edges
367
+ for (const edge of graph.edges()) {
368
+ clone.addEdge(edge.source, edge.target, edge.weight, edge.data);
369
+ }
370
+
371
+ return clone;
372
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Community Detection Algorithms
3
+ *
4
+ * This module provides algorithms for detecting community structure in graphs.
5
+ * Communities are groups of nodes that are more densely connected internally
6
+ * than with the rest of the network.
7
+ */
8
+
9
+ export {girvanNewman} from "./girvan-newman.js";
10
+ export {labelPropagation, labelPropagationAsync, labelPropagationSemiSupervised} from "./label-propagation.js";
11
+ export {leiden} from "./leiden.js";
12
+ export {louvain} from "./louvain.js";
13
+