@graphty/algorithms 2.1.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/README.md +139 -50
  2. package/dist/algorithms.js +15260 -10390
  3. package/dist/algorithms.js.map +1 -1
  4. package/dist/algorithms.standalone.js +26675 -21542
  5. package/dist/algorithms.standalone.js.map +1 -1
  6. package/dist/src/algorithms/centrality/betweenness.d.ts +18 -0
  7. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  8. package/dist/src/algorithms/centrality/betweenness.js +40 -1
  9. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts +20 -0
  11. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  12. package/dist/src/algorithms/centrality/closeness.js +93 -8
  13. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  14. package/dist/src/algorithms/centrality/degree.d.ts +9 -0
  15. package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
  16. package/dist/src/algorithms/centrality/degree.js +18 -0
  17. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  18. package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -0
  19. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
  20. package/dist/src/algorithms/centrality/eigenvector.js +29 -0
  21. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
  22. package/dist/src/algorithms/centrality/hits.d.ts +9 -0
  23. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
  24. package/dist/src/algorithms/centrality/hits.js +23 -0
  25. package/dist/src/algorithms/centrality/hits.js.map +1 -1
  26. package/dist/src/algorithms/centrality/katz.d.ts +9 -0
  27. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
  28. package/dist/src/algorithms/centrality/katz.js +31 -0
  29. package/dist/src/algorithms/centrality/katz.js.map +1 -1
  30. package/dist/src/algorithms/centrality/pagerank.d.ts +13 -13
  31. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  32. package/dist/src/algorithms/centrality/pagerank.js +47 -10
  33. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  34. package/dist/src/algorithms/community/girvan-newman-legacy.d.ts +27 -0
  35. package/dist/src/algorithms/community/girvan-newman-legacy.d.ts.map +1 -0
  36. package/dist/src/algorithms/community/girvan-newman-legacy.js +335 -0
  37. package/dist/src/algorithms/community/girvan-newman-legacy.js.map +1 -0
  38. package/dist/src/algorithms/community/girvan-newman.d.ts +5 -3
  39. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  40. package/dist/src/algorithms/community/girvan-newman.js +34 -308
  41. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  42. package/dist/src/algorithms/community/label-propagation.d.ts +9 -6
  43. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  44. package/dist/src/algorithms/community/label-propagation.js +24 -110
  45. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  46. package/dist/src/algorithms/components/connected.d.ts.map +1 -1
  47. package/dist/src/algorithms/components/connected.js +28 -77
  48. package/dist/src/algorithms/components/connected.js.map +1 -1
  49. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
  50. package/dist/src/algorithms/mst/kruskal.js +59 -3
  51. package/dist/src/algorithms/mst/kruskal.js.map +1 -1
  52. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  53. package/dist/src/algorithms/shortest-path/bellman-ford.js +46 -0
  54. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  55. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  56. package/dist/src/algorithms/shortest-path/dijkstra.js +27 -0
  57. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  58. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +15 -5
  59. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/floyd-warshall.js +97 -107
  61. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
  62. package/dist/src/algorithms/traversal/bfs-unified.d.ts +2 -12
  63. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
  64. package/dist/src/algorithms/traversal/bfs-unified.js +47 -308
  65. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
  66. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
  67. package/dist/src/algorithms/traversal/bfs-variants.js +5 -25
  68. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
  69. package/dist/src/algorithms/traversal/bfs.d.ts +0 -3
  70. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  71. package/dist/src/algorithms/traversal/bfs.js +0 -4
  72. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  73. package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
  74. package/dist/src/algorithms/traversal/dfs.js +33 -154
  75. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  76. package/dist/src/clustering/hierarchical-legacy.d.ts +56 -0
  77. package/dist/src/clustering/hierarchical-legacy.d.ts.map +1 -0
  78. package/dist/src/clustering/hierarchical-legacy.js +441 -0
  79. package/dist/src/clustering/hierarchical-legacy.js.map +1 -0
  80. package/dist/src/clustering/hierarchical.d.ts +7 -39
  81. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  82. package/dist/src/clustering/hierarchical.js +56 -417
  83. package/dist/src/clustering/hierarchical.js.map +1 -1
  84. package/dist/src/clustering/k-core-legacy.d.ts +30 -0
  85. package/dist/src/clustering/k-core-legacy.d.ts.map +1 -0
  86. package/dist/src/clustering/k-core-legacy.js +191 -0
  87. package/dist/src/clustering/k-core-legacy.js.map +1 -0
  88. package/dist/src/clustering/k-core.d.ts +8 -7
  89. package/dist/src/clustering/k-core.d.ts.map +1 -1
  90. package/dist/src/clustering/k-core.js +33 -174
  91. package/dist/src/clustering/k-core.js.map +1 -1
  92. package/dist/src/clustering/mcl-legacy.d.ts +42 -0
  93. package/dist/src/clustering/mcl-legacy.d.ts.map +1 -0
  94. package/dist/src/clustering/mcl-legacy.js +386 -0
  95. package/dist/src/clustering/mcl-legacy.js.map +1 -0
  96. package/dist/src/clustering/mcl.d.ts +9 -32
  97. package/dist/src/clustering/mcl.d.ts.map +1 -1
  98. package/dist/src/clustering/mcl.js +48 -374
  99. package/dist/src/clustering/mcl.js.map +1 -1
  100. package/dist/src/index.d.ts +29 -3
  101. package/dist/src/index.d.ts.map +1 -1
  102. package/dist/src/index.js +2 -1
  103. package/dist/src/index.js.map +1 -1
  104. package/dist/src/indexed/accelerator.d.ts +141 -11
  105. package/dist/src/indexed/accelerator.d.ts.map +1 -1
  106. package/dist/src/indexed/accelerator.js +319 -5
  107. package/dist/src/indexed/accelerator.js.map +1 -1
  108. package/dist/src/indexed/all-pairs.d.ts +59 -0
  109. package/dist/src/indexed/all-pairs.d.ts.map +1 -0
  110. package/dist/src/indexed/all-pairs.js +274 -0
  111. package/dist/src/indexed/all-pairs.js.map +1 -0
  112. package/dist/src/indexed/bellman-ford.d.ts +25 -0
  113. package/dist/src/indexed/bellman-ford.d.ts.map +1 -0
  114. package/dist/src/indexed/bellman-ford.js +59 -0
  115. package/dist/src/indexed/bellman-ford.js.map +1 -0
  116. package/dist/src/indexed/betweenness.d.ts +85 -0
  117. package/dist/src/indexed/betweenness.d.ts.map +1 -0
  118. package/dist/src/indexed/betweenness.js +188 -0
  119. package/dist/src/indexed/betweenness.js.map +1 -0
  120. package/dist/src/indexed/bfs.d.ts +69 -2
  121. package/dist/src/indexed/bfs.d.ts.map +1 -1
  122. package/dist/src/indexed/bfs.js +148 -1
  123. package/dist/src/indexed/bfs.js.map +1 -1
  124. package/dist/src/indexed/bipartite.d.ts +37 -0
  125. package/dist/src/indexed/bipartite.d.ts.map +1 -0
  126. package/dist/src/indexed/bipartite.js +54 -0
  127. package/dist/src/indexed/bipartite.js.map +1 -0
  128. package/dist/src/indexed/closeness.d.ts +49 -0
  129. package/dist/src/indexed/closeness.d.ts.map +1 -0
  130. package/dist/src/indexed/closeness.js +127 -0
  131. package/dist/src/indexed/closeness.js.map +1 -0
  132. package/dist/src/indexed/common-neighbors.d.ts +14 -1
  133. package/dist/src/indexed/common-neighbors.d.ts.map +1 -1
  134. package/dist/src/indexed/common-neighbors.js +24 -14
  135. package/dist/src/indexed/common-neighbors.js.map +1 -1
  136. package/dist/src/indexed/degree.d.ts +19 -0
  137. package/dist/src/indexed/degree.d.ts.map +1 -0
  138. package/dist/src/indexed/degree.js +45 -0
  139. package/dist/src/indexed/degree.js.map +1 -0
  140. package/dist/src/indexed/delta-pagerank.d.ts +145 -0
  141. package/dist/src/indexed/delta-pagerank.d.ts.map +1 -0
  142. package/dist/src/indexed/delta-pagerank.js +428 -0
  143. package/dist/src/indexed/delta-pagerank.js.map +1 -0
  144. package/dist/src/indexed/dfs.d.ts +55 -0
  145. package/dist/src/indexed/dfs.d.ts.map +1 -0
  146. package/dist/src/indexed/dfs.js +146 -0
  147. package/dist/src/indexed/dfs.js.map +1 -0
  148. package/dist/src/indexed/eigenvector.d.ts +48 -0
  149. package/dist/src/indexed/eigenvector.d.ts.map +1 -0
  150. package/dist/src/indexed/eigenvector.js +162 -0
  151. package/dist/src/indexed/eigenvector.js.map +1 -0
  152. package/dist/src/indexed/facade.d.ts +109 -0
  153. package/dist/src/indexed/facade.d.ts.map +1 -0
  154. package/dist/src/indexed/facade.js +204 -0
  155. package/dist/src/indexed/facade.js.map +1 -0
  156. package/dist/src/indexed/flow.d.ts +120 -0
  157. package/dist/src/indexed/flow.d.ts.map +1 -0
  158. package/dist/src/indexed/flow.js +377 -0
  159. package/dist/src/indexed/flow.js.map +1 -0
  160. package/dist/src/indexed/girvan-newman.d.ts +42 -0
  161. package/dist/src/indexed/girvan-newman.d.ts.map +1 -0
  162. package/dist/src/indexed/girvan-newman.js +90 -0
  163. package/dist/src/indexed/girvan-newman.js.map +1 -0
  164. package/dist/src/indexed/grsbm.d.ts +85 -0
  165. package/dist/src/indexed/grsbm.d.ts.map +1 -0
  166. package/dist/src/indexed/grsbm.js +287 -0
  167. package/dist/src/indexed/grsbm.js.map +1 -0
  168. package/dist/src/indexed/hierarchical.d.ts +69 -0
  169. package/dist/src/indexed/hierarchical.d.ts.map +1 -0
  170. package/dist/src/indexed/hierarchical.js +245 -0
  171. package/dist/src/indexed/hierarchical.js.map +1 -0
  172. package/dist/src/indexed/index.d.ts +29 -3
  173. package/dist/src/indexed/index.d.ts.map +1 -1
  174. package/dist/src/indexed/index.js +29 -3
  175. package/dist/src/indexed/index.js.map +1 -1
  176. package/dist/src/indexed/isomorphism.d.ts +42 -0
  177. package/dist/src/indexed/isomorphism.d.ts.map +1 -0
  178. package/dist/src/indexed/isomorphism.js +269 -0
  179. package/dist/src/indexed/isomorphism.js.map +1 -0
  180. package/dist/src/indexed/label-propagation.d.ts +134 -0
  181. package/dist/src/indexed/label-propagation.d.ts.map +1 -0
  182. package/dist/src/indexed/label-propagation.js +486 -0
  183. package/dist/src/indexed/label-propagation.js.map +1 -0
  184. package/dist/src/indexed/leiden.d.ts +52 -0
  185. package/dist/src/indexed/leiden.d.ts.map +1 -0
  186. package/dist/src/indexed/leiden.js +384 -0
  187. package/dist/src/indexed/leiden.js.map +1 -0
  188. package/dist/src/indexed/link-prediction.d.ts +147 -0
  189. package/dist/src/indexed/link-prediction.d.ts.map +1 -0
  190. package/dist/src/indexed/link-prediction.js +317 -0
  191. package/dist/src/indexed/link-prediction.js.map +1 -0
  192. package/dist/src/indexed/markov.d.ts +54 -0
  193. package/dist/src/indexed/markov.d.ts.map +1 -0
  194. package/dist/src/indexed/markov.js +284 -0
  195. package/dist/src/indexed/markov.js.map +1 -0
  196. package/dist/src/indexed/matching.d.ts +53 -0
  197. package/dist/src/indexed/matching.d.ts.map +1 -0
  198. package/dist/src/indexed/matching.js +139 -0
  199. package/dist/src/indexed/matching.js.map +1 -0
  200. package/dist/src/indexed/min-cut.d.ts +48 -0
  201. package/dist/src/indexed/min-cut.d.ts.map +1 -0
  202. package/dist/src/indexed/min-cut.js +175 -0
  203. package/dist/src/indexed/min-cut.js.map +1 -0
  204. package/dist/src/indexed/modularity.d.ts +34 -0
  205. package/dist/src/indexed/modularity.d.ts.map +1 -0
  206. package/dist/src/indexed/modularity.js +74 -0
  207. package/dist/src/indexed/modularity.js.map +1 -0
  208. package/dist/src/indexed/mst.d.ts +29 -1
  209. package/dist/src/indexed/mst.d.ts.map +1 -1
  210. package/dist/src/indexed/mst.js +62 -0
  211. package/dist/src/indexed/mst.js.map +1 -1
  212. package/dist/src/indexed/pagerank.d.ts +29 -6
  213. package/dist/src/indexed/pagerank.d.ts.map +1 -1
  214. package/dist/src/indexed/pagerank.js +107 -11
  215. package/dist/src/indexed/pagerank.js.map +1 -1
  216. package/dist/src/indexed/point-to-point.d.ts +53 -0
  217. package/dist/src/indexed/point-to-point.d.ts.map +1 -0
  218. package/dist/src/indexed/point-to-point.js +173 -0
  219. package/dist/src/indexed/point-to-point.js.map +1 -0
  220. package/dist/src/indexed/scc.d.ts +53 -0
  221. package/dist/src/indexed/scc.d.ts.map +1 -0
  222. package/dist/src/indexed/scc.js +110 -0
  223. package/dist/src/indexed/scc.js.map +1 -0
  224. package/dist/src/indexed/spectral.d.ts +61 -0
  225. package/dist/src/indexed/spectral.d.ts.map +1 -0
  226. package/dist/src/indexed/spectral.js +476 -0
  227. package/dist/src/indexed/spectral.js.map +1 -0
  228. package/dist/src/indexed/structures/bit-set.d.ts +45 -0
  229. package/dist/src/indexed/structures/bit-set.d.ts.map +1 -0
  230. package/dist/src/indexed/structures/bit-set.js +57 -0
  231. package/dist/src/indexed/structures/bit-set.js.map +1 -0
  232. package/dist/src/indexed/structures/max-heap.d.ts +52 -0
  233. package/dist/src/indexed/structures/max-heap.d.ts.map +1 -0
  234. package/dist/src/indexed/structures/max-heap.js +66 -0
  235. package/dist/src/indexed/structures/max-heap.js.map +1 -0
  236. package/dist/src/indexed/structures/min-heap.d.ts +30 -1
  237. package/dist/src/indexed/structures/min-heap.d.ts.map +1 -1
  238. package/dist/src/indexed/structures/min-heap.js +46 -6
  239. package/dist/src/indexed/structures/min-heap.js.map +1 -1
  240. package/dist/src/indexed/structures/ring-queue.d.ts +41 -0
  241. package/dist/src/indexed/structures/ring-queue.d.ts.map +1 -0
  242. package/dist/src/indexed/structures/ring-queue.js +64 -0
  243. package/dist/src/indexed/structures/ring-queue.js.map +1 -0
  244. package/dist/src/indexed/sync.d.ts +50 -0
  245. package/dist/src/indexed/sync.d.ts.map +1 -0
  246. package/dist/src/indexed/sync.js +175 -0
  247. package/dist/src/indexed/sync.js.map +1 -0
  248. package/dist/src/indexed/terahac.d.ts +58 -0
  249. package/dist/src/indexed/terahac.d.ts.map +1 -0
  250. package/dist/src/indexed/terahac.js +244 -0
  251. package/dist/src/indexed/terahac.js.map +1 -0
  252. package/dist/src/indexed/to-snapshot.d.ts +45 -2
  253. package/dist/src/indexed/to-snapshot.d.ts.map +1 -1
  254. package/dist/src/indexed/to-snapshot.js +140 -6
  255. package/dist/src/indexed/to-snapshot.js.map +1 -1
  256. package/dist/src/link-prediction/common-neighbors-legacy.d.ts +71 -0
  257. package/dist/src/link-prediction/common-neighbors-legacy.d.ts.map +1 -0
  258. package/dist/src/link-prediction/common-neighbors-legacy.js +172 -0
  259. package/dist/src/link-prediction/common-neighbors-legacy.js.map +1 -0
  260. package/dist/src/link-prediction/common-neighbors.d.ts +11 -19
  261. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  262. package/dist/src/link-prediction/common-neighbors.js +56 -123
  263. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  264. package/dist/src/optimized/direction-optimized-bfs.d.ts +6 -0
  265. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
  266. package/dist/src/optimized/direction-optimized-bfs.js +6 -0
  267. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
  268. package/dist/src/research/grsbm-legacy.d.ts +82 -0
  269. package/dist/src/research/grsbm-legacy.d.ts.map +1 -0
  270. package/dist/src/research/grsbm-legacy.js +416 -0
  271. package/dist/src/research/grsbm-legacy.js.map +1 -0
  272. package/dist/src/research/grsbm.d.ts +9 -66
  273. package/dist/src/research/grsbm.d.ts.map +1 -1
  274. package/dist/src/research/grsbm.js +67 -391
  275. package/dist/src/research/grsbm.js.map +1 -1
  276. package/dist/src/research/sync-legacy.d.ts +48 -0
  277. package/dist/src/research/sync-legacy.d.ts.map +1 -0
  278. package/dist/src/research/sync-legacy.js +337 -0
  279. package/dist/src/research/sync-legacy.js.map +1 -0
  280. package/dist/src/research/sync.d.ts +10 -32
  281. package/dist/src/research/sync.d.ts.map +1 -1
  282. package/dist/src/research/sync.js +28 -316
  283. package/dist/src/research/sync.js.map +1 -1
  284. package/package.json +4 -3
  285. package/src/algorithms/centrality/betweenness.ts +51 -1
  286. package/src/algorithms/centrality/closeness.ts +112 -12
  287. package/src/algorithms/centrality/degree.ts +19 -0
  288. package/src/algorithms/centrality/eigenvector.ts +34 -0
  289. package/src/algorithms/centrality/hits.ts +24 -0
  290. package/src/algorithms/centrality/katz.ts +32 -0
  291. package/src/algorithms/centrality/pagerank.ts +53 -13
  292. package/src/algorithms/community/girvan-newman-legacy.ts +414 -0
  293. package/src/algorithms/community/girvan-newman.ts +36 -386
  294. package/src/algorithms/community/label-propagation.ts +24 -133
  295. package/src/algorithms/components/connected.ts +28 -95
  296. package/src/algorithms/mst/kruskal.ts +65 -4
  297. package/src/algorithms/shortest-path/bellman-ford.ts +49 -0
  298. package/src/algorithms/shortest-path/dijkstra.ts +29 -0
  299. package/src/algorithms/shortest-path/floyd-warshall.ts +106 -136
  300. package/src/algorithms/traversal/bfs-unified.ts +52 -366
  301. package/src/algorithms/traversal/bfs-variants.ts +5 -28
  302. package/src/algorithms/traversal/bfs.ts +0 -4
  303. package/src/algorithms/traversal/dfs.ts +39 -182
  304. package/src/clustering/hierarchical-legacy.ts +551 -0
  305. package/src/clustering/hierarchical.ts +64 -520
  306. package/src/clustering/k-core-legacy.ts +229 -0
  307. package/src/clustering/k-core.ts +38 -209
  308. package/src/clustering/mcl-legacy.ts +498 -0
  309. package/src/clustering/mcl.ts +46 -479
  310. package/src/index.ts +90 -3
  311. package/src/indexed/accelerator.ts +502 -16
  312. package/src/indexed/all-pairs.ts +342 -0
  313. package/src/indexed/bellman-ford.ts +72 -0
  314. package/src/indexed/betweenness.ts +258 -0
  315. package/src/indexed/bfs.ts +189 -3
  316. package/src/indexed/bipartite.ts +77 -0
  317. package/src/indexed/closeness.ts +160 -0
  318. package/src/indexed/common-neighbors.ts +31 -14
  319. package/src/indexed/degree.ts +55 -0
  320. package/src/indexed/delta-pagerank.ts +513 -0
  321. package/src/indexed/dfs.ts +189 -0
  322. package/src/indexed/eigenvector.ts +202 -0
  323. package/src/indexed/facade.ts +229 -0
  324. package/src/indexed/flow.ts +500 -0
  325. package/src/indexed/girvan-newman.ts +120 -0
  326. package/src/indexed/grsbm.ts +390 -0
  327. package/src/indexed/hierarchical.ts +295 -0
  328. package/src/indexed/index.ts +94 -3
  329. package/src/indexed/isomorphism.ts +305 -0
  330. package/src/indexed/label-propagation.ts +582 -0
  331. package/src/indexed/leiden.ts +436 -0
  332. package/src/indexed/link-prediction.ts +399 -0
  333. package/src/indexed/markov.ts +324 -0
  334. package/src/indexed/matching.ts +192 -0
  335. package/src/indexed/min-cut.ts +201 -0
  336. package/src/indexed/modularity.ts +81 -0
  337. package/src/indexed/mst.ts +79 -1
  338. package/src/indexed/pagerank.ts +128 -15
  339. package/src/indexed/point-to-point.ts +220 -0
  340. package/src/indexed/scc.ts +132 -0
  341. package/src/indexed/spectral.ts +550 -0
  342. package/src/indexed/structures/bit-set.ts +65 -0
  343. package/src/indexed/structures/max-heap.ts +74 -0
  344. package/src/indexed/structures/min-heap.ts +51 -6
  345. package/src/indexed/structures/ring-queue.ts +70 -0
  346. package/src/indexed/sync.ts +229 -0
  347. package/src/indexed/terahac.ts +290 -0
  348. package/src/indexed/to-snapshot.ts +156 -6
  349. package/src/link-prediction/common-neighbors-legacy.ts +252 -0
  350. package/src/link-prediction/common-neighbors.ts +65 -168
  351. package/src/optimized/direction-optimized-bfs.ts +6 -1
  352. package/src/research/grsbm-legacy.ts +586 -0
  353. package/src/research/grsbm.ts +76 -555
  354. package/src/research/sync-legacy.ts +456 -0
  355. package/src/research/sync.ts +30 -431
