@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.
Files changed (337) hide show
  1. package/README.md +375 -276
  2. package/dist/algorithms.d.ts +42 -2
  3. package/dist/algorithms.js +11402 -2
  4. package/dist/algorithms.js.map +1 -1
  5. package/dist/src/algorithms/centrality/betweenness.d.ts +14 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +144 -169
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.d.ts +35 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  11. package/dist/src/algorithms/centrality/closeness.js +70 -149
  12. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  13. package/dist/src/algorithms/centrality/degree.d.ts +7 -0
  14. package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
  15. package/dist/src/algorithms/centrality/degree.js +12 -9
  16. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  17. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +57 -0
  18. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
  19. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +304 -0
  20. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
  21. package/dist/src/algorithms/centrality/delta-pagerank.d.ts +106 -0
  22. package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
  23. package/dist/src/algorithms/centrality/delta-pagerank.js +320 -0
  24. package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
  25. package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -3
  26. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
  27. package/dist/src/algorithms/centrality/eigenvector.js +11 -4
  28. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
  29. package/dist/src/algorithms/centrality/hits.d.ts +10 -3
  30. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
  31. package/dist/src/algorithms/centrality/hits.js +11 -4
  32. package/dist/src/algorithms/centrality/hits.js.map +1 -1
  33. package/dist/src/algorithms/centrality/index.d.ts +3 -1
  34. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  35. package/dist/src/algorithms/centrality/index.js +2 -1
  36. package/dist/src/algorithms/centrality/index.js.map +1 -1
  37. package/dist/src/algorithms/centrality/katz.d.ts +10 -3
  38. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
  39. package/dist/src/algorithms/centrality/katz.js +13 -8
  40. package/dist/src/algorithms/centrality/katz.js.map +1 -1
  41. package/dist/src/algorithms/centrality/pagerank.d.ts +31 -0
  42. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  43. package/dist/src/algorithms/centrality/pagerank.js +61 -1
  44. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  45. package/dist/src/algorithms/community/girvan-newman.d.ts +2 -3
  46. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  47. package/dist/src/algorithms/community/girvan-newman.js +67 -57
  48. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  49. package/dist/src/algorithms/community/index.d.ts +2 -0
  50. package/dist/src/algorithms/community/index.d.ts.map +1 -1
  51. package/dist/src/algorithms/community/index.js.map +1 -1
  52. package/dist/src/algorithms/community/label-propagation.d.ts +11 -4
  53. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  54. package/dist/src/algorithms/community/label-propagation.js +60 -38
  55. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  56. package/dist/src/algorithms/community/leiden.d.ts +3 -3
  57. package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
  58. package/dist/src/algorithms/community/leiden.js +70 -44
  59. package/dist/src/algorithms/community/leiden.js.map +1 -1
  60. package/dist/src/algorithms/community/louvain-optimized.d.ts +149 -0
  61. package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
  62. package/dist/src/algorithms/community/louvain-optimized.js +338 -0
  63. package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
  64. package/dist/src/algorithms/community/louvain.d.ts +2 -3
  65. package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
  66. package/dist/src/algorithms/community/louvain.js +35 -81
  67. package/dist/src/algorithms/community/louvain.js.map +1 -1
  68. package/dist/src/algorithms/community/modularity-utils.d.ts +55 -0
  69. package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
  70. package/dist/src/algorithms/community/modularity-utils.js +108 -0
  71. package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
  72. package/dist/src/algorithms/components/connected.d.ts +23 -0
  73. package/dist/src/algorithms/components/connected.d.ts.map +1 -1
  74. package/dist/src/algorithms/components/connected.js +29 -3
  75. package/dist/src/algorithms/components/connected.js.map +1 -1
  76. package/dist/src/algorithms/matching/bipartite.d.ts +10 -0
  77. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
  78. package/dist/src/algorithms/matching/bipartite.js +18 -40
  79. package/dist/src/algorithms/matching/bipartite.js.map +1 -1
  80. package/dist/src/algorithms/matching/index.d.ts.map +1 -1
  81. package/dist/src/algorithms/matching/index.js.map +1 -1
  82. package/dist/src/algorithms/matching/isomorphism.d.ts +8 -0
  83. package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -1
  84. package/dist/src/algorithms/matching/isomorphism.js +36 -2
  85. package/dist/src/algorithms/matching/isomorphism.js.map +1 -1
  86. package/dist/src/algorithms/mst/index.d.ts.map +1 -1
  87. package/dist/src/algorithms/mst/index.js.map +1 -1
  88. package/dist/src/algorithms/mst/kruskal.d.ts +14 -0
  89. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
  90. package/dist/src/algorithms/mst/kruskal.js +17 -3
  91. package/dist/src/algorithms/mst/kruskal.js.map +1 -1
  92. package/dist/src/algorithms/mst/prim.d.ts +9 -0
  93. package/dist/src/algorithms/mst/prim.d.ts.map +1 -1
  94. package/dist/src/algorithms/mst/prim.js +9 -0
  95. package/dist/src/algorithms/mst/prim.js.map +1 -1
  96. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts +10 -0
  97. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  98. package/dist/src/algorithms/shortest-path/bellman-ford.js +11 -12
  99. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  100. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +44 -0
  101. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
  102. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +195 -0
  103. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
  104. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +20 -2
  105. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  106. package/dist/src/algorithms/shortest-path/dijkstra.js +30 -14
  107. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  108. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +17 -0
  109. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
  110. package/dist/src/algorithms/shortest-path/floyd-warshall.js +17 -0
  111. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
  112. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
  113. package/dist/src/algorithms/shortest-path/index.js.map +1 -1
  114. package/dist/src/algorithms/traversal/bfs-unified.d.ts +42 -0
  115. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
  116. package/dist/src/algorithms/traversal/bfs-unified.js +370 -0
  117. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
  118. package/dist/src/algorithms/traversal/bfs-variants.d.ts +77 -0
  119. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
  120. package/dist/src/algorithms/traversal/bfs-variants.js +402 -0
  121. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
  122. package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
  123. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  124. package/dist/src/algorithms/traversal/bfs.js +5 -182
  125. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  126. package/dist/src/algorithms/traversal/dfs.d.ts +10 -0
  127. package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
  128. package/dist/src/algorithms/traversal/dfs.js +48 -0
  129. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  130. package/dist/src/algorithms/traversal/index.d.ts.map +1 -1
  131. package/dist/src/algorithms/traversal/index.js.map +1 -1
  132. package/dist/src/benchmark-all-algorithms.d.ts +25 -0
  133. package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
  134. package/dist/src/benchmark-all-algorithms.js +491 -0
  135. package/dist/src/benchmark-all-algorithms.js.map +1 -0
  136. package/dist/src/clustering/hierarchical.d.ts +23 -15
  137. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  138. package/dist/src/clustering/hierarchical.js +226 -224
  139. package/dist/src/clustering/hierarchical.js.map +1 -1
  140. package/dist/src/clustering/index.d.ts +4 -2
  141. package/dist/src/clustering/index.d.ts.map +1 -1
  142. package/dist/src/clustering/index.js +2 -2
  143. package/dist/src/clustering/index.js.map +1 -1
  144. package/dist/src/clustering/k-core.d.ts +17 -19
  145. package/dist/src/clustering/k-core.d.ts.map +1 -1
  146. package/dist/src/clustering/k-core.js +68 -30
  147. package/dist/src/clustering/k-core.js.map +1 -1
  148. package/dist/src/clustering/mcl.d.ts +6 -0
  149. package/dist/src/clustering/mcl.d.ts.map +1 -1
  150. package/dist/src/clustering/mcl.js +34 -3
  151. package/dist/src/clustering/mcl.js.map +1 -1
  152. package/dist/src/clustering/spectral.d.ts +11 -0
  153. package/dist/src/clustering/spectral.d.ts.map +1 -1
  154. package/dist/src/clustering/spectral.js +99 -47
  155. package/dist/src/clustering/spectral.js.map +1 -1
  156. package/dist/src/core/graph.d.ts +45 -0
  157. package/dist/src/core/graph.d.ts.map +1 -1
  158. package/dist/src/core/graph.js +49 -5
  159. package/dist/src/core/graph.js.map +1 -1
  160. package/dist/src/data-structures/index.d.ts.map +1 -1
  161. package/dist/src/data-structures/index.js.map +1 -1
  162. package/dist/src/data-structures/priority-queue.d.ts +18 -1
  163. package/dist/src/data-structures/priority-queue.d.ts.map +1 -1
  164. package/dist/src/data-structures/priority-queue.js +26 -5
  165. package/dist/src/data-structures/priority-queue.js.map +1 -1
  166. package/dist/src/data-structures/union-find.d.ts +21 -0
  167. package/dist/src/data-structures/union-find.d.ts.map +1 -1
  168. package/dist/src/data-structures/union-find.js +21 -0
  169. package/dist/src/data-structures/union-find.js.map +1 -1
  170. package/dist/src/flow/ford-fulkerson.d.ts +20 -12
  171. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
  172. package/dist/src/flow/ford-fulkerson.js +170 -217
  173. package/dist/src/flow/ford-fulkerson.js.map +1 -1
  174. package/dist/src/flow/min-cut.d.ts +6 -8
  175. package/dist/src/flow/min-cut.d.ts.map +1 -1
  176. package/dist/src/flow/min-cut.js +34 -12
  177. package/dist/src/flow/min-cut.js.map +1 -1
  178. package/dist/src/index.d.ts +3 -1
  179. package/dist/src/index.d.ts.map +1 -1
  180. package/dist/src/index.js +6 -1
  181. package/dist/src/index.js.map +1 -1
  182. package/dist/src/link-prediction/adamic-adar.d.ts +26 -0
  183. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
  184. package/dist/src/link-prediction/adamic-adar.js +47 -30
  185. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  186. package/dist/src/link-prediction/common-neighbors.d.ts +21 -0
  187. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  188. package/dist/src/link-prediction/common-neighbors.js +31 -14
  189. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  190. package/dist/src/link-prediction/index.d.ts.map +1 -1
  191. package/dist/src/optimized/bit-packed.d.ts +193 -0
  192. package/dist/src/optimized/bit-packed.d.ts.map +1 -0
  193. package/dist/src/optimized/bit-packed.js +342 -0
  194. package/dist/src/optimized/bit-packed.js.map +1 -0
  195. package/dist/src/optimized/csr-graph.d.ts +149 -0
  196. package/dist/src/optimized/csr-graph.d.ts.map +1 -0
  197. package/dist/src/optimized/csr-graph.js +404 -0
  198. package/dist/src/optimized/csr-graph.js.map +1 -0
  199. package/dist/src/optimized/direction-optimized-bfs.d.ts +95 -0
  200. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
  201. package/dist/src/optimized/direction-optimized-bfs.js +242 -0
  202. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
  203. package/dist/src/optimized/graph-adapter.d.ts +111 -0
  204. package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
  205. package/dist/src/optimized/graph-adapter.js +194 -0
  206. package/dist/src/optimized/graph-adapter.js.map +1 -0
  207. package/dist/src/optimized/index.d.ts +14 -0
  208. package/dist/src/optimized/index.d.ts.map +1 -0
  209. package/dist/src/optimized/index.js +30 -0
  210. package/dist/src/optimized/index.js.map +1 -0
  211. package/dist/src/pathfinding/astar.d.ts +25 -9
  212. package/dist/src/pathfinding/astar.d.ts.map +1 -1
  213. package/dist/src/pathfinding/astar.js +25 -10
  214. package/dist/src/pathfinding/astar.js.map +1 -1
  215. package/dist/src/pathfinding/utils.d.ts +15 -6
  216. package/dist/src/pathfinding/utils.d.ts.map +1 -1
  217. package/dist/src/pathfinding/utils.js +19 -7
  218. package/dist/src/pathfinding/utils.js.map +1 -1
  219. package/dist/src/research/grsbm.d.ts +0 -1
  220. package/dist/src/research/grsbm.d.ts.map +1 -1
  221. package/dist/src/research/grsbm.js +28 -23
  222. package/dist/src/research/grsbm.js.map +1 -1
  223. package/dist/src/research/index.d.ts.map +1 -1
  224. package/dist/src/research/index.js.map +1 -1
  225. package/dist/src/research/sync.d.ts +0 -1
  226. package/dist/src/research/sync.d.ts.map +1 -1
  227. package/dist/src/research/sync.js +31 -36
  228. package/dist/src/research/sync.js.map +1 -1
  229. package/dist/src/research/terahac.d.ts +2 -1
  230. package/dist/src/research/terahac.d.ts.map +1 -1
  231. package/dist/src/research/terahac.js +39 -7
  232. package/dist/src/research/terahac.js.map +1 -1
  233. package/dist/src/types/index.d.ts +8 -0
  234. package/dist/src/types/index.d.ts.map +1 -1
  235. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  236. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  237. package/dist/src/utils/algorithm-utilities.js +2 -0
  238. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  239. package/dist/src/utils/graph-converters.d.ts +110 -0
  240. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  241. package/dist/src/utils/graph-converters.js +227 -0
  242. package/dist/src/utils/graph-converters.js.map +1 -0
  243. package/dist/src/utils/graph-utilities.d.ts +62 -0
  244. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  245. package/dist/src/utils/graph-utilities.js +148 -0
  246. package/dist/src/utils/graph-utilities.js.map +1 -0
  247. package/dist/src/utils/index.d.ts +5 -0
  248. package/dist/src/utils/index.d.ts.map +1 -0
  249. package/dist/src/utils/index.js +5 -0
  250. package/dist/src/utils/index.js.map +1 -0
  251. package/dist/src/utils/math-utilities.d.ts +63 -0
  252. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  253. package/dist/src/utils/math-utilities.js +132 -0
  254. package/dist/src/utils/math-utilities.js.map +1 -0
  255. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  256. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  257. package/dist/src/utils/matrix-utilities.js +2 -0
  258. package/dist/src/utils/matrix-utilities.js.map +1 -0
  259. package/dist/src/utils/optimization-helpers.d.ts +42 -0
  260. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  261. package/dist/src/utils/optimization-helpers.js +59 -0
  262. package/dist/src/utils/optimization-helpers.js.map +1 -0
  263. package/dist/src/utils/priorityQueue.d.ts +24 -0
  264. package/dist/src/utils/priorityQueue.d.ts.map +1 -1
  265. package/dist/src/utils/priorityQueue.js +34 -4
  266. package/dist/src/utils/priorityQueue.js.map +1 -1
  267. package/package.json +58 -39
  268. package/src/algorithms/centrality/betweenness.ts +185 -200
  269. package/src/algorithms/centrality/closeness.ts +93 -194
  270. package/src/algorithms/centrality/degree.ts +18 -22
  271. package/src/algorithms/centrality/delta-pagerank-simple.ts +374 -0
  272. package/src/algorithms/centrality/delta-pagerank.ts +458 -0
  273. package/src/algorithms/centrality/eigenvector.ts +14 -15
  274. package/src/algorithms/centrality/hits.ts +17 -17
  275. package/src/algorithms/centrality/index.ts +20 -13
  276. package/src/algorithms/centrality/katz.ts +17 -25
  277. package/src/algorithms/centrality/pagerank.ts +91 -16
  278. package/src/algorithms/community/girvan-newman.ts +90 -86
  279. package/src/algorithms/community/index.ts +6 -5
  280. package/src/algorithms/community/label-propagation.ts +69 -48
  281. package/src/algorithms/community/leiden.ts +88 -65
  282. package/src/algorithms/community/louvain-optimized.ts +474 -0
  283. package/src/algorithms/community/louvain.ts +52 -122
  284. package/src/algorithms/community/modularity-utils.ts +134 -0
  285. package/src/algorithms/components/connected.ts +40 -17
  286. package/src/algorithms/matching/bipartite.ts +24 -53
  287. package/src/algorithms/matching/index.ts +4 -4
  288. package/src/algorithms/matching/isomorphism.ts +53 -23
  289. package/src/algorithms/mst/index.ts +3 -4
  290. package/src/algorithms/mst/kruskal.ts +21 -6
  291. package/src/algorithms/mst/prim.ts +13 -4
  292. package/src/algorithms/shortest-path/bellman-ford.ts +16 -28
  293. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  294. package/src/algorithms/shortest-path/dijkstra.ts +38 -30
  295. package/src/algorithms/shortest-path/floyd-warshall.ts +21 -4
  296. package/src/algorithms/shortest-path/index.ts +5 -5
  297. package/src/algorithms/traversal/bfs-unified.ts +437 -0
  298. package/src/algorithms/traversal/bfs-variants.ts +520 -0
  299. package/src/algorithms/traversal/bfs.ts +5 -229
  300. package/src/algorithms/traversal/dfs.ts +61 -42
  301. package/src/algorithms/traversal/index.ts +3 -3
  302. package/src/benchmark-all-algorithms.ts +580 -0
  303. package/src/clustering/hierarchical.ts +268 -272
  304. package/src/clustering/index.ts +8 -6
  305. package/src/clustering/k-core.ts +93 -61
  306. package/src/clustering/mcl.ts +53 -23
  307. package/src/clustering/spectral.ts +130 -72
  308. package/src/core/graph.ts +58 -18
  309. package/src/data-structures/index.ts +2 -2
  310. package/src/data-structures/priority-queue.ts +36 -11
  311. package/src/data-structures/union-find.ts +22 -1
  312. package/src/flow/ford-fulkerson.ts +221 -262
  313. package/src/flow/min-cut.ts +59 -51
  314. package/src/index.ts +9 -2
  315. package/src/link-prediction/adamic-adar.ts +75 -74
  316. package/src/link-prediction/common-neighbors.ts +49 -49
  317. package/src/link-prediction/index.ts +1 -1
  318. package/src/optimized/bit-packed.ts +390 -0
  319. package/src/optimized/csr-graph.ts +518 -0
  320. package/src/optimized/direction-optimized-bfs.ts +310 -0
  321. package/src/optimized/graph-adapter.ts +248 -0
  322. package/src/optimized/index.ts +34 -0
  323. package/src/pathfinding/astar.ts +40 -25
  324. package/src/pathfinding/utils.ts +20 -13
  325. package/src/research/grsbm.ts +36 -39
  326. package/src/research/index.ts +4 -4
  327. package/src/research/sync.ts +41 -54
  328. package/src/research/terahac.ts +63 -21
  329. package/src/types/index.ts +8 -0
  330. package/src/utils/algorithm-utilities.ts +2 -0
  331. package/src/utils/graph-converters.ts +269 -0
  332. package/src/utils/graph-utilities.ts +173 -0
  333. package/src/utils/index.ts +4 -0
  334. package/src/utils/math-utilities.ts +149 -0
  335. package/src/utils/matrix-utilities.ts +2 -0
  336. package/src/utils/optimization-helpers.ts +87 -0
  337. package/src/utils/priorityQueue.ts +40 -6
