@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
@@ -0,0 +1,500 @@
1
+ import {
2
+ fromEdgeArrays,
3
+ GraphBuilder,
4
+ type GraphSnapshot,
5
+ INVALID_INDEX,
6
+ makeMask,
7
+ maskSet,
8
+ maskTest,
9
+ type NodeId,
10
+ type NodeMask,
11
+ type NumericVector,
12
+ type U32,
13
+ } from "@graphty/graph-format";
14
+
15
+ /** Options of {@link maxFlow} and {@link minSTCut}. @public */
16
+ export interface MaxFlowOptions {
17
+ /**
18
+ * How augmenting paths are found: `"edmonds-karp"` (breadth-first, shortest paths first,
19
+ * O(V E^2)) or `"ford-fulkerson"` (depth-first, O(E f)). Default `"edmonds-karp"` for
20
+ * {@link maxFlow} and `"ford-fulkerson"` for {@link minSTCut}. Both give the same source side
21
+ * and the same flow value up to floating-point rounding; the per-edge flows can differ where
22
+ * the maximum flow is not unique.
23
+ */
24
+ readonly algorithm?: "edmonds-karp" | "ford-fulkerson" | undefined;
25
+ /**
26
+ * Per-arc capacity override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`
27
+ * for exact f64 capacities. Without it the snapshot's arc weights are the capacities, and an
28
+ * unweighted snapshot gives every edge capacity 1.
29
+ */
30
+ readonly weights?: NumericVector | undefined;
31
+ }
32
+
33
+ /** Result of {@link maxFlow}. @public */
34
+ export interface MaxFlowResult {
35
+ /** The value of the maximum flow. */
36
+ readonly maxFlow: number;
37
+ /**
38
+ * Net flow per logical edge in its declared orientation. On an undirected snapshot a negative
39
+ * value is flow from the declared target to the declared source.
40
+ */
41
+ readonly flow: Float64Array;
42
+ /** The nodes reachable from the source in the final residual graph: the source side of a minimum cut. */
43
+ readonly sourceSide: NodeMask;
44
+ /**
45
+ * Logical edges with positive capacity that cross the cut, in edge order: from the source side
46
+ * to the other on a directed snapshot, with exactly one endpoint on the source side on an
47
+ * undirected one.
48
+ */
49
+ readonly cutEdges: U32;
50
+ }
51
+
52
+ /** Result of the index-based minimum cuts ({@link minSTCut}, `stoerWagner`, `kargerMinCut`). @public */
53
+ export interface MinCutResult {
54
+ /** The total weight of the cut. */
55
+ readonly cutValue: number;
56
+ /** One side of the cut; every other node is on the other side. */
57
+ readonly side: NodeMask;
58
+ /** Logical edges with one endpoint on each side, in edge order. */
59
+ readonly cutEdges: U32;
60
+ }
61
+
62
+ /**
63
+ * The capacity of every logical edge: the override's value at the edge's declared arc, else the
64
+ * snapshot's per-edge weight, else 1.
65
+ * @param s - The snapshot
66
+ * @param weights - The optional per-arc override
67
+ * @returns One capacity per logical edge
68
+ */
69
+ export function edgeCapacities(s: GraphSnapshot, weights: NumericVector | undefined): Float64Array {
70
+ const el = s.edgeList();
71
+ const out = new Float64Array(s.edgeCount);
72
+ for (let e = 0; e < s.edgeCount; e++) {
73
+ out[e] = weights !== undefined ? weights[el.arc[e]] : (el.weights?.[e] ?? 1);
74
+ }
75
+ return out;
76
+ }
77
+
78
+ /**
79
+ * The logical edges whose endpoints lie on different sides of `side` -- leaving it, when
80
+ * `directed` -- with positive capacity when `positiveOnly` is set.
81
+ * @param s - The snapshot
82
+ * @param side - One side of the cut
83
+ * @param directed - Whether only edges from `side` to the other side count
84
+ * @param capacity - Per-edge capacities, consulted only with `positiveOnly`
85
+ * @param positiveOnly - Whether to leave out edges of capacity zero or less
86
+ * @returns Edge indices in edge order
87
+ */
88
+ export function crossingEdges(
89
+ s: GraphSnapshot,
90
+ side: NodeMask,
91
+ directed: boolean,
92
+ capacity: Float64Array,
93
+ positiveOnly: boolean,
94
+ ): U32 {
95
+ const { src, dst } = s.edgeList();
96
+ const out: number[] = [];
97
+ for (let e = 0; e < s.edgeCount; e++) {
98
+ const inSrc = maskTest(side, src[e]);
99
+ const crosses = directed ? inSrc && !maskTest(side, dst[e]) : inSrc !== maskTest(side, dst[e]);
100
+ if (crosses && (!positiveOnly || capacity[e] > 0)) {
101
+ out.push(e);
102
+ }
103
+ }
104
+ return Uint32Array.from(out);
105
+ }
106
+
107
+ /**
108
+ * The residual graph, kept per NODE PAIR: every arc from u to v is one entry.
109
+ *
110
+ * The residual snapshot is built with `fromEdgeArrays` over `[edges ++ reversed edges]`, so the
111
+ * twin of residual edge `r` is `r + E` below E and `r - E` above it (graph-format design 14.2's
112
+ * flow rule). A directed edge's reversed copy has capacity 0; an undirected edge carries its
113
+ * capacity both ways, so both copies are real. Rows are sorted by target, so the arcs from u to v
114
+ * are one contiguous GROUP, named by its first arc, and all residual state is per group: the
115
+ * capacities of parallel arcs, and of an undirected edge's two directions, add up. That is the
116
+ * legacy Map-of-Maps residual exactly, where `residual.get(u).get(v)` is one number per pair, and
117
+ * it is why the design's four-copy undirected layout is not needed.
118
+ */
119
+ interface Residual {
120
+ readonly rowPtr: U32;
121
+ readonly colIdx: U32;
122
+ /** Residual edge of every arc. */
123
+ readonly arcToEdge: U32;
124
+ /** Residual capacity per group, at the group's first arc. */
125
+ readonly capacity: Float64Array;
126
+ /** The group of every arc. */
127
+ readonly groupOf: U32;
128
+ /** The group of the opposite pair (v to u), per group. */
129
+ readonly twin: U32;
130
+ /**
131
+ * Per node, the groups in the order a search visits them, in the node's own arc range:
132
+ * `order[rowPtr[u] .. rowPtr[u] + placed[u])`.
133
+ */
134
+ readonly order: U32;
135
+ readonly placed: U32;
136
+ /** Whether a group is in its row's visiting order yet. */
137
+ readonly inOrder: Uint8Array;
138
+ /** Whether a group holds at least one real (non-reverse) arc. */
139
+ readonly real: Uint8Array;
140
+ }
141
+
142
+ /**
143
+ * Build the pair-keyed residual graph of `s`. The visiting order starts as the legacy
144
+ * `graphToMap` row order -- each neighbour at the first edge that joins it -- and a group with no
145
+ * real arc is appended to its row when flow first reaches it, where the legacy residual Map
146
+ * inserts the key.
147
+ * @param s - The snapshot
148
+ * @param capacity - Per-edge capacities
149
+ * @returns The residual graph
150
+ */
151
+ function buildResidual(s: GraphSnapshot, capacity: Float64Array): Residual {
152
+ const n = s.nodeCount;
153
+ const E = s.edgeCount;
154
+ const { src, dst } = s.edgeList();
155
+ const rSrc = new Uint32Array(2 * E);
156
+ const rDst = new Uint32Array(2 * E);
157
+ rSrc.set(src);
158
+ rSrc.set(dst, E);
159
+ rDst.set(dst);
160
+ rDst.set(src, E);
161
+ const r = fromEdgeArrays({ directed: true, nodeCount: n, src: rSrc, dst: rDst });
162
+ const { rowPtr, colIdx, arcToEdge, edgeToArc } = r;
163
+ const A = r.arcCount;
164
+ const residual = new Float64Array(A);
165
+ const groupOf = new Uint32Array(A);
166
+ const real = new Uint8Array(A);
167
+ for (let u = 0; u < n; u++) {
168
+ for (let a = rowPtr[u]; a < rowPtr[u + 1]; a++) {
169
+ const g = a > rowPtr[u] && colIdx[a] === colIdx[a - 1] ? groupOf[a - 1] : a;
170
+ groupOf[a] = g;
171
+ const re = arcToEdge[a];
172
+ if (re < E || !s.directed) {
173
+ real[g] = 1;
174
+ residual[g] += Math.max(capacity[re < E ? re : re - E], 0);
175
+ }
176
+ }
177
+ }
178
+ const twin = new Uint32Array(A);
179
+ for (let a = 0; a < A; a++) {
180
+ if (groupOf[a] === a) {
181
+ const re = arcToEdge[a];
182
+ twin[a] = groupOf[edgeToArc[re < E ? re + E : re - E]];
183
+ }
184
+ }
185
+ const order = new Uint32Array(A);
186
+ const placed = new Uint32Array(n);
187
+ const inOrder = new Uint8Array(A);
188
+ const place = (u: number, g: number): void => {
189
+ if (inOrder[g] === 0) {
190
+ inOrder[g] = 1;
191
+ order[rowPtr[u] + placed[u]++] = g;
192
+ }
193
+ };
194
+ for (let e = 0; e < E; e++) {
195
+ place(src[e], groupOf[edgeToArc[e]]);
196
+ if (!s.directed) {
197
+ place(dst[e], groupOf[edgeToArc[e + E]]);
198
+ }
199
+ }
200
+ return { rowPtr, colIdx, arcToEdge, capacity: residual, groupOf, twin, order, placed, inOrder, real };
201
+ }
202
+
203
+ /**
204
+ * Maximum flow from `source` to `sink` by augmenting paths.
205
+ *
206
+ * Paths, bottlenecks and per-edge flows equal the legacy `edmondsKarp` / `fordFulkerson` on a
207
+ * graph with no parallel edges and no two opposite directed edges: the residual is kept per node
208
+ * pair and searched in the legacy row order (see {@link buildResidual}). Where two opposite
209
+ * directed edges carry flow, legacy records every push on the edge in the push's direction, so
210
+ * one edge can show more flow than its capacity; this port reports the net flow of the pair on
211
+ * the edges in its direction instead, each within its capacity.
212
+ * @param s - The snapshot
213
+ * @param source - The source node index
214
+ * @param sink - The sink node index
215
+ * @param options - The path search and the capacity override
216
+ * @returns The flow value, the per-edge flows, the source side and the cut edges
217
+ * @throws RangeError when `source` or `sink` is not a node index, or they are the same node
218
+ * @public
219
+ */
220
+ export function maxFlow(s: GraphSnapshot, source: number, sink: number, options: MaxFlowOptions = {}): MaxFlowResult {
221
+ const n = s.nodeCount;
222
+ if (!(Number.isInteger(source) && source >= 0 && source < n && Number.isInteger(sink) && sink >= 0 && sink < n)) {
223
+ throw new RangeError(`source ${String(source)} and sink ${String(sink)} must be node indices below ${n}`);
224
+ }
225
+ if (source === sink) {
226
+ throw new RangeError(`source and sink are the same node (${source})`);
227
+ }
228
+ const capacity = edgeCapacities(s, options.weights);
229
+ const r = buildResidual(s, capacity);
230
+ const { colIdx, twin } = r;
231
+ // Flow pushed per real group, accumulated as the legacy flow Map accumulates it: a push along
232
+ // a pair with a real arc adds to that pair, a push along a reverse-only pair subtracts from
233
+ // the opposite one.
234
+ const pushed = new Float64Array(colIdx.length);
235
+ const predGroup = new Uint32Array(n);
236
+ const findPath = options.algorithm === "ford-fulkerson" ? depthFirstPath : breadthFirstPath;
237
+ let total = 0;
238
+ while (findPath(r, source, sink, predGroup)) {
239
+ let bottleneck = Infinity;
240
+ for (let v = sink; v !== source; v = colIdx[twin[predGroup[v]]]) {
241
+ bottleneck = Math.min(bottleneck, r.capacity[predGroup[v]]);
242
+ }
243
+ for (let v = sink; v !== source; ) {
244
+ const g = predGroup[v];
245
+ const t = twin[g];
246
+ r.capacity[g] -= bottleneck;
247
+ r.capacity[t] += bottleneck;
248
+ if (r.inOrder[t] === 0) {
249
+ r.inOrder[t] = 1;
250
+ r.order[r.rowPtr[v] + r.placed[v]++] = t;
251
+ }
252
+ if (r.real[g] === 1) {
253
+ pushed[g] += bottleneck;
254
+ } else {
255
+ pushed[t] -= bottleneck;
256
+ }
257
+ v = colIdx[t];
258
+ }
259
+ total += bottleneck;
260
+ }
261
+ const sourceSide = reachable(r, source);
262
+ return {
263
+ maxFlow: total,
264
+ flow: edgeFlows(s, r, capacity, pushed),
265
+ sourceSide,
266
+ cutEdges: crossingEdges(s, sourceSide, s.directed, capacity, true),
267
+ };
268
+ }
269
+
270
+ /**
271
+ * Spread each pair's net flow over its logical edges: all of it on the pair's only edge in the
272
+ * flow's direction, and in arc order up to capacity where there are several (the last one takes
273
+ * the remainder, so rounding never drops flow).
274
+ * @param s - The snapshot
275
+ * @param r - The residual graph after the last augmentation
276
+ * @param capacity - Per-edge capacities
277
+ * @param pushed - Flow pushed per real group
278
+ * @returns Net flow per logical edge in its declared orientation
279
+ */
280
+ function edgeFlows(s: GraphSnapshot, r: Residual, capacity: Float64Array, pushed: Float64Array): Float64Array {
281
+ const E = s.edgeCount;
282
+ const flow = new Float64Array(E);
283
+ const { rowPtr, colIdx, arcToEdge, groupOf } = r;
284
+ for (let u = 0; u < s.nodeCount; u++) {
285
+ for (let g = rowPtr[u]; g < rowPtr[u + 1]; g++) {
286
+ const v = colIdx[g];
287
+ if (groupOf[g] !== g || v <= u) {
288
+ continue;
289
+ }
290
+ const net = pushed[g] - pushed[r.twin[g]];
291
+ if (net === 0) {
292
+ continue;
293
+ }
294
+ // Walk the arcs of the group the flow runs along: u to v, or v to u.
295
+ const along = net > 0 ? g : r.twin[g];
296
+ const end = rowPtr[colIdx[r.twin[along]] + 1];
297
+ let remaining = Math.abs(net);
298
+ let lastEdge = -1;
299
+ let lastSign = 1;
300
+ for (let a = along; a < end && groupOf[a] === along; a++) {
301
+ const re = arcToEdge[a];
302
+ if (re >= E && s.directed) {
303
+ continue; // a reverse arc carries no edge
304
+ }
305
+ if (lastEdge >= 0) {
306
+ const x = Math.min(remaining, Math.max(capacity[lastEdge], 0));
307
+ flow[lastEdge] += lastSign * x;
308
+ remaining -= x;
309
+ }
310
+ lastEdge = re < E ? re : re - E;
311
+ lastSign = re < E ? 1 : -1;
312
+ }
313
+ if (lastEdge >= 0) {
314
+ flow[lastEdge] += lastSign * remaining;
315
+ }
316
+ }
317
+ }
318
+ return flow;
319
+ }
320
+
321
+ /**
322
+ * Minimum s-t cut: the source side and value of a maximum flow (max-flow min-cut theorem). The
323
+ * side is the set reachable from the source in the final residual graph, which is the same for
324
+ * every maximum flow, so the path search does not change it. The search defaults to
325
+ * `"ford-fulkerson"`, as the legacy `minSTCut` uses: on weights that are not binary fractions
326
+ * another search adds the bottlenecks in another order and the value can differ in its last bits.
327
+ * @param s - The snapshot
328
+ * @param source - The source node index
329
+ * @param sink - The sink node index
330
+ * @param options - The path search and the capacity override
331
+ * @returns The cut value, the source side and the cut edges
332
+ * @throws RangeError as {@link maxFlow} does
333
+ * @public
334
+ */
335
+ export function minSTCut(s: GraphSnapshot, source: number, sink: number, options: MaxFlowOptions = {}): MinCutResult {
336
+ const r = maxFlow(s, source, sink, { ...options, algorithm: options.algorithm ?? "ford-fulkerson" });
337
+ return { cutValue: r.maxFlow, side: r.sourceSide, cutEdges: r.cutEdges };
338
+ }
339
+
340
+ /**
341
+ * Breadth-first search for a source-to-sink path over groups with residual capacity, stopping when
342
+ * the sink is discovered (its parent is fixed then, as in the legacy search that stops when it
343
+ * dequeues the sink).
344
+ * @param r - The residual graph
345
+ * @param source - The source node index
346
+ * @param sink - The sink node index
347
+ * @param predGroup - Receives the group that discovered each node on the path
348
+ * @returns Whether the sink was reached
349
+ */
350
+ function breadthFirstPath(r: Residual, source: number, sink: number, predGroup: U32): boolean {
351
+ const { rowPtr, colIdx, order, placed, capacity } = r;
352
+ predGroup.fill(INVALID_INDEX);
353
+ const queue = new Uint32Array(predGroup.length);
354
+ let head = 0;
355
+ let tail = 0;
356
+ queue[tail++] = source;
357
+ while (head < tail) {
358
+ const u = queue[head++];
359
+ const end = rowPtr[u] + placed[u];
360
+ for (let k = rowPtr[u]; k < end; k++) {
361
+ const g = order[k];
362
+ const v = colIdx[g];
363
+ if (capacity[g] > 0 && v !== source && predGroup[v] === INVALID_INDEX) {
364
+ predGroup[v] = g;
365
+ if (v === sink) {
366
+ return true;
367
+ }
368
+ queue[tail++] = v;
369
+ }
370
+ }
371
+ }
372
+ return false;
373
+ }
374
+
375
+ /**
376
+ * Depth-first search for a source-to-sink path over groups with residual capacity, in the order a
377
+ * recursive search takes: the first group with capacity to an unvisited node is followed to its
378
+ * end before the next is tried.
379
+ * @param r - The residual graph
380
+ * @param source - The source node index
381
+ * @param sink - The sink node index
382
+ * @param predGroup - Receives the group that entered each node on the path
383
+ * @returns Whether the sink was reached
384
+ */
385
+ function depthFirstPath(r: Residual, source: number, sink: number, predGroup: U32): boolean {
386
+ const { rowPtr, colIdx, order, placed, capacity } = r;
387
+ const n = predGroup.length;
388
+ predGroup.fill(INVALID_INDEX);
389
+ const visited = new Uint8Array(n);
390
+ const stack = new Uint32Array(n);
391
+ const cursor = new Uint32Array(n);
392
+ let depth = 0;
393
+ stack[depth++] = source;
394
+ visited[source] = 1;
395
+ cursor[source] = rowPtr[source];
396
+ while (depth > 0) {
397
+ const u = stack[depth - 1];
398
+ const k = cursor[u]++;
399
+ if (k >= rowPtr[u] + placed[u]) {
400
+ depth--;
401
+ continue;
402
+ }
403
+ const g = order[k];
404
+ const v = colIdx[g];
405
+ if (capacity[g] > 0 && visited[v] === 0) {
406
+ predGroup[v] = g;
407
+ if (v === sink) {
408
+ return true;
409
+ }
410
+ visited[v] = 1;
411
+ cursor[v] = rowPtr[v];
412
+ stack[depth++] = v;
413
+ }
414
+ }
415
+ return false;
416
+ }
417
+
418
+ /**
419
+ * The nodes reachable from `source` over groups with residual capacity.
420
+ * @param r - The residual graph
421
+ * @param source - The source node index
422
+ * @returns The reachable set as a node mask
423
+ */
424
+ function reachable(r: Residual, source: number): NodeMask {
425
+ const { rowPtr, colIdx, order, placed, capacity } = r;
426
+ const n = rowPtr.length - 1;
427
+ const mask = makeMask(n);
428
+ const queue = new Uint32Array(n);
429
+ let head = 0;
430
+ let tail = 0;
431
+ queue[tail++] = source;
432
+ maskSet(mask, source, true);
433
+ while (head < tail) {
434
+ const u = queue[head++];
435
+ const end = rowPtr[u] + placed[u];
436
+ for (let k = rowPtr[u]; k < end; k++) {
437
+ const v = colIdx[order[k]];
438
+ if (capacity[order[k]] > 0 && !maskTest(mask, v)) {
439
+ maskSet(mask, v, true);
440
+ queue[tail++] = v;
441
+ }
442
+ }
443
+ }
444
+ return mask;
445
+ }
446
+
447
+ /** Result of {@link bipartiteFlowNetwork}. @public */
448
+ export interface BipartiteFlowNetwork {
449
+ /** The directed, unit-capacity (unweighted) flow network. */
450
+ readonly snapshot: GraphSnapshot;
451
+ /** The index of the added source node, id `"__source__"`. */
452
+ readonly source: number;
453
+ /** The index of the added sink node, id `"__sink__"`. */
454
+ readonly sink: number;
455
+ }
456
+
457
+ const SOURCE_ID = "__source__";
458
+ const SINK_ID = "__sink__";
459
+
460
+ /**
461
+ * The unit-capacity flow network whose maximum flow is a maximum bipartite matching: a source with
462
+ * an edge to every left node, every given left-right edge, and an edge from every right node to a
463
+ * sink. Nodes are numbered source, left nodes, left endpoints of `edges` not already listed, right
464
+ * nodes, right endpoints not already listed, sink; a repeated edge is kept once.
465
+ * @param left - The left side's node ids
466
+ * @param right - The right side's node ids
467
+ * @param edges - Left-to-right edges as `[left id, right id]`
468
+ * @returns The network and the indices of its source and sink
469
+ * @throws RangeError when a given id is `"__source__"` or `"__sink__"`
470
+ * @public
471
+ */
472
+ export function bipartiteFlowNetwork(
473
+ left: readonly NodeId[],
474
+ right: readonly NodeId[],
475
+ edges: readonly (readonly [NodeId, NodeId])[],
476
+ ): BipartiteFlowNetwork {
477
+ const b = new GraphBuilder({ directed: true, duplicateEdges: "first" });
478
+ const add = (id: NodeId): number => {
479
+ if (id === SOURCE_ID || id === SINK_ID) {
480
+ throw new RangeError(`node id "${id}" is reserved for the flow network's source or sink`);
481
+ }
482
+ return b.addNode(id);
483
+ };
484
+ const source = b.addNode(SOURCE_ID);
485
+ const leftIndex = left.map(add);
486
+ const edgeLeft = edges.map(([u]) => add(u));
487
+ const rightIndex = right.map(add);
488
+ const edgeRight = edges.map(([, v]) => add(v));
489
+ const sink = b.addNode(SINK_ID);
490
+ for (const u of leftIndex) {
491
+ b.addEdgeByIndex(source, u);
492
+ }
493
+ for (let e = 0; e < edges.length; e++) {
494
+ b.addEdgeByIndex(edgeLeft[e], edgeRight[e]);
495
+ }
496
+ for (const v of rightIndex) {
497
+ b.addEdgeByIndex(v, sink);
498
+ }
499
+ return { snapshot: b.freeze({ label: "algorithms.bipartiteFlowNetwork" }), source, sink };
500
+ }
@@ -0,0 +1,120 @@
1
+ import {
2
+ type EdgeMask,
3
+ type F64,
4
+ type GraphSnapshot,
5
+ makeMask,
6
+ maskCount,
7
+ maskSet,
8
+ maskTest,
9
+ type U32,
10
+ } from "@graphty/graph-format";
11
+
12
+ import { edgeBetweennessCentrality } from "./betweenness.js";
13
+ import { arcWeightsOf, exactEdgeWeights } from "./label-propagation.js";
14
+ import { modularity } from "./modularity.js";
15
+ import { IntUnionFind } from "./structures/union-find.js";
16
+
17
+ /** Options of the index-based Girvan-Newman, matching the legacy `girvanNewman`. @public */
18
+ export interface GirvanNewmanOptions {
19
+ /** Stop once a level has at least this many communities of `minCommunitySize` or more nodes. */
20
+ readonly maxCommunities?: number | undefined;
21
+ /** Communities smaller than this do not count towards `maxCommunities`; default 1. */
22
+ readonly minCommunitySize?: number | undefined;
23
+ /** Cap on rounds of edge removal; default 100. */
24
+ readonly maxIterations?: number | undefined;
25
+ }
26
+
27
+ /** The Girvan-Newman dendrogram: one partition per level, the uncut graph first. @public */
28
+ export interface GirvanNewmanResult {
29
+ /** Dense community label per node index at each level, in first-seen order. */
30
+ readonly levels: U32[];
31
+ /** Modularity of each level's partition over the original graph. */
32
+ readonly modularity: F64;
33
+ }
34
+
35
+ /** Scores this close to the maximum are removed together, as the legacy function does. */
36
+ const TIE = 1e-10;
37
+
38
+ /**
39
+ * Connected components over the edges still alive.
40
+ * @param s - The snapshot
41
+ * @param alive - Kept edges
42
+ * @returns Dense labels and their count
43
+ */
44
+ function aliveComponents(s: GraphSnapshot, alive: EdgeMask): { labels: U32; count: number } {
45
+ const uf = new IntUnionFind(s.nodeCount);
46
+ const el = s.edgeList();
47
+ for (let e = 0; e < s.edgeCount; e++) {
48
+ if (maskTest(alive, e)) {
49
+ uf.union(el.src[e], el.dst[e]);
50
+ }
51
+ }
52
+ return uf.toLabels();
53
+ }
54
+
55
+ /**
56
+ * Girvan-Newman divisive community detection (PNAS 99:7821, 2002) over an undirected snapshot:
57
+ * repeatedly delete every edge whose betweenness is the maximum (within 1e-10), recording the
58
+ * connected components after each round as one level of a dendrogram.
59
+ *
60
+ * Edges are deleted by clearing bits of an alive mask over logical edges that the edge betweenness
61
+ * reads, so no graph is rebuilt. Betweenness is unweighted, as in the legacy function; a pair of
62
+ * nodes joined by parallel edges is one neighbour relation whose share goes to the first alive
63
+ * parallel, so the parallels are deleted one round at a time. Modularity reads the edge weights,
64
+ * the exact f64 ones when the snapshot keeps them, with graph-format's `weightedDegree()` rule (a
65
+ * self-loop counts twice); on a graph without self-loops it equals the legacy function's.
66
+ *
67
+ * The run stops when no edge is left, after `maxIterations` rounds, when a level has
68
+ * `maxCommunities` communities of at least `minCommunitySize` nodes, or when that count reaches
69
+ * the node count. Every level is returned whole: the legacy function drops communities below
70
+ * `minCommunitySize` from the lists it returns, which a caller does with `levels[i]` and a size
71
+ * count.
72
+ * @param s - An undirected snapshot
73
+ * @param options - Stopping rules
74
+ * @returns The levels and their modularity
75
+ * @throws Error on a directed snapshot
76
+ * @public
77
+ */
78
+ export function girvanNewman(s: GraphSnapshot, options: GirvanNewmanOptions = {}): GirvanNewmanResult {
79
+ if (s.directed) {
80
+ throw new Error("Girvan-Newman requires an undirected graph. Pass s.toUndirected().snapshot.");
81
+ }
82
+ const maxCommunities = options.maxCommunities ?? 0;
83
+ const minCommunitySize = options.minCommunitySize ?? 1;
84
+ const maxIterations = options.maxIterations ?? 100;
85
+ const { nodeCount: n, edgeCount } = s;
86
+ const weights = arcWeightsOf(s, exactEdgeWeights(s));
87
+ const alive = makeMask(edgeCount, true);
88
+ const levels: U32[] = [];
89
+ const scores: number[] = [];
90
+ const record = (): number => {
91
+ const { labels, count } = aliveComponents(s, alive);
92
+ levels.push(labels);
93
+ scores.push(modularity(s, labels, weights === null ? {} : { weights }));
94
+ const sizes = new Uint32Array(count);
95
+ for (let u = 0; u < n; u++) {
96
+ sizes[labels[u]]++;
97
+ }
98
+ return sizes.filter((size) => size >= minCommunitySize).length;
99
+ };
100
+ record();
101
+ for (let round = 0; round < maxIterations && maskCount(alive, edgeCount) > 0; round++) {
102
+ const betweenness = edgeBetweennessCentrality(s, { alive }).scores;
103
+ let max = -Infinity;
104
+ for (let e = 0; e < edgeCount; e++) {
105
+ if (maskTest(alive, e) && betweenness[e] > max) {
106
+ max = betweenness[e];
107
+ }
108
+ }
109
+ for (let e = 0; e < edgeCount; e++) {
110
+ if (maskTest(alive, e) && Math.abs(betweenness[e] - max) < TIE) {
111
+ maskSet(alive, e, false);
112
+ }
113
+ }
114
+ const valid = record();
115
+ if ((maxCommunities > 0 && valid >= maxCommunities) || valid === n) {
116
+ break;
117
+ }
118
+ }
119
+ return { levels, modularity: Float64Array.from(scores) };
120
+ }