@@ -1,4 +1,7 @@
1
1
  import type { Graph } from "../../core/graph.js";
2
+ import { degreeCentrality as indexedDegree } from "../../indexed/degree.js";
3
+ import { scoresToRecord } from "../../indexed/facade.js";
4
+ import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
2
5
  import type { CentralityOptions, CentralityResult } from "../../types/index.js";
3
6
 
4
7
  /**
@@ -15,6 +18,22 @@ import type { CentralityOptions, CentralityResult } from "../../types/index.js";
15
18
  * @returns Centrality scores for each node keyed by node ID
16
19
  */
17
20
  export function degreeCentrality(graph: Graph, options: CentralityOptions = {}): CentralityResult {
21
+ if (needsLegacyCode(graph)) {
22
+ return legacyDegreeCentrality(graph, options);
23
+ }
24
+ const s = toSnapshot(graph);
25
+ return scoresToRecord(s.ids, indexedDegree(s, { mode: options.mode, normalized: options.normalized }));
26
+ }
27
+
28
+ /**
29
+ * The implementation {@link degreeCentrality} delegates away from, kept as its differential-test
30
+ * oracle. Deleted at the removal release.
31
+ * @param graph - The input graph to analyze
32
+ * @param options - Algorithm configuration options
33
+ * @returns Centrality scores for each node keyed by node ID
34
+ * @internal
35
+ */
36
+ export function legacyDegreeCentrality(graph: Graph, options: CentralityOptions = {}): CentralityResult {
18
37
  const centrality: CentralityResult = {};
19
38
  const { nodeCount } = graph;
20
39
 
@@ -1,5 +1,8 @@
1
1
  import type { Graph } from "../../core/graph.js";
2
2
  import { ConvergenceError } from "../../errors.js";
3
+ import { eigenvectorCentrality as indexedEigenvector } from "../../indexed/eigenvector.js";
4
+ import { scoresToRecord } from "../../indexed/facade.js";
5
+ import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
3
6
  import type { CentralityOptions, CentralityResult } from "../../types/index.js";
4
7
 
5
8
  /**
@@ -41,6 +44,37 @@ export interface EigenvectorCentralityOptions extends CentralityOptions {
41
44
  * raises `PowerIterationFailedConvergence`. Raise `maxIterations` or `tolerance` and call again.
42
45
  */
43
46
  export function eigenvectorCentrality(graph: Graph, options: EigenvectorCentralityOptions = {}): CentralityResult {
47
+ if (needsLegacyCode(graph)) {
48
+ return legacyEigenvectorCentrality(graph, options);
49
+ }
50
+ const s = toSnapshot(graph);
51
+ const { startVector } = options;
52
+ const r = indexedEigenvector(s, {
53
+ maxIterations: options.maxIterations,
54
+ tolerance: options.tolerance,
55
+ normalized: options.normalized,
56
+ mode: options.mode,
57
+ startVector:
58
+ startVector === undefined
59
+ ? undefined
60
+ : Float64Array.from({ length: s.nodeCount }, (_, i) => startVector.get(String(s.ids.idOf(i))) ?? 1),
61
+ });
62
+ return scoresToRecord(s.ids, r.scores);
63
+ }
64
+
65
+ /**
66
+ * The implementation {@link eigenvectorCentrality} delegates away from, kept as its
67
+ * differential-test oracle. Deleted at the removal release.
68
+ * @param graph - The graph to compute eigenvector centrality on
69
+ * @param options - Configuration options for the computation
70
+ * @returns Object mapping node IDs to their eigenvector centrality scores
71
+ * @throws {ConvergenceError} When `maxIterations` passes do not meet `tolerance`
72
+ * @internal
73
+ */
74
+ export function legacyEigenvectorCentrality(
75
+ graph: Graph,
76
+ options: EigenvectorCentralityOptions = {},
77
+ ): CentralityResult {
44
78
  const { maxIterations = 100, tolerance = 1e-6, normalized = true, startVector, mode = "in" } = options;
45
79
 
46
80
  const nodeIds = Array.from(graph.nodes(), (node) => node.id);
@@ -1,4 +1,7 @@
1
1
  import type { Graph } from "../../core/graph.js";
2
+ import { scoresToRecord } from "../../indexed/facade.js";
3
+ import { hits as indexedHits } from "../../indexed/hits.js";
4
+ import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
2
5
  import type { CentralityOptions } from "../../types/index.js";
3
6
 
4
7
  /**
@@ -32,6 +35,27 @@ export interface HITSOptions extends CentralityOptions {
32
35
  * @returns Object containing hub and authority scores for all nodes
33
36
  */
34
37
  export function hits(graph: Graph, options: HITSOptions = {}): HITSResult {
38
+ if (needsLegacyCode(graph)) {
39
+ return legacyHits(graph, options);
40
+ }
41
+ const s = toSnapshot(graph);
42
+ const r = indexedHits(s, {
43
+ maxIterations: options.maxIterations,
44
+ tolerance: options.tolerance,
45
+ normalized: options.normalized,
46
+ });
47
+ return { hubs: scoresToRecord(s.ids, r.hubs), authorities: scoresToRecord(s.ids, r.authorities) };
48
+ }
49
+
50
+ /**
51
+ * The implementation {@link hits} delegates away from, kept as its differential-test oracle.
52
+ * Deleted at the removal release.
53
+ * @param graph - The graph to compute HITS scores on
54
+ * @param options - Configuration options for the computation
55
+ * @returns Object containing hub and authority scores for all nodes
56
+ * @internal
57
+ */
58
+ export function legacyHits(graph: Graph, options: HITSOptions = {}): HITSResult {
35
59
  const { maxIterations = 100, tolerance = 1e-6, normalized = true } = options;
36
60
 
37
61
  const hubs: Record<string, number> = {};
@@ -1,4 +1,7 @@
1
1
  import type { Graph } from "../../core/graph.js";
2
+ import { scoresToRecord } from "../../indexed/facade.js";
3
+ import { katzCentrality as indexedKatz } from "../../indexed/katz.js";
4
+ import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
2
5
  import type { CentralityOptions, CentralityResult } from "../../types/index.js";
3
6
 
4
7
  /**
@@ -29,6 +32,35 @@ export interface KatzCentralityOptions extends CentralityOptions {
29
32
  * @returns Object mapping node IDs to their Katz centrality scores
30
33
  */
31
34
  export function katzCentrality(graph: Graph, options: KatzCentralityOptions = {}): CentralityResult {
35
+ const { alpha = 0.1, beta = 1.0 } = options;
36
+ // A negative alpha or beta can leave every score negative, and the legacy rescale measures the
37
+ // maximum from 0 rather than from the largest score.
38
+ if (alpha < 0 || beta < 0) {
39
+ return legacyKatzCentrality(graph, options);
40
+ }
41
+ if (needsLegacyCode(graph)) {
42
+ return legacyKatzCentrality(graph, options);
43
+ }
44
+ const s = toSnapshot(graph);
45
+ const r = indexedKatz(s, {
46
+ alpha,
47
+ beta,
48
+ maxIterations: options.maxIterations,
49
+ tolerance: options.tolerance,
50
+ normalized: options.normalized,
51
+ });
52
+ return scoresToRecord(s.ids, r.scores);
53
+ }
54
+
55
+ /**
56
+ * The implementation {@link katzCentrality} delegates away from, kept as its differential-test
57
+ * oracle and for a negative `alpha` or `beta`. Deleted at the removal release.
58
+ * @param graph - The graph to compute Katz centrality on
59
+ * @param options - Configuration options for the computation
60
+ * @returns Object mapping node IDs to their Katz centrality scores
61
+ * @internal
62
+ */
63
+ export function legacyKatzCentrality(graph: Graph, options: KatzCentralityOptions = {}): CentralityResult {
32
64
  const { alpha = 0.1, beta = 1.0, maxIterations = 100, tolerance = 1e-6, normalized = true } = options;
33
65
 
34
66
  const centrality: CentralityResult = {};
@@ -1,4 +1,9 @@
1
+ import { type F64, INVALID_INDEX } from "@graphty/graph-format";
2
+
1
3
  import type { Graph } from "../../core/graph.js";
4
+ import { deltaPageRank } from "../../indexed/delta-pagerank.js";
5
+ import { exactArcWeights, scoresToRecord } from "../../indexed/facade.js";
6
+ import { needsLegacyCode, toSnapshot } from "../../indexed/to-snapshot.js";
2
7
  import type { NodeId } from "../../types/index.js";
3
8
  import { SimpleDeltaPageRank } from "./delta-pagerank-simple.js";
4
9
 
@@ -38,9 +43,8 @@ export interface PageRankOptions {
38
43
  */
39
44
  weight?: string;
40
45
  /**
41
- * Use delta-based optimization for faster convergence.
42
- * Defaults to true for graphs with >100 nodes, false for smaller graphs.
43
- * Set explicitly to override automatic heuristic.
46
+ * Accepted and ignored. It once chose between two engines that compute the same iteration, so
47
+ * it never changed the result, and now there is one engine.
44
48
  */
45
49
  useDelta?: boolean;
46
50
  }
@@ -66,21 +70,57 @@ export interface PageRankResult {
66
70
  /**
67
71
  * Calculate PageRank for all nodes in the graph
68
72
  *
69
- * Uses delta-based optimization by default for improved performance on larger graphs.
70
- * Automatically falls back to standard algorithm for very small graphs.
71
- *
72
- * The delta-based approach provides significant speedup for:
73
- * - Incremental updates after graph modifications
74
- * - Graphs with localized changes
75
- * - Early convergence detection per vertex
76
- *
77
- * For initial computation on small-medium graphs, standard algorithm may be faster
78
- * due to lower overhead.
73
+ * Power iteration with the dangling mass spread over every node and an L-infinity stopping rule.
74
+ * `useDelta` is ignored: it chose between two engines that computed the same iteration.
79
75
  * @param graph - The directed input graph to analyze
80
76
  * @param options - Algorithm configuration options
81
77
  * @returns PageRank result containing ranks, iteration count, and convergence status
82
78
  */
83
79
  export function pageRank(graph: Graph, options: PageRankOptions = {}): PageRankResult {
80
+ if (needsLegacyCode(graph)) {
81
+ return legacyPageRank(graph, options);
82
+ }
83
+ const s = toSnapshot(graph);
84
+ const { personalization, initialRanks, weight } = options;
85
+ // The legacy code normalises the personalization over every entry of the Map, so an id the
86
+ // graph lacks takes a share; the port sees only the graph's nodes.
87
+ if (
88
+ personalization !== undefined &&
89
+ [...personalization.keys()].some((id) => s.ids.indexOf(id) === INVALID_INDEX)
90
+ ) {
91
+ return legacyPageRank(graph, options);
92
+ }
93
+ const perNode = (map: Map<NodeId, number> | undefined, fill: number): F64 | undefined => {
94
+ if (map === undefined) {
95
+ return undefined;
96
+ }
97
+ const out = new Float64Array(s.nodeCount);
98
+ for (let i = 0; i < out.length; i++) {
99
+ out[i] = map.get(s.ids.idOf(i)) ?? fill;
100
+ }
101
+ return out;
102
+ };
103
+ const r = deltaPageRank(s, {
104
+ dampingFactor: options.dampingFactor,
105
+ maxIterations: options.maxIterations,
106
+ tolerance: options.tolerance,
107
+ weighted: Boolean(weight),
108
+ weights: weight ? exactArcWeights(s) : undefined,
109
+ initialRanks: perNode(initialRanks, 1 / s.nodeCount),
110
+ personalization: perNode(personalization, 0),
111
+ });
112
+ return { ranks: scoresToRecord(s.ids, r.scores), iterations: r.iterations, converged: r.converged };
113
+ }
114
+
115
+ /**
116
+ * The implementation {@link pageRank} delegates away from, kept as its differential-test oracle and
117
+ * for a personalization naming a node the graph lacks. Deleted at the removal release.
118
+ * @param graph - The directed input graph to analyze
119
+ * @param options - Algorithm configuration options
120
+ * @returns PageRank result containing ranks, iteration count, and convergence status
121
+ * @internal
122
+ */
123
+ export function legacyPageRank(graph: Graph, options: PageRankOptions = {}): PageRankResult {
84
124
  const {
85
125
  dampingFactor = 0.85,
86
126
  maxIterations = 100,
@@ -0,0 +1,414 @@
1
+ /**
2
+ * Girvan-Newman as it was before `girvanNewman` delegated to `indexed.girvanNewman`, unchanged. The
3
+ * facade in `girvan-newman.ts` still calls it for a directed graph, a self-loop, a NaN weight or
4
+ * ids spelled alike, and its facade test uses it as the oracle.
5
+ */
6
+
7
+ import { Graph } from "../../core/graph.js";
8
+ import type { CommunityResult, ComponentResult, GirvanNewmanOptions, NodeId } from "../../types/index.js";
9
+ import { connectedComponents } from "../components/connected.js";
10
+
11
+ /**
12
+ * Girvan-Newman community detection algorithm
13
+ *
14
+ * Implements the Girvan-Newman method for community detection by iteratively
15
+ * removing edges with the highest betweenness centrality until the graph
16
+ * splits into disconnected components.
17
+ *
18
+ * This is a divisive hierarchical clustering algorithm that produces a
19
+ * dendrogram of community structures.
20
+ *
21
+ * References:
22
+ * - Girvan, M., & Newman, M. E. J. (2002). Community structure in social
23
+ * and biological networks. Proceedings of the National Academy of Sciences,
24
+ * 99(12), 7821-7826.
25
+ * @param graph - The input graph
26
+ * @param options - Algorithm options
27
+ * @returns Array of community detection results representing the dendrogram
28
+ */
29
+ export function girvanNewman(graph: Graph, options: GirvanNewmanOptions = {}): CommunityResult[] {
30
+ const { maxCommunities } = options;
31
+ const minCommunitySize = options.minCommunitySize ?? 1;
32
+ const maxIterations = options.maxIterations ?? 100; // Prevent infinite loops
33
+
34
+ const dendrogram: CommunityResult[] = [];
35
+ const workingGraph = graph.clone();
36
+ let iterations = 0;
37
+
38
+ // Initial state: one large component
39
+ let components = getConnectedComponentsResult(workingGraph);
40
+ dendrogram.push({
41
+ communities: components.components.filter((community) => community.length >= minCommunitySize),
42
+ modularity: calculateModularity(graph, components.componentMap),
43
+ });
44
+
45
+ while (Array.from(workingGraph.edges()).length > 0 && iterations < maxIterations) {
46
+ iterations++;
47
+ // Calculate edge betweenness centrality
48
+ const { betweenness: edgeBetweenness, edgeEndpoints } = calculateEdgeBetweenness(workingGraph);
49
+
50
+ if (edgeBetweenness.size === 0) {
51
+ break;
52
+ }
53
+
54
+ // Find edges with maximum betweenness
55
+ const maxBetweenness = Math.max(...edgeBetweenness.values());
56
+ const edgesToRemove: { source: NodeId; target: NodeId }[] = [];
57
+
58
+ for (const [edgeKey, centrality] of edgeBetweenness) {
59
+ if (Math.abs(centrality - maxBetweenness) < 1e-10) {
60
+ // Use original edge endpoints to preserve node ID types (number vs string)
61
+ const endpoints = edgeEndpoints.get(edgeKey);
62
+ if (endpoints) {
63
+ edgesToRemove.push(endpoints);
64
+ }
65
+ }
66
+ }
67
+
68
+ // Remove edges with highest betweenness
69
+ for (const { source, target } of edgesToRemove) {
70
+ workingGraph.removeEdge(source, target);
71
+ }
72
+
73
+ // Find new connected components (communities)
74
+ components = getConnectedComponentsResult(workingGraph);
75
+
76
+ // Filter communities by minimum size
77
+ const validCommunities = components.components.filter((community) => community.length >= minCommunitySize);
78
+
79
+ // Calculate modularity for the new community structure
80
+ const modularity = calculateModularity(graph, components.componentMap);
81
+
82
+ dendrogram.push({
83
+ communities: validCommunities,
84
+ modularity,
85
+ });
86
+
87
+ // Stop if desired number of communities reached
88
+ if (maxCommunities && validCommunities.length >= maxCommunities) {
89
+ break;
90
+ }
91
+
92
+ // Stop if no more meaningful communities can be formed
93
+ if (validCommunities.length === workingGraph.nodeCount) {
94
+ break;
95
+ }
96
+ }
97
+
98
+ return dendrogram;
99
+ }
100
+
101
+ /**
102
+ * Edge betweenness result containing centrality values and original edge endpoints
103
+ */
104
+ interface EdgeBetweennessResult {
105
+ betweenness: Map<string, number>;
106
+ edgeEndpoints: Map<string, { source: NodeId; target: NodeId }>;
107
+ }
108
+
109
+ /**
110
+ * Calculate edge betweenness centrality for all edges in the graph
111
+ *
112
+ * Edge betweenness is the fraction of shortest paths that pass through the edge.
113
+ * We adapt node betweenness centrality calculation to work with edges.
114
+ * @param graph - The input graph to analyze
115
+ * @returns Object with betweenness map and edge endpoints map (preserving original ID types)
116
+ */
117
+ function calculateEdgeBetweenness(graph: Graph): EdgeBetweennessResult {
118
+ const edgeBetweenness = new Map<string, number>();
119
+ const edgeEndpoints = new Map<string, { source: NodeId; target: NodeId }>();
120
+
121
+ // Initialize all edges with 0 betweenness and store original endpoints
122
+ for (const edge of graph.edges()) {
123
+ const edgeKey = getEdgeKey(edge.source, edge.target);
124
+ edgeBetweenness.set(edgeKey, 0);
125
+ edgeEndpoints.set(edgeKey, { source: edge.source, target: edge.target });
126
+ }
127
+
128
+ // For each node as source, calculate shortest paths and accumulate edge betweenness
129
+ for (const sourceNode of graph.nodes()) {
130
+ const source = sourceNode.id;
131
+
132
+ // Run BFS to find all shortest paths from source
133
+ const { distances, predecessors, pathCounts } = findAllShortestPaths(graph, source);
134
+
135
+ // Calculate dependency for each node and accumulate edge betweenness
136
+ const dependency = new Map<NodeId, number>();
137
+
138
+ // Initialize dependency
139
+ for (const node of graph.nodes()) {
140
+ dependency.set(node.id, 0);
141
+ }
142
+
143
+ // Process nodes in order of decreasing distance
144
+ const sortedNodes = Array.from(distances.keys())
145
+ .filter((node) => {
146
+ const distance = distances.get(node);
147
+ return distance !== undefined && distance < Infinity;
148
+ })
149
+ .sort((a, b) => {
150
+ const distanceA = distances.get(a);
151
+ const distanceB = distances.get(b);
152
+ if (distanceA === undefined || distanceB === undefined) {
153
+ return 0;
154
+ }
155
+
156
+ return distanceB - distanceA;
157
+ });
158
+
159
+ for (const node of sortedNodes) {
160
+ if (node === source) {
161
+ continue;
162
+ }
163
+
164
+ const nodeDependency = dependency.get(node);
165
+ const nodePathCount = pathCounts.get(node);
166
+
167
+ if (nodeDependency === undefined || nodePathCount === undefined) {
168
+ continue;
169
+ }
170
+
171
+ // Distribute dependency to predecessors
172
+ const nodePredecessors = predecessors.get(node) ?? [];
173
+ for (const predecessor of nodePredecessors) {
174
+ const predPathCount = pathCounts.get(predecessor);
175
+ if (predPathCount === undefined) {
176
+ continue;
177
+ }
178
+
179
+ const edgeDependency = (predPathCount / nodePathCount) * (1 + nodeDependency);
180
+
181
+ // Update predecessor dependency
182
+ const currentDependency = dependency.get(predecessor);
183
+ if (currentDependency !== undefined) {
184
+ dependency.set(predecessor, currentDependency + edgeDependency);
185
+ }
186
+
187
+ // Update edge betweenness
188
+ const edgeKey = getEdgeKey(predecessor, node);
189
+ const currentBetweenness = edgeBetweenness.get(edgeKey) ?? 0;
190
+ edgeBetweenness.set(edgeKey, currentBetweenness + edgeDependency);
191
+ }
192
+ }
193
+ }
194
+
195
+ // Normalize: divide by 2 for undirected graphs (each edge counted twice)
196
+ if (!graph.isDirected) {
197
+ for (const [edgeKey, betweenness] of edgeBetweenness) {
198
+ edgeBetweenness.set(edgeKey, betweenness / 2);
199
+ }
200
+ }
201
+
202
+ return { betweenness: edgeBetweenness, edgeEndpoints };
203
+ }
204
+
205
+ /**
206
+ * Find all shortest paths from a source node using optimized BFS
207
+ * @param graph - The input graph to search
208
+ * @param source - The source node to compute shortest paths from
209
+ * @returns Object containing distances, predecessors, and path counts
210
+ */
211
+ function findAllShortestPaths(
212
+ graph: Graph,
213
+ source: NodeId,
214
+ ): {
215
+ distances: Map<NodeId, number>;
216
+ predecessors: Map<NodeId, NodeId[]>;
217
+ pathCounts: Map<NodeId, number>;
218
+ } {
219
+ const distances = new Map<NodeId, number>();
220
+ const predecessors = new Map<NodeId, NodeId[]>();
221
+ const pathCounts = new Map<NodeId, number>();
222
+ const queue: NodeId[] = [];
223
+ const visited = new Set<NodeId>();
224
+
225
+ // Only initialize for reachable nodes
226
+ distances.set(source, 0);
227
+ predecessors.set(source, []);
228
+ pathCounts.set(source, 1);
229
+ queue.push(source);
230
+
231
+ // Optimized BFS with early termination
232
+ let queueIndex = 0;
233
+ while (queueIndex < queue.length) {
234
+ const current = queue[queueIndex++];
235
+ if (current === undefined) {
236
+ continue;
237
+ }
238
+
239
+ if (visited.has(current)) {
240
+ continue;
241
+ }
242
+
243
+ visited.add(current);
244
+
245
+ const currentDistance = distances.get(current);
246
+ if (currentDistance === undefined) {
247
+ continue;
248
+ }
249
+
250
+ for (const neighbor of graph.neighbors(current)) {
251
+ // Skip if already processed
252
+ if (visited.has(neighbor)) {
253
+ continue;
254
+ }
255
+
256
+ const edgeWeight = 1; // Unweighted for community detection
257
+ const newDistance = currentDistance + edgeWeight;
258
+ const neighborDistance = distances.get(neighbor) ?? Infinity;
259
+
260
+ if (newDistance < neighborDistance) {
261
+ // Found shorter path
262
+ distances.set(neighbor, newDistance);
263
+ predecessors.set(neighbor, [current]);
264
+ const currentPathCount = pathCounts.get(current);
265
+ if (currentPathCount !== undefined) {
266
+ pathCounts.set(neighbor, currentPathCount);
267
+ }
268
+
269
+ queue.push(neighbor);
270
+ } else if (newDistance === neighborDistance) {
271
+ // Found alternative shortest path
272
+ const neighborPredecessors = predecessors.get(neighbor);
273
+ if (neighborPredecessors) {
274
+ neighborPredecessors.push(current);
275
+ const currentPathCount = pathCounts.get(current);
276
+ if (currentPathCount !== undefined) {
277
+ pathCounts.set(neighbor, (pathCounts.get(neighbor) ?? 0) + currentPathCount);
278
+ }
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ return { distances, predecessors, pathCounts };
285
+ }
286
+
287
+ /**
288
+ * Generate a consistent edge key for undirected graphs
289
+ * @param source - The source node ID
290
+ * @param target - The target node ID
291
+ * @returns A consistent string key for the edge (ordered alphabetically)
292
+ */
293
+ function getEdgeKey(source: NodeId, target: NodeId): string {
294
+ // For undirected graphs, ensure consistent ordering
295
+ const sourceStr = String(source);
296
+ const targetStr = String(target);
297
+
298
+ if (sourceStr <= targetStr) {
299
+ return `${sourceStr}|${targetStr}`;
300
+ }
301
+
302
+ return `${targetStr}|${sourceStr}`;
303
+ }
304
+
305
+ /**
306
+ * Calculate modularity for a given community structure
307
+ *
308
+ * Newman's Q, written per community:
309
+ *
310
+ * Q = sum over communities c of [ w_in(c) / m - ( K_c / (2m) )^2 ]
311
+ *
312
+ * where m is the total edge weight with each undirected edge counted once, w_in(c) is the summed
313
+ * weight of the edges with both endpoints inside c, and K_c is the summed weighted degree of c's
314
+ * nodes.
315
+ *
316
+ * WHY NOT A SUM OVER EDGES. The textbook double sum runs over every ordered PAIR of nodes inside
317
+ * a community, not only over the pairs an edge happens to join. Collecting the null-model term
318
+ * only where an edge exists leaves the penalty far too small, and the shortfall grows with
319
+ * community size -- so the uncut whole graph, whose modularity is 0 by definition, outscores
320
+ * every real cut and a caller reading the dendrogram the standard way is handed one community
321
+ * containing everything. The per-community form above counts every pair exactly once and still
322
+ * costs one pass over the edges rather than a pass over every pair.
323
+ * @param graph - The original graph
324
+ * @param communityMap - Map from node IDs to community indices
325
+ * @returns The modularity score of the partition
326
+ */
327
+ function calculateModularity(graph: Graph, communityMap: Map<NodeId, number>): number {
328
+ const totalEdgeWeight = getTotalEdgeWeight(graph);
329
+ if (totalEdgeWeight === 0) {
330
+ return 0;
331
+ }
332
+
333
+ // Summed weighted degree of each community's nodes.
334
+ const degreeSum = new Map<number, number>();
335
+ for (const node of graph.nodes()) {
336
+ const community = communityMap.get(node.id);
337
+ if (community === undefined) {
338
+ continue;
339
+ }
340
+
341
+ degreeSum.set(community, (degreeSum.get(community) ?? 0) + getNodeDegree(graph, node.id));
342
+ }
343
+
344
+ // Summed weight of the edges that stay inside a community.
345
+ const internalWeight = new Map<number, number>();
346
+ for (const edge of graph.edges()) {
347
+ const communityI = communityMap.get(edge.source);
348
+ const communityJ = communityMap.get(edge.target);
349
+
350
+ if (communityI === undefined || communityI !== communityJ) {
351
+ continue;
352
+ }
353
+
354
+ internalWeight.set(communityI, (internalWeight.get(communityI) ?? 0) + (edge.weight ?? 1));
355
+ }
356
+
357
+ let modularity = 0;
358
+ for (const [community, degrees] of degreeSum) {
359
+ const share = degrees / (2 * totalEdgeWeight);
360
+ modularity += (internalWeight.get(community) ?? 0) / totalEdgeWeight - share * share;
361
+ }
362
+
363
+ return modularity;
364
+ }
365
+
366
+ /**
367
+ * Calculate total edge weight in the graph
368
+ * @param graph - The input graph
369
+ * @returns Total sum of all edge weights
370
+ */
371
+ function getTotalEdgeWeight(graph: Graph): number {
372
+ let totalWeight = 0;
373
+
374
+ for (const edge of graph.edges()) {
375
+ totalWeight += edge.weight ?? 1;
376
+ }
377
+
378
+ return totalWeight;
379
+ }
380
+
381
+ /**
382
+ * Get the total degree (sum of edge weights) for a node
383
+ * @param graph - The input graph
384
+ * @param nodeId - The node ID to compute degree for
385
+ * @returns The weighted degree of the node
386
+ */
387
+ function getNodeDegree(graph: Graph, nodeId: NodeId): number {
388
+ let degree = 0;
389
+
390
+ for (const neighbor of graph.neighbors(nodeId)) {
391
+ const edge = graph.getEdge(nodeId, neighbor);
392
+ degree += edge?.weight ?? 1;
393
+ }
394
+
395
+ return degree;
396
+ }
397
+
398
+ /**
399
+ * Convert connected components result to ComponentResult format
400
+ * @param graph - The input graph
401
+ * @returns Object containing components array and node-to-component map
402
+ */
403
+ function getConnectedComponentsResult(graph: Graph): ComponentResult {
404
+ const components = connectedComponents(graph);
405
+ const componentMap = new Map<NodeId, number>();
406
+
407
+ components.forEach((component, index) => {
408
+ component.forEach((nodeId) => {
409
+ componentMap.set(nodeId, index);
410
+ });
411
+ });
412
+
413
+ return { components, componentMap };
414
+ }