@@ -0,0 +1,474 @@
1
+ import type { Graph } from "../../core/graph.js";
2
+ import type { CommunityResult, NodeId } from "../../types/index.js";
3
+
4
+ /**
5
+ * Optimized Louvain community detection algorithm with early pruning and threshold cycling
6
+ *
7
+ * Key optimizations:
8
+ * - Leaf node pruning: Skip nodes with degree 1
9
+ * - Importance ordering: Process high-impact nodes first
10
+ * - Threshold cycling: Adaptive convergence thresholds
11
+ * - Early termination: Stop when changes become insignificant
12
+ *
13
+ * Expected speedup: 2-5x on large graphs with many leaf nodes
14
+ */
15
+
16
+ export interface OptimizedLouvainOptions {
17
+ /**
18
+ * Resolution parameter (default: 1.0)
19
+ */
20
+ resolution?: number;
21
+ /**
22
+ * Maximum iterations per level (default: 100)
23
+ */
24
+ maxIterations?: number;
25
+ /**
26
+ * Convergence tolerance (default: 1e-6)
27
+ */
28
+ tolerance?: number;
29
+ /**
30
+ * Enable leaf node pruning (default: true)
31
+ */
32
+ pruneLeaves?: boolean;
33
+ /**
34
+ * Enable importance-based node ordering (default: true)
35
+ */
36
+ importanceOrdering?: boolean;
37
+ /**
38
+ * Base pruning threshold (default: 0.01)
39
+ */
40
+ pruningThreshold?: number;
41
+ /**
42
+ * Enable adaptive threshold cycling (default: true)
43
+ */
44
+ thresholdCycling?: boolean;
45
+ }
46
+
47
+ interface PruningStats {
48
+ leafNodesPruned: number;
49
+ lowDegreeNodesPruned: number;
50
+ stableNodesPruned: number;
51
+ }
52
+
53
+ /**
54
+ * Optimized Louvain implementation with early pruning and threshold cycling
55
+ */
56
+ export class OptimizedLouvain {
57
+ private graph: Graph;
58
+ private communities: Map<NodeId, number>;
59
+ private communityWeights: Map<number, number>;
60
+ private nodeWeights: Map<NodeId, number>;
61
+ private nodeDegrees: Map<NodeId, number>;
62
+ private totalWeight: number;
63
+ private pruningStats: PruningStats;
64
+
65
+ /**
66
+ * Create an optimized Louvain detector for the given graph
67
+ * @param graph - The input graph to detect communities in
68
+ */
69
+ constructor(graph: Graph) {
70
+ this.graph = graph;
71
+ this.communities = new Map();
72
+ this.communityWeights = new Map();
73
+ this.nodeWeights = new Map();
74
+ this.nodeDegrees = new Map();
75
+ this.totalWeight = 0;
76
+ this.pruningStats = {
77
+ leafNodesPruned: 0,
78
+ lowDegreeNodesPruned: 0,
79
+ stableNodesPruned: 0,
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Run optimized Louvain algorithm
85
+ * @param options - Algorithm configuration options
86
+ * @returns Community detection result with communities, modularity, and iterations
87
+ */
88
+ public detectCommunities(options: OptimizedLouvainOptions = {}): CommunityResult {
89
+ const {
90
+ resolution = 1.0,
91
+ maxIterations = 100,
92
+ tolerance = 1e-6,
93
+ pruneLeaves = true,
94
+ importanceOrdering = true,
95
+ pruningThreshold = 0.01,
96
+ thresholdCycling = true,
97
+ } = options;
98
+
99
+ // Initialize
100
+ this.initialize();
101
+ let modularity = this.calculateModularity(resolution);
102
+ let iteration = 0;
103
+ let improved = true;
104
+
105
+ while (iteration < maxIterations && improved) {
106
+ // Get nodes in optimal processing order
107
+ const orderedNodes = importanceOrdering
108
+ ? this.getNodesInImportanceOrder()
109
+ : Array.from(this.graph.nodes()).map((n) => n.id);
110
+
111
+ // Apply adaptive threshold
112
+ const threshold = thresholdCycling ? this.getAdaptiveThreshold(iteration, pruningThreshold) : 0;
113
+
114
+ // Perform local optimization
115
+ improved = this.performLocalMoving(orderedNodes, {
116
+ pruneLeaves,
117
+ threshold,
118
+ resolution,
119
+ });
120
+
121
+ if (improved) {
122
+ const newModularity = this.calculateModularity(resolution);
123
+
124
+ // Check convergence
125
+ if (Math.abs(newModularity - modularity) < tolerance) {
126
+ break;
127
+ }
128
+
129
+ modularity = newModularity;
130
+ iteration++;
131
+ }
132
+ }
133
+
134
+ // Convert community assignments to result format
135
+ const communityGroups = new Map<number, NodeId[]>();
136
+
137
+ for (const [nodeId, community] of this.communities) {
138
+ if (!communityGroups.has(community)) {
139
+ communityGroups.set(community, []);
140
+ }
141
+
142
+ const group = communityGroups.get(community);
143
+ if (group) {
144
+ group.push(nodeId);
145
+ }
146
+ }
147
+
148
+ return {
149
+ communities: Array.from(communityGroups.values()),
150
+ modularity,
151
+ iterations: iteration,
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Initialize data structures
157
+ */
158
+ private initialize(): void {
159
+ let communityId = 0;
160
+
161
+ // Initialize each node in its own community
162
+ for (const node of this.graph.nodes()) {
163
+ this.communities.set(node.id, communityId);
164
+
165
+ // Calculate node weight and degree
166
+ let nodeWeight = 0;
167
+ let degree = 0;
168
+
169
+ for (const neighbor of Array.from(this.graph.neighbors(node.id))) {
170
+ const edge = this.graph.getEdge(node.id, neighbor);
171
+ const weight = edge?.weight ?? 1;
172
+ nodeWeight += weight;
173
+ degree++;
174
+ }
175
+
176
+ // For undirected graphs, also check incoming edges
177
+ if (!this.graph.isDirected) {
178
+ for (const neighbor of Array.from(this.graph.inNeighbors(node.id))) {
179
+ if (!this.graph.hasEdge(node.id, neighbor)) {
180
+ const edge = this.graph.getEdge(neighbor, node.id);
181
+ const weight = edge?.weight ?? 1;
182
+ nodeWeight += weight;
183
+ degree++;
184
+ }
185
+ }
186
+ }
187
+
188
+ this.nodeWeights.set(node.id, nodeWeight);
189
+ this.nodeDegrees.set(node.id, degree);
190
+ this.communityWeights.set(communityId, nodeWeight);
191
+ this.totalWeight += nodeWeight;
192
+
193
+ communityId++;
194
+ }
195
+
196
+ // Total weight is sum of all edge weights
197
+ // For undirected graphs, each edge is counted twice from both endpoints
198
+ this.totalWeight = this.totalWeight / 2;
199
+ }
200
+
201
+ /**
202
+ * Get nodes ordered by importance (degree * log(weight))
203
+ * @returns Array of node IDs sorted by descending importance
204
+ */
205
+ private getNodesInImportanceOrder(): NodeId[] {
206
+ const nodeImportance = new Map<NodeId, number>();
207
+
208
+ for (const [nodeId, degree] of this.nodeDegrees) {
209
+ const weight = this.nodeWeights.get(nodeId) ?? 0;
210
+ // Importance score: combination of degree and weight
211
+ // High-degree nodes and nodes with heavy edges are processed first
212
+ const importance = degree * Math.log(1 + weight);
213
+ nodeImportance.set(nodeId, importance);
214
+ }
215
+
216
+ // Sort by importance (descending)
217
+ return Array.from(nodeImportance.entries())
218
+ .sort((a, b) => b[1] - a[1])
219
+ .map(([nodeId]) => nodeId);
220
+ }
221
+
222
+ /**
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
230
+ */
231
+ private performLocalMoving(
232
+ nodes: NodeId[],
233
+ options: {
234
+ pruneLeaves: boolean;
235
+ threshold: number;
236
+ resolution: number;
237
+ },
238
+ ): boolean {
239
+ const { pruneLeaves, threshold, resolution } = options;
240
+ let improvement = false;
241
+ let hasChanged = true;
242
+
243
+ while (hasChanged) {
244
+ hasChanged = false;
245
+
246
+ for (const nodeId of nodes) {
247
+ // Early pruning: skip leaf nodes
248
+ if (pruneLeaves && this.isLeafNode(nodeId)) {
249
+ this.pruningStats.leafNodesPruned++;
250
+ continue;
251
+ }
252
+
253
+ const currentCommunity = this.communities.get(nodeId) ?? 0;
254
+ const neighborCommunities = this.getNeighborCommunities(nodeId);
255
+
256
+ // Skip isolated nodes
257
+ if (neighborCommunities.size === 0) {
258
+ continue;
259
+ }
260
+
261
+ // Find best community to move to
262
+ let bestCommunity = currentCommunity;
263
+ let bestGain = 0;
264
+
265
+ // Remove node from its current community to calculate gains
266
+ this.removeNodeFromCommunity(nodeId, currentCommunity);
267
+
268
+ for (const community of neighborCommunities) {
269
+ const gain = this.calculateModularityGain(nodeId, community, resolution);
270
+
271
+ // Apply threshold - only move if gain exceeds threshold
272
+ if (gain > bestGain + threshold) {
273
+ bestGain = gain;
274
+ bestCommunity = community;
275
+ }
276
+ }
277
+
278
+ // Try staying in current community
279
+ const currentGain = this.calculateModularityGain(nodeId, currentCommunity, resolution);
280
+ if (currentGain > bestGain + threshold) {
281
+ bestGain = currentGain;
282
+ bestCommunity = currentCommunity;
283
+ }
284
+
285
+ // Add node to best community
286
+ this.addNodeToCommunity(nodeId, bestCommunity);
287
+
288
+ // Track if node moved
289
+ if (bestCommunity !== currentCommunity) {
290
+ hasChanged = true;
291
+ improvement = true;
292
+ }
293
+ }
294
+ }
295
+
296
+ return improvement;
297
+ }
298
+
299
+ /**
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
303
+ */
304
+ private isLeafNode(nodeId: NodeId): boolean {
305
+ const degree = this.nodeDegrees.get(nodeId) ?? 0;
306
+ return degree === 1;
307
+ }
308
+
309
+ /**
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
314
+ */
315
+ private getAdaptiveThreshold(iteration: number, baseThreshold: number): number {
316
+ // Exponentially decay threshold with iterations
317
+ // This allows coarse movements early and fine-tuning later
318
+ return baseThreshold * Math.pow(0.5, iteration / 10);
319
+ }
320
+
321
+ /**
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
327
+ */
328
+ private calculateModularityGain(nodeId: NodeId, targetCommunity: number, resolution: number): number {
329
+ const nodeWeight = this.nodeWeights.get(nodeId) ?? 0;
330
+
331
+ // Sum of weights from node to target community
332
+ let weightToTarget = 0;
333
+
334
+ for (const neighbor of Array.from(this.graph.neighbors(nodeId))) {
335
+ if (this.communities.get(neighbor) === targetCommunity) {
336
+ const edge = this.graph.getEdge(nodeId, neighbor);
337
+ weightToTarget += edge?.weight ?? 1;
338
+ }
339
+ }
340
+
341
+ // For undirected graphs, also check incoming edges
342
+ if (!this.graph.isDirected) {
343
+ for (const neighbor of Array.from(this.graph.inNeighbors(nodeId))) {
344
+ if (this.communities.get(neighbor) === targetCommunity && !this.graph.hasEdge(nodeId, neighbor)) {
345
+ const edge = this.graph.getEdge(neighbor, nodeId);
346
+ weightToTarget += edge?.weight ?? 1;
347
+ }
348
+ }
349
+ }
350
+
351
+ // Weight of target community
352
+ const targetWeight = this.communityWeights.get(targetCommunity) ?? 0;
353
+
354
+ // Modularity gain formula
355
+ const gain =
356
+ (weightToTarget - (resolution * nodeWeight * targetWeight) / (2 * this.totalWeight)) / this.totalWeight;
357
+
358
+ return gain;
359
+ }
360
+
361
+ /**
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
365
+ */
366
+ private removeNodeFromCommunity(nodeId: NodeId, community: number): void {
367
+ const nodeWeight = this.nodeWeights.get(nodeId) ?? 0;
368
+ this.communityWeights.set(community, (this.communityWeights.get(community) ?? 0) - nodeWeight);
369
+ this.communities.delete(nodeId);
370
+ }
371
+
372
+ /**
373
+ * Add node to community
374
+ * @param nodeId - The node ID to add
375
+ * @param community - The community ID to add to
376
+ */
377
+ private addNodeToCommunity(nodeId: NodeId, community: number): void {
378
+ const nodeWeight = this.nodeWeights.get(nodeId) ?? 0;
379
+ this.communityWeights.set(community, (this.communityWeights.get(community) ?? 0) + nodeWeight);
380
+ this.communities.set(nodeId, community);
381
+ }
382
+
383
+ /**
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
387
+ */
388
+ private getNeighborCommunities(nodeId: NodeId): Set<number> {
389
+ const communities = new Set<number>();
390
+
391
+ for (const neighbor of Array.from(this.graph.neighbors(nodeId))) {
392
+ const community = this.communities.get(neighbor);
393
+ if (community !== undefined) {
394
+ communities.add(community);
395
+ }
396
+ }
397
+
398
+ // For undirected graphs, also check incoming edges
399
+ if (!this.graph.isDirected) {
400
+ for (const neighbor of Array.from(this.graph.inNeighbors(nodeId))) {
401
+ const community = this.communities.get(neighbor);
402
+ if (community !== undefined) {
403
+ communities.add(community);
404
+ }
405
+ }
406
+ }
407
+
408
+ return communities;
409
+ }
410
+
411
+ /**
412
+ * Calculate total modularity
413
+ * @param resolution - Resolution parameter for modularity calculation
414
+ * @returns The modularity score of the current partition
415
+ */
416
+ private calculateModularity(resolution: number): number {
417
+ if (this.totalWeight === 0) {
418
+ return 0;
419
+ }
420
+
421
+ let modularity = 0;
422
+
423
+ // Sum over all communities
424
+ const communityInternalWeights = new Map<number, number>();
425
+
426
+ // Calculate internal weights for each community
427
+ for (const node of this.graph.nodes()) {
428
+ const nodeId = node.id;
429
+ const community = this.communities.get(nodeId) ?? 0;
430
+
431
+ for (const neighbor of Array.from(this.graph.neighbors(nodeId))) {
432
+ if (this.communities.get(neighbor) === community) {
433
+ const edge = this.graph.getEdge(nodeId, neighbor);
434
+ const weight = edge?.weight ?? 1;
435
+ communityInternalWeights.set(community, (communityInternalWeights.get(community) ?? 0) + weight);
436
+ }
437
+ }
438
+ }
439
+
440
+ // Calculate modularity
441
+ for (const [community, internalWeight] of communityInternalWeights) {
442
+ const communityWeight = this.communityWeights.get(community) ?? 0;
443
+ // For undirected graphs, internal weights are counted twice
444
+ const aIn = this.graph.isDirected ? internalWeight : internalWeight / 2;
445
+ const aTotal = communityWeight;
446
+
447
+ // Modularity formula: sum of (fraction of edges within community - expected fraction)
448
+ const actualFraction = aIn / this.totalWeight;
449
+ const expectedFraction = resolution * Math.pow(aTotal / (2 * this.totalWeight), 2);
450
+ modularity += actualFraction - expectedFraction;
451
+ }
452
+
453
+ return modularity;
454
+ }
455
+
456
+ /**
457
+ * Get pruning statistics
458
+ * @returns Statistics about nodes pruned during optimization
459
+ */
460
+ public getPruningStats(): PruningStats {
461
+ return { ...this.pruningStats };
462
+ }
463
+ }
464
+
465
+ /**
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
470
+ */
471
+ export function louvainOptimized(graph: Graph, options: OptimizedLouvainOptions = {}): CommunityResult {
472
+ const optimizer = new OptimizedLouvain(graph);
473
+ return optimizer.detectCommunities(options);
474
+ }