@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,342 @@
1
+ import { type F64, type GraphSnapshot, INVALID_INDEX, type NumericVector, type U32 } from "@graphty/graph-format";
2
+
3
+ import { PathWalkError } from "../errors.js";
4
+ import { walkPredArcs, walkPredEdges } from "./dijkstra.js";
5
+ import { IndexedMinHeap } from "./structures/min-heap.js";
6
+
7
+ /** The default `maxNodes`: the GPU kernel's ceiling at WebGPU's 128 MiB storage binding, floor(sqrt(2^27 / 4)). */
8
+ export const APSP_DEFAULT_MAX_NODES = 5792;
9
+
10
+ /** Options of the index-based all-pairs shortest paths. @public */
11
+ export interface ApspOptions {
12
+ /**
13
+ * Per-arc weight override, arcCount long; defaults to `s.weights` (null = 1 per arc). The
14
+ * snapshot's weights are f32, whose rounding can change which path is shortest; pass the exact
15
+ * f64 weights here to match the shipped `floydWarshall`.
16
+ */
17
+ readonly weights?: NumericVector | undefined;
18
+ /** `false` counts hops and ignores every weight. Default true. */
19
+ readonly weighted?: boolean | undefined;
20
+ /**
21
+ * Strategy override. `"auto"` (the default): BFS rows on unit weights, Floyd-Warshall on any
22
+ * negative weight, Dijkstra rows below arcCount n^2 / 3, Floyd-Warshall otherwise.
23
+ * `"floyd-warshall"` always sweeps; `"per-source"` runs BFS or Dijkstra rows and throws on a
24
+ * negative weight.
25
+ */
26
+ readonly method?: "auto" | "floyd-warshall" | "per-source" | undefined;
27
+ /** Record `predArc` so `pathTo` / `pathEdges` work; adds 4 n^2 bytes. Default false. */
28
+ readonly paths?: boolean | undefined;
29
+ /** Refuse larger graphs before allocating. Default 5,792. */
30
+ readonly maxNodes?: number | undefined;
31
+ }
32
+
33
+ /** All-pairs shortest paths as a dense row-major matrix. @public */
34
+ export interface ApspResult {
35
+ /** dist[i * n + j] = distance from i to j; +Infinity unreachable, 0 on the diagonal, NaN under a negative cycle. */
36
+ readonly dist: F64;
37
+ /** The side of the matrix, s.nodeCount. */
38
+ readonly n: number;
39
+ /** True when a negative cycle exists; dist is then all NaN and the path accessors throw. */
40
+ readonly hasNegativeCycle: boolean;
41
+ /** The strategy that ran. */
42
+ readonly method: "bfs" | "dijkstra" | "floyd-warshall";
43
+ /** The arc ending each shortest path, row-major; INVALID_INDEX on the diagonal and when unreachable. Null unless `paths: true`. */
44
+ readonly predArc: U32 | null;
45
+ /**
46
+ * Node indices from `source` to `target` inclusive; empty when unreachable.
47
+ * @param source - The row's node index
48
+ * @param target - The column's node index
49
+ */
50
+ pathTo(source: number, target: number): U32;
51
+ /**
52
+ * Logical edge indices along that path; empty when unreachable or when source === target.
53
+ * @param source - The row's node index
54
+ * @param target - The column's node index
55
+ */
56
+ pathEdges(source: number, target: number): U32;
57
+ }
58
+
59
+ /**
60
+ * The weights in use, after the checks of design section 5.2.
61
+ * @param s - The snapshot
62
+ * @param options - The caller's options
63
+ * @returns The weight vector, or `null` when every weight is 1 (or weights are ignored), and whether one is negative
64
+ */
65
+ function weightsInUse(s: GraphSnapshot, options: ApspOptions): { w: NumericVector | null; negative: boolean } {
66
+ if (options.weighted === false) {
67
+ return { w: null, negative: false };
68
+ }
69
+ if (options.weights !== undefined && options.weights.length !== s.arcCount) {
70
+ throw new RangeError(
71
+ `allPairsShortestPath: the weights override has ${String(options.weights.length)} entries; the snapshot has ${String(s.arcCount)} arcs`,
72
+ );
73
+ }
74
+ const w = options.weights ?? s.weights;
75
+ if (w === null) {
76
+ return { w: null, negative: false };
77
+ }
78
+ let unit = true;
79
+ let negative = false;
80
+ for (let a = 0; a < s.arcCount; a++) {
81
+ const x = w[a];
82
+ if (!Number.isFinite(x)) {
83
+ // A finite weight above 3.4e38 reaches the f32 arc column as Infinity.
84
+ const hint =
85
+ options.weights === undefined && !Number.isNaN(x)
86
+ ? "; a finite weight above the f32 range (3.4e38) becomes Infinity in the snapshot's f32 arc weights -- pass the exact f64 weights through the weights override"
87
+ : "";
88
+ throw new RangeError(
89
+ `allPairsShortestPath: arc ${String(a)} has weight ${String(x)}; weights must be finite${hint}`,
90
+ );
91
+ }
92
+ unit &&= x === 1;
93
+ negative ||= x < 0;
94
+ }
95
+ return { w: unit ? null : w, negative };
96
+ }
97
+
98
+ /**
99
+ * Floyd-Warshall in place (design section 3.1): k-i-j over the row-major matrix, row offsets
100
+ * hoisted, a row skipped when `d[i][k]` is +Infinity, strict `<` so the smallest pivot wins ties.
101
+ * With a negative weight in play the diagonal is scanned after every round and the sweep stops at
102
+ * the first negative entry (design section 3.3), before any value can run away.
103
+ * @param s - The snapshot
104
+ * @param w - The weights in use, or `null` for 1 per arc
105
+ * @param negative - Whether some weight is negative, so a cycle is possible
106
+ * @param d - The n x n output, overwritten
107
+ * @param p - The n x n predecessor arcs, filled with INVALID_INDEX, or `null` when paths are off
108
+ * @returns True when a negative cycle was found; `d` is then partial
109
+ */
110
+ function floydWarshall(s: GraphSnapshot, w: NumericVector | null, negative: boolean, d: F64, p: U32 | null): boolean {
111
+ const { nodeCount: n, rowPtr, colIdx } = s;
112
+ d.fill(Infinity);
113
+ for (let u = 0; u < n; u++) {
114
+ const ur = u * n;
115
+ for (let a = rowPtr[u]; a < rowPtr[u + 1]; a++) {
116
+ const v = colIdx[a];
117
+ const x = w === null ? 1 : w[a];
118
+ if (v !== u && x < d[ur + v]) {
119
+ d[ur + v] = x;
120
+ if (p !== null) {
121
+ p[ur + v] = a;
122
+ }
123
+ }
124
+ }
125
+ d[ur + u] = 0;
126
+ }
127
+ for (let k = 0; k < n; k++) {
128
+ const kr = k * n;
129
+ // Row views, not d[kr + j]: 12 to 19 percent faster at every measured size (design section 12).
130
+ const dk = d.subarray(kr, kr + n);
131
+ for (let i = 0; i < n; i++) {
132
+ const ir = i * n;
133
+ const dik = d[ir + k];
134
+ if (dik === Infinity) {
135
+ continue;
136
+ }
137
+ const di = d.subarray(ir, ir + n);
138
+ // two copies of the j loop so the common no-paths sweep carries no per-cell branch
139
+ if (p === null) {
140
+ for (let j = 0; j < di.length; j++) {
141
+ const via = dik + dk[j];
142
+ if (via < di[j]) {
143
+ di[j] = via;
144
+ }
145
+ }
146
+ } else {
147
+ const pk = p.subarray(kr, kr + n);
148
+ const pi = p.subarray(ir, ir + n);
149
+ for (let j = 0; j < di.length; j++) {
150
+ const via = dik + dk[j];
151
+ if (via < di[j]) {
152
+ di[j] = via;
153
+ pi[j] = pk[j];
154
+ }
155
+ }
156
+ }
157
+ }
158
+ if (negative) {
159
+ for (let i = 0; i < n; i++) {
160
+ if (d[i * n + i] < 0) {
161
+ return true;
162
+ }
163
+ }
164
+ }
165
+ }
166
+ return false;
167
+ }
168
+
169
+ /**
170
+ * The negative cycles the weights alone prove (design section 3.3): a negative self-loop, or any
171
+ * negative weight on an undirected snapshot (u-v-u).
172
+ * @param s - The snapshot
173
+ * @param w - The weights in use
174
+ * @returns True when such a cycle exists
175
+ */
176
+ function negativeCycleFromWeights(s: GraphSnapshot, w: NumericVector): boolean {
177
+ for (let u = 0; u < s.nodeCount; u++) {
178
+ for (let a = s.rowPtr[u]; a < s.rowPtr[u + 1]; a++) {
179
+ if (w[a] < 0 && (!s.directed || s.colIdx[a] === u)) {
180
+ return true;
181
+ }
182
+ }
183
+ }
184
+ return false;
185
+ }
186
+
187
+ /**
188
+ * One breadth-first search per source, hop counts written straight into row `src` of `d`. One
189
+ * queue of n entries serves every source.
190
+ * @param s - The snapshot
191
+ * @param d - The n x n output, overwritten
192
+ * @param p - The n x n predecessor arcs, filled with INVALID_INDEX, or `null`: the discovering arc
193
+ */
194
+ function bfsRows(s: GraphSnapshot, d: F64, p: U32 | null): void {
195
+ const { nodeCount: n, rowPtr, colIdx } = s;
196
+ const queue = new Uint32Array(n);
197
+ d.fill(Infinity);
198
+ for (let src = 0; src < n; src++) {
199
+ const row = d.subarray(src * n, src * n + n);
200
+ const prow = p?.subarray(src * n, src * n + n);
201
+ row[src] = 0;
202
+ queue[0] = src;
203
+ let tail = 1;
204
+ for (let head = 0; head < tail; head++) {
205
+ const u = queue[head];
206
+ const next = row[u] + 1;
207
+ for (let a = rowPtr[u]; a < rowPtr[u + 1]; a++) {
208
+ const v = colIdx[a];
209
+ if (row[v] === Infinity) {
210
+ row[v] = next;
211
+ if (prow !== undefined) {
212
+ prow[v] = a;
213
+ }
214
+ queue[tail++] = v;
215
+ }
216
+ }
217
+ }
218
+ }
219
+ }
220
+
221
+ /**
222
+ * One Dijkstra per source, relaxing straight into row `src` of `d`. One heap serves every source:
223
+ * a drained heap is empty again. Not `indexed.dijkstra` per source, which would allocate two O(n)
224
+ * arrays, a heap and two closures per source and then copy the row (design section 4).
225
+ * @param s - The snapshot
226
+ * @param w - The weights in use, all non-negative
227
+ * @param d - The n x n output, overwritten
228
+ * @param p - The n x n predecessor arcs, filled with INVALID_INDEX, or `null`: the relaxing arc
229
+ */
230
+ function dijkstraRows(s: GraphSnapshot, w: NumericVector, d: F64, p: U32 | null): void {
231
+ const { nodeCount: n, rowPtr, colIdx } = s;
232
+ const heap = new IndexedMinHeap(n);
233
+ d.fill(Infinity);
234
+ for (let src = 0; src < n; src++) {
235
+ const row = d.subarray(src * n, src * n + n);
236
+ const prow = p?.subarray(src * n, src * n + n);
237
+ row[src] = 0;
238
+ heap.push(src, 0);
239
+ while (!heap.isEmpty()) {
240
+ const u = heap.pop();
241
+ const du = row[u];
242
+ for (let a = rowPtr[u]; a < rowPtr[u + 1]; a++) {
243
+ const v = colIdx[a];
244
+ const dv = du + w[a];
245
+ if (dv < row[v]) {
246
+ row[v] = dv;
247
+ if (prow !== undefined) {
248
+ prow[v] = a;
249
+ }
250
+ heap.pushOrDecrease(v, dv);
251
+ }
252
+ }
253
+ }
254
+ }
255
+ }
256
+
257
+ /**
258
+ * The strategy rule of design section 4.
259
+ * @param s - The snapshot
260
+ * @param w - The weights in use, or `null` for 1 per arc
261
+ * @param negative - Whether some weight is negative
262
+ * @param method - The caller's override
263
+ * @returns The strategy to run
264
+ */
265
+ function pickStrategy(
266
+ s: GraphSnapshot,
267
+ w: NumericVector | null,
268
+ negative: boolean,
269
+ method: ApspOptions["method"],
270
+ ): ApspResult["method"] {
271
+ if (method === "floyd-warshall") {
272
+ return "floyd-warshall";
273
+ }
274
+ if (w === null) {
275
+ return "bfs";
276
+ }
277
+ if (negative) {
278
+ if (method === "per-source") {
279
+ throw new Error(
280
+ 'allPairsShortestPath: method "per-source" runs Dijkstra, which is incorrect with a negative weight; use "auto" or "floyd-warshall"',
281
+ );
282
+ }
283
+ return "floyd-warshall";
284
+ }
285
+ // The measured crossover (design section 12): n^2 / 3 arcs, above SciPy's n^2 / 4. Integer form, no rounding.
286
+ const n = s.nodeCount;
287
+ return method === "per-source" || 3 * s.arcCount < n * n ? "dijkstra" : "floyd-warshall";
288
+ }
289
+
290
+ /**
291
+ * All-pairs shortest paths over a snapshot.
292
+ * @param s - The snapshot
293
+ * @param options - Weights, strategy, paths and the size bound
294
+ * @returns The distance matrix and the path accessors
295
+ * @public
296
+ */
297
+ export function allPairsShortestPath(s: GraphSnapshot, options: ApspOptions = {}): ApspResult {
298
+ const n = s.nodeCount;
299
+ const maxNodes = options.maxNodes ?? APSP_DEFAULT_MAX_NODES;
300
+ // Written negated so a NaN maxNodes refuses rather than switching the bound off.
301
+ if (!(n <= maxNodes)) {
302
+ const bytes = (options.paths === true ? 12 : 8) * n * n;
303
+ throw new RangeError(
304
+ `allPairsShortestPath: ${String(n)} nodes exceeds maxNodes ${String(maxNodes)}; the result would allocate ${String(bytes)} bytes. Pass a larger maxNodes to allow it.`,
305
+ );
306
+ }
307
+ const { w, negative } = weightsInUse(s, options);
308
+ const method = pickStrategy(s, w, negative, options.method);
309
+ const dist = new Float64Array(n * n);
310
+ const predArc = options.paths === true ? new Uint32Array(n * n).fill(INVALID_INDEX) : null;
311
+ let hasNegativeCycle = false;
312
+ if (method === "bfs") {
313
+ bfsRows(s, dist, predArc);
314
+ } else if (method === "dijkstra" && w !== null) {
315
+ dijkstraRows(s, w, dist, predArc);
316
+ } else {
317
+ hasNegativeCycle =
318
+ (negative && w !== null && negativeCycleFromWeights(s, w)) || floydWarshall(s, w, negative, dist, predArc);
319
+ }
320
+ if (hasNegativeCycle) {
321
+ dist.fill(NaN);
322
+ }
323
+ // Row i of predArc is a single-source predecessor-arc array, so the SSSP walkers apply to it.
324
+ const row = (source: number, target: number): U32 => {
325
+ if (predArc === null) {
326
+ throw new Error("allPairsShortestPath: pass paths: true to walk shortest paths");
327
+ }
328
+ if (hasNegativeCycle) {
329
+ throw new PathWalkError(source, target, "cycle");
330
+ }
331
+ return predArc.subarray(source * n, source * n + n);
332
+ };
333
+ return {
334
+ dist,
335
+ n,
336
+ hasNegativeCycle,
337
+ method,
338
+ predArc,
339
+ pathTo: (source, target) => walkPredArcs(s, row(source, target), source, target),
340
+ pathEdges: (source, target) => walkPredEdges(s, row(source, target), source, target),
341
+ };
342
+ }
@@ -0,0 +1,72 @@
1
+ import { type AdjacencyView, INVALID_INDEX, type NumericVector, type U32 } from "@graphty/graph-format";
2
+
3
+ import { type SsspOptions, type SsspResult, walkPredArcs, walkPredEdges } from "./dijkstra.js";
4
+
5
+ /** Result of the index-based Bellman-Ford: the SSSP result plus the negative-cycle flag. @public */
6
+ export interface BellmanFordResult extends SsspResult {
7
+ /**
8
+ * True when a negative cycle is reachable from the source. `dist` and `predArc` then hold the
9
+ * state after `nodeCount - 1` rounds, which are not shortest paths, and a path accessor may throw
10
+ * `PathWalkError` on a predecessor chain that loops.
11
+ */
12
+ readonly hasNegativeCycle: boolean;
13
+ }
14
+
15
+ /**
16
+ * Bellman-Ford single-source shortest paths, index-based, for weights that may be negative. It
17
+ * relaxes every arc, so an undirected edge is relaxed both ways (it has an arc in each direction),
18
+ * and one negative undirected edge is therefore a negative cycle. Rounds stop early once one changes
19
+ * nothing; after `nodeCount - 1` rounds one more round decides `hasNegativeCycle`. The predecessor
20
+ * is the relaxing ARC, as in `dijkstra`, so a parallel edge on a path is identified exactly.
21
+ * @param g - The adjacency to search
22
+ * @param source - The node index to start from
23
+ * @param options - Cutoff (a relaxation beyond it is skipped) and per-arc weight override
24
+ * @returns The distances, the predecessor arcs, the path accessors and the negative-cycle flag
25
+ * @public
26
+ */
27
+ export function bellmanFord(g: AdjacencyView, source: number, options: SsspOptions = {}): BellmanFordResult {
28
+ const { nodeCount, rowPtr, colIdx } = g;
29
+ const weights: NumericVector | null = options.weights ?? g.weights;
30
+ const cutoff = options.cutoff ?? Infinity;
31
+ const dist = new Float64Array(nodeCount).fill(Infinity);
32
+ const predArc = new Uint32Array(nodeCount).fill(INVALID_INDEX);
33
+ dist[source] = 0;
34
+
35
+ /**
36
+ * One pass over every arc leaving a reached node.
37
+ * @returns True when any distance dropped
38
+ */
39
+ const round = (): boolean => {
40
+ let changed = false;
41
+ for (let u = 0; u < nodeCount; u++) {
42
+ const du = dist[u];
43
+ if (du === Infinity) {
44
+ continue;
45
+ }
46
+ const end = rowPtr[u + 1];
47
+ for (let a = rowPtr[u]; a < end; a++) {
48
+ const v = colIdx[a];
49
+ const dv = du + (weights === null ? 1 : weights[a]);
50
+ if (dv < dist[v] && dv <= cutoff) {
51
+ dist[v] = dv;
52
+ predArc[v] = a;
53
+ changed = true;
54
+ }
55
+ }
56
+ }
57
+ return changed;
58
+ };
59
+
60
+ let settled = false;
61
+ for (let i = 0; i < nodeCount - 1 && !settled; i++) {
62
+ settled = !round();
63
+ }
64
+ const hasNegativeCycle = !settled && round();
65
+ return {
66
+ dist,
67
+ predArc,
68
+ hasNegativeCycle,
69
+ pathTo: (target: number): U32 => walkPredArcs(g, predArc, source, target),
70
+ pathEdges: (target: number): U32 => walkPredEdges(g, predArc, source, target),
71
+ };
72
+ }
@@ -0,0 +1,258 @@
1
+ import { type EdgeMask, type F64, type GraphSnapshot, maskTest } from "@graphty/graph-format";
2
+
3
+ /** Options of the index-based node betweenness. @public */
4
+ export interface BetweennessOptions {
5
+ /**
6
+ * Divide by the number of ordered pairs a node can sit between: `(n - 1)(n - 2)` directed, half that
7
+ * undirected; with `endpoints`, `n (n - 1)` and half that. Default false.
8
+ */
9
+ readonly normalized?: boolean | undefined;
10
+ /**
11
+ * Count a path's two ends as lying on it, as NetworkX's `endpoints=True` does. Default false. The legacy
12
+ * `betweennessCentrality` accepts this option and ignores it.
13
+ */
14
+ readonly endpoints?: boolean | undefined;
15
+ /** Sampled betweenness: the source node indices to run from. Duplicates run twice. */
16
+ readonly sources?: readonly number[] | undefined;
17
+ /**
18
+ * Sampled betweenness: how many distinct sources to draw when `sources` is not given. The draw is
19
+ * deterministic -- the same `(n, k)` draws the same sources every time, and the dispatcher hands an
20
+ * accelerator the drawn sources, so both paths run the same ones. With `sources` it must equal `sources.length`.
21
+ */
22
+ readonly k?: number | undefined;
23
+ }
24
+
25
+ /** Options of the index-based edge betweenness. @public */
26
+ export interface EdgeBetweennessOptions {
27
+ /** Divide by `(n - 1)(n - 2)` directed, half that undirected, as the node scores are. Default false. */
28
+ readonly normalized?: boolean | undefined;
29
+ /** Sampled edge betweenness: the source node indices to run from. */
30
+ readonly sources?: readonly number[] | undefined;
31
+ /** Sampled edge betweenness: how many sources to draw; see {@link BetweennessOptions.k}. */
32
+ readonly k?: number | undefined;
33
+ /**
34
+ * Logical edges to keep, as a packed mask over `edgeCount`; a cleared edge is treated as deleted and scores
35
+ * 0. Girvan-Newman removes edges this way without building a new snapshot.
36
+ */
37
+ readonly alive?: EdgeMask | undefined;
38
+ }
39
+
40
+ /** Scores per node index from an exact, non-iterative computation. @public */
41
+ export interface ScoresResult {
42
+ /** One score per node index. */
43
+ readonly scores: F64;
44
+ /** The number of breadth-first searches or shortest-path searches run: one per source. */
45
+ readonly iterations: number;
46
+ /** Always true: nothing here iterates to a tolerance. */
47
+ readonly converged: true;
48
+ }
49
+
50
+ /** Scores per logical edge index. @public */
51
+ export interface EdgeScoresResult {
52
+ /** One score per logical edge. */
53
+ readonly scores: F64;
54
+ }
55
+
56
+ /** The seed of the `k` draw. Any fixed value works: the draw only has to repeat. */
57
+ const SAMPLE_SEED = 0x9e3779b9;
58
+
59
+ /**
60
+ * `k` distinct node indices by a partial Fisher-Yates shuffle over a fixed-seed mulberry32 stream, so the same
61
+ * `(n, k)` draws the same sources every time.
62
+ * @param n - The node count
63
+ * @param k - How many to draw, at most n
64
+ * @returns The sources
65
+ */
66
+ function drawSources(n: number, k: number): number[] {
67
+ const pool = Array.from({ length: n }, (_, i) => i);
68
+ let state = SAMPLE_SEED;
69
+ for (let i = 0; i < k; i++) {
70
+ state = (state + 0x6d2b79f5) >>> 0;
71
+ let t = Math.imul(state ^ (state >>> 15), state | 1);
72
+ t = (t + Math.imul(t ^ (t >>> 7), t | 61)) ^ t;
73
+ const unit = ((t ^ (t >>> 14)) >>> 0) / 2 ** 32;
74
+ const j = i + Math.floor(unit * (n - i));
75
+ [pool[i], pool[j]] = [pool[j], pool[i]];
76
+ }
77
+ return pool.slice(0, k);
78
+ }
79
+
80
+ /**
81
+ * The sources a call runs: `sources` as given, else `k` drawn, else every node.
82
+ * @param n - The node count
83
+ * @param sources - The caller's list
84
+ * @param k - The caller's count
85
+ * @returns The sources
86
+ * @throws RangeError for a source outside `[0, n)`, a `k` outside `[0, n]`, or a `k` that disagrees with the list
87
+ */
88
+ export function resolveSources(
89
+ n: number,
90
+ sources: readonly number[] | undefined,
91
+ k: number | undefined,
92
+ ): readonly number[] {
93
+ if (k !== undefined && (!Number.isInteger(k) || k < 0 || k > n)) {
94
+ throw new RangeError(`betweenness: k must be an integer in [0, ${n}], got ${k}`);
95
+ }
96
+ if (sources !== undefined) {
97
+ for (const v of sources) {
98
+ if (!Number.isInteger(v) || v < 0 || v >= n) {
99
+ throw new RangeError(`betweenness: sources must be node indices in [0, ${n}), got ${v}`);
100
+ }
101
+ }
102
+ if (k !== undefined && k !== sources.length) {
103
+ throw new RangeError(`betweenness: k (${k}) must be absent or equal sources.length (${sources.length})`);
104
+ }
105
+ return sources;
106
+ }
107
+ return k === undefined ? Array.from({ length: n }, (_, i) => i) : drawSources(n, k);
108
+ }
109
+
110
+ /**
111
+ * Brandes' accumulation from every source, into `node` and/or `edge` (raw sums, before halving or
112
+ * normalising).
113
+ *
114
+ * A pair joined by parallel edges is ONE neighbour relation (graph-format design 3.5, the adjacent-skip idiom),
115
+ * so path counts are the simple graph's and equal the legacy function's. The pair's share of an edge score goes
116
+ * to the first alive parallel edge in row order; the others score 0. Removing that edge hands the whole share to
117
+ * the next parallel, so the scores of a pair's parallels always sum to the simple graph's edge score.
118
+ * Dependencies are pulled over OUT-arcs from the deepest level up, so no reverse adjacency is built.
119
+ * @param s - The snapshot
120
+ * @param sources - Source node indices
121
+ * @param node - Node sums, or null
122
+ * @param edge - Edge sums, or null
123
+ * @param alive - Kept edges, or null for all
124
+ * @param endpoints - Count path ends on the path (node sums only)
125
+ */
126
+ function accumulate(
127
+ s: GraphSnapshot,
128
+ sources: readonly number[],
129
+ node: F64 | null,
130
+ edge: F64 | null,
131
+ alive: EdgeMask | null,
132
+ endpoints: boolean,
133
+ ): void {
134
+ const { nodeCount: n, rowPtr, colIdx, arcToEdge } = s;
135
+ const depth = new Int32Array(n).fill(-1);
136
+ const sigma = new Float64Array(n);
137
+ const delta = new Float64Array(n);
138
+ const order = new Uint32Array(n);
139
+ for (const source of sources) {
140
+ let head = 0;
141
+ let tail = 0;
142
+ depth[source] = 0;
143
+ sigma[source] = 1;
144
+ order[tail++] = source;
145
+ while (head < tail) {
146
+ const v = order[head++];
147
+ const next = depth[v] + 1;
148
+ let prev = -1;
149
+ for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
150
+ const w = colIdx[a];
151
+ if (w === prev || (alive !== null && !maskTest(alive, arcToEdge[a]))) {
152
+ continue;
153
+ }
154
+ prev = w;
155
+ if (depth[w] < 0) {
156
+ depth[w] = next;
157
+ order[tail++] = w;
158
+ }
159
+ if (depth[w] === next) {
160
+ sigma[w] += sigma[v];
161
+ }
162
+ }
163
+ }
164
+ for (let i = tail - 1; i >= 0; i--) {
165
+ const v = order[i];
166
+ const next = depth[v] + 1;
167
+ let prev = -1;
168
+ for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
169
+ const w = colIdx[a];
170
+ if (w === prev || (alive !== null && !maskTest(alive, arcToEdge[a]))) {
171
+ continue;
172
+ }
173
+ prev = w;
174
+ if (depth[w] === next) {
175
+ const c = (sigma[v] / sigma[w]) * (1 + delta[w]);
176
+ delta[v] += c;
177
+ if (edge !== null) {
178
+ edge[arcToEdge[a]] += c;
179
+ }
180
+ }
181
+ }
182
+ if (node !== null && v !== source) {
183
+ node[v] += endpoints ? delta[v] + 1 : delta[v];
184
+ }
185
+ }
186
+ if (node !== null && endpoints) {
187
+ node[source] += tail - 1;
188
+ }
189
+ for (let i = 0; i < tail; i++) {
190
+ const v = order[i];
191
+ depth[v] = -1;
192
+ sigma[v] = 0;
193
+ delta[v] = 0;
194
+ }
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Halve on an undirected snapshot (each unordered pair was counted from both ends) and divide by `factor` when
200
+ * normalising and it is positive.
201
+ * @param s - The snapshot
202
+ * @param scores - The raw sums, scaled in place
203
+ * @param normalized - Whether to normalise
204
+ * @param factor - The directed pair count to normalise by
205
+ */
206
+ function scale(s: GraphSnapshot, scores: F64, normalized: boolean | undefined, factor: number): void {
207
+ // Undirected: halving and then dividing by half the directed pair count is dividing by the whole count.
208
+ const halve = s.directed ? 1 : 2;
209
+ const divisor = normalized === true && factor > 0 ? factor : halve;
210
+ for (let i = 0; i < scores.length; i++) {
211
+ scores[i] /= divisor;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Node betweenness centrality by Brandes' algorithm: breadth-first, so weights are ignored, as in the legacy
217
+ * `betweennessCentrality`, whose scores this equals on every graph it can hold. A sampled run (`sources` or `k`)
218
+ * is the UNSCALED sum over the sources run, as the WebGPU accelerator reports it.
219
+ * @param s - The snapshot
220
+ * @param options - Normalisation, endpoints and sampling
221
+ * @returns One score per node index; `iterations` is the number of sources run
222
+ * @throws RangeError for a bad `sources` or `k`
223
+ * @public
224
+ */
225
+ export function betweennessCentrality(s: GraphSnapshot, options: BetweennessOptions = {}): ScoresResult {
226
+ const n = s.nodeCount;
227
+ const sources = resolveSources(n, options.sources, options.k);
228
+ const scores = new Float64Array(n);
229
+ const endpoints = options.endpoints === true;
230
+ accumulate(s, sources, scores, null, null, endpoints);
231
+ scale(s, scores, options.normalized, endpoints ? n * (n - 1) : (n - 1) * (n - 2));
232
+ return { scores, iterations: sources.length, converged: true };
233
+ }
234
+
235
+ /**
236
+ * Edge betweenness centrality by Brandes' algorithm, one score per logical edge.
237
+ *
238
+ * On a directed snapshot an edge's score equals the legacy `edgeBetweennessCentrality` value keyed
239
+ * `"source-target"`. On an undirected one it is the number of pairs whose shortest paths cross the edge in
240
+ * either direction, which is the SUM of the two values the legacy function reports under `"u-v"` and `"v-u"`
241
+ * (each of those holds half), and is the score the WebGPU accelerator returns.
242
+ * @param s - The snapshot
243
+ * @param options - Normalisation, sampling and the alive-edge mask
244
+ * @returns One score per logical edge
245
+ * @throws RangeError for a bad `sources` or `k`, or an `alive` mask shorter than the edge count
246
+ * @public
247
+ */
248
+ export function edgeBetweennessCentrality(s: GraphSnapshot, options: EdgeBetweennessOptions = {}): EdgeScoresResult {
249
+ const n = s.nodeCount;
250
+ const alive = options.alive ?? null;
251
+ if (alive !== null && alive.length < Math.ceil(s.edgeCount / 32)) {
252
+ throw new RangeError(`edgeBetweennessCentrality: alive covers fewer than ${s.edgeCount} edges`);
253
+ }
254
+ const scores = new Float64Array(s.edgeCount);
255
+ accumulate(s, resolveSources(n, options.sources, options.k), null, scores, alive, false);
256
+ scale(s, scores, options.normalized, (n - 1) * (n - 2));
257
+ return { scores };
258
+ }