@graphty/algorithms 2.1.2 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (355) hide show
  1. package/README.md +139 -50
  2. package/dist/algorithms.js +15260 -10390
  3. package/dist/algorithms.js.map +1 -1
  4. package/dist/algorithms.standalone.js +26675 -21542
  5. package/dist/algorithms.standalone.js.map +1 -1
  6. package/dist/src/algorithms/centrality/betweenness.d.ts +18 -0
  7. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  8. package/dist/src/algorithms/centrality/betweenness.js +40 -1
  9. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts +20 -0
  11. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  12. package/dist/src/algorithms/centrality/closeness.js +93 -8
  13. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  14. package/dist/src/algorithms/centrality/degree.d.ts +9 -0
  15. package/dist/src/algorithms/centrality/degree.d.ts.map +1 -1
  16. package/dist/src/algorithms/centrality/degree.js +18 -0
  17. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  18. package/dist/src/algorithms/centrality/eigenvector.d.ts +10 -0
  19. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -1
  20. package/dist/src/algorithms/centrality/eigenvector.js +29 -0
  21. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -1
  22. package/dist/src/algorithms/centrality/hits.d.ts +9 -0
  23. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -1
  24. package/dist/src/algorithms/centrality/hits.js +23 -0
  25. package/dist/src/algorithms/centrality/hits.js.map +1 -1
  26. package/dist/src/algorithms/centrality/katz.d.ts +9 -0
  27. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -1
  28. package/dist/src/algorithms/centrality/katz.js +31 -0
  29. package/dist/src/algorithms/centrality/katz.js.map +1 -1
  30. package/dist/src/algorithms/centrality/pagerank.d.ts +13 -13
  31. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  32. package/dist/src/algorithms/centrality/pagerank.js +47 -10
  33. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  34. package/dist/src/algorithms/community/girvan-newman-legacy.d.ts +27 -0
  35. package/dist/src/algorithms/community/girvan-newman-legacy.d.ts.map +1 -0
  36. package/dist/src/algorithms/community/girvan-newman-legacy.js +335 -0
  37. package/dist/src/algorithms/community/girvan-newman-legacy.js.map +1 -0
  38. package/dist/src/algorithms/community/girvan-newman.d.ts +5 -3
  39. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  40. package/dist/src/algorithms/community/girvan-newman.js +34 -308
  41. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  42. package/dist/src/algorithms/community/label-propagation.d.ts +9 -6
  43. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  44. package/dist/src/algorithms/community/label-propagation.js +24 -110
  45. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  46. package/dist/src/algorithms/components/connected.d.ts.map +1 -1
  47. package/dist/src/algorithms/components/connected.js +28 -77
  48. package/dist/src/algorithms/components/connected.js.map +1 -1
  49. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -1
  50. package/dist/src/algorithms/mst/kruskal.js +59 -3
  51. package/dist/src/algorithms/mst/kruskal.js.map +1 -1
  52. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  53. package/dist/src/algorithms/shortest-path/bellman-ford.js +46 -0
  54. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  55. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  56. package/dist/src/algorithms/shortest-path/dijkstra.js +27 -0
  57. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  58. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +15 -5
  59. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/floyd-warshall.js +97 -107
  61. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -1
  62. package/dist/src/algorithms/traversal/bfs-unified.d.ts +2 -12
  63. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -1
  64. package/dist/src/algorithms/traversal/bfs-unified.js +47 -308
  65. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -1
  66. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -1
  67. package/dist/src/algorithms/traversal/bfs-variants.js +5 -25
  68. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -1
  69. package/dist/src/algorithms/traversal/bfs.d.ts +0 -3
  70. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  71. package/dist/src/algorithms/traversal/bfs.js +0 -4
  72. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  73. package/dist/src/algorithms/traversal/dfs.d.ts.map +1 -1
  74. package/dist/src/algorithms/traversal/dfs.js +33 -154
  75. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  76. package/dist/src/clustering/hierarchical-legacy.d.ts +56 -0
  77. package/dist/src/clustering/hierarchical-legacy.d.ts.map +1 -0
  78. package/dist/src/clustering/hierarchical-legacy.js +441 -0
  79. package/dist/src/clustering/hierarchical-legacy.js.map +1 -0
  80. package/dist/src/clustering/hierarchical.d.ts +7 -39
  81. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  82. package/dist/src/clustering/hierarchical.js +56 -417
  83. package/dist/src/clustering/hierarchical.js.map +1 -1
  84. package/dist/src/clustering/k-core-legacy.d.ts +30 -0
  85. package/dist/src/clustering/k-core-legacy.d.ts.map +1 -0
  86. package/dist/src/clustering/k-core-legacy.js +191 -0
  87. package/dist/src/clustering/k-core-legacy.js.map +1 -0
  88. package/dist/src/clustering/k-core.d.ts +8 -7
  89. package/dist/src/clustering/k-core.d.ts.map +1 -1
  90. package/dist/src/clustering/k-core.js +33 -174
  91. package/dist/src/clustering/k-core.js.map +1 -1
  92. package/dist/src/clustering/mcl-legacy.d.ts +42 -0
  93. package/dist/src/clustering/mcl-legacy.d.ts.map +1 -0
  94. package/dist/src/clustering/mcl-legacy.js +386 -0
  95. package/dist/src/clustering/mcl-legacy.js.map +1 -0
  96. package/dist/src/clustering/mcl.d.ts +9 -32
  97. package/dist/src/clustering/mcl.d.ts.map +1 -1
  98. package/dist/src/clustering/mcl.js +48 -374
  99. package/dist/src/clustering/mcl.js.map +1 -1
  100. package/dist/src/index.d.ts +29 -3
  101. package/dist/src/index.d.ts.map +1 -1
  102. package/dist/src/index.js +2 -1
  103. package/dist/src/index.js.map +1 -1
  104. package/dist/src/indexed/accelerator.d.ts +141 -11
  105. package/dist/src/indexed/accelerator.d.ts.map +1 -1
  106. package/dist/src/indexed/accelerator.js +319 -5
  107. package/dist/src/indexed/accelerator.js.map +1 -1
  108. package/dist/src/indexed/all-pairs.d.ts +59 -0
  109. package/dist/src/indexed/all-pairs.d.ts.map +1 -0
  110. package/dist/src/indexed/all-pairs.js +274 -0
  111. package/dist/src/indexed/all-pairs.js.map +1 -0
  112. package/dist/src/indexed/bellman-ford.d.ts +25 -0
  113. package/dist/src/indexed/bellman-ford.d.ts.map +1 -0
  114. package/dist/src/indexed/bellman-ford.js +59 -0
  115. package/dist/src/indexed/bellman-ford.js.map +1 -0
  116. package/dist/src/indexed/betweenness.d.ts +85 -0
  117. package/dist/src/indexed/betweenness.d.ts.map +1 -0
  118. package/dist/src/indexed/betweenness.js +188 -0
  119. package/dist/src/indexed/betweenness.js.map +1 -0
  120. package/dist/src/indexed/bfs.d.ts +69 -2
  121. package/dist/src/indexed/bfs.d.ts.map +1 -1
  122. package/dist/src/indexed/bfs.js +148 -1
  123. package/dist/src/indexed/bfs.js.map +1 -1
  124. package/dist/src/indexed/bipartite.d.ts +37 -0
  125. package/dist/src/indexed/bipartite.d.ts.map +1 -0
  126. package/dist/src/indexed/bipartite.js +54 -0
  127. package/dist/src/indexed/bipartite.js.map +1 -0
  128. package/dist/src/indexed/closeness.d.ts +49 -0
  129. package/dist/src/indexed/closeness.d.ts.map +1 -0
  130. package/dist/src/indexed/closeness.js +127 -0
  131. package/dist/src/indexed/closeness.js.map +1 -0
  132. package/dist/src/indexed/common-neighbors.d.ts +14 -1
  133. package/dist/src/indexed/common-neighbors.d.ts.map +1 -1
  134. package/dist/src/indexed/common-neighbors.js +24 -14
  135. package/dist/src/indexed/common-neighbors.js.map +1 -1
  136. package/dist/src/indexed/degree.d.ts +19 -0
  137. package/dist/src/indexed/degree.d.ts.map +1 -0
  138. package/dist/src/indexed/degree.js +45 -0
  139. package/dist/src/indexed/degree.js.map +1 -0
  140. package/dist/src/indexed/delta-pagerank.d.ts +145 -0
  141. package/dist/src/indexed/delta-pagerank.d.ts.map +1 -0
  142. package/dist/src/indexed/delta-pagerank.js +428 -0
  143. package/dist/src/indexed/delta-pagerank.js.map +1 -0
  144. package/dist/src/indexed/dfs.d.ts +55 -0
  145. package/dist/src/indexed/dfs.d.ts.map +1 -0
  146. package/dist/src/indexed/dfs.js +146 -0
  147. package/dist/src/indexed/dfs.js.map +1 -0
  148. package/dist/src/indexed/eigenvector.d.ts +48 -0
  149. package/dist/src/indexed/eigenvector.d.ts.map +1 -0
  150. package/dist/src/indexed/eigenvector.js +162 -0
  151. package/dist/src/indexed/eigenvector.js.map +1 -0
  152. package/dist/src/indexed/facade.d.ts +109 -0
  153. package/dist/src/indexed/facade.d.ts.map +1 -0
  154. package/dist/src/indexed/facade.js +204 -0
  155. package/dist/src/indexed/facade.js.map +1 -0
  156. package/dist/src/indexed/flow.d.ts +120 -0
  157. package/dist/src/indexed/flow.d.ts.map +1 -0
  158. package/dist/src/indexed/flow.js +377 -0
  159. package/dist/src/indexed/flow.js.map +1 -0
  160. package/dist/src/indexed/girvan-newman.d.ts +42 -0
  161. package/dist/src/indexed/girvan-newman.d.ts.map +1 -0
  162. package/dist/src/indexed/girvan-newman.js +90 -0
  163. package/dist/src/indexed/girvan-newman.js.map +1 -0
  164. package/dist/src/indexed/grsbm.d.ts +85 -0
  165. package/dist/src/indexed/grsbm.d.ts.map +1 -0
  166. package/dist/src/indexed/grsbm.js +287 -0
  167. package/dist/src/indexed/grsbm.js.map +1 -0
  168. package/dist/src/indexed/hierarchical.d.ts +69 -0
  169. package/dist/src/indexed/hierarchical.d.ts.map +1 -0
  170. package/dist/src/indexed/hierarchical.js +245 -0
  171. package/dist/src/indexed/hierarchical.js.map +1 -0
  172. package/dist/src/indexed/index.d.ts +29 -3
  173. package/dist/src/indexed/index.d.ts.map +1 -1
  174. package/dist/src/indexed/index.js +29 -3
  175. package/dist/src/indexed/index.js.map +1 -1
  176. package/dist/src/indexed/isomorphism.d.ts +42 -0
  177. package/dist/src/indexed/isomorphism.d.ts.map +1 -0
  178. package/dist/src/indexed/isomorphism.js +269 -0
  179. package/dist/src/indexed/isomorphism.js.map +1 -0
  180. package/dist/src/indexed/label-propagation.d.ts +134 -0
  181. package/dist/src/indexed/label-propagation.d.ts.map +1 -0
  182. package/dist/src/indexed/label-propagation.js +486 -0
  183. package/dist/src/indexed/label-propagation.js.map +1 -0
  184. package/dist/src/indexed/leiden.d.ts +52 -0
  185. package/dist/src/indexed/leiden.d.ts.map +1 -0
  186. package/dist/src/indexed/leiden.js +384 -0
  187. package/dist/src/indexed/leiden.js.map +1 -0
  188. package/dist/src/indexed/link-prediction.d.ts +147 -0
  189. package/dist/src/indexed/link-prediction.d.ts.map +1 -0
  190. package/dist/src/indexed/link-prediction.js +317 -0
  191. package/dist/src/indexed/link-prediction.js.map +1 -0
  192. package/dist/src/indexed/markov.d.ts +54 -0
  193. package/dist/src/indexed/markov.d.ts.map +1 -0
  194. package/dist/src/indexed/markov.js +284 -0
  195. package/dist/src/indexed/markov.js.map +1 -0
  196. package/dist/src/indexed/matching.d.ts +53 -0
  197. package/dist/src/indexed/matching.d.ts.map +1 -0
  198. package/dist/src/indexed/matching.js +139 -0
  199. package/dist/src/indexed/matching.js.map +1 -0
  200. package/dist/src/indexed/min-cut.d.ts +48 -0
  201. package/dist/src/indexed/min-cut.d.ts.map +1 -0
  202. package/dist/src/indexed/min-cut.js +175 -0
  203. package/dist/src/indexed/min-cut.js.map +1 -0
  204. package/dist/src/indexed/modularity.d.ts +34 -0
  205. package/dist/src/indexed/modularity.d.ts.map +1 -0
  206. package/dist/src/indexed/modularity.js +74 -0
  207. package/dist/src/indexed/modularity.js.map +1 -0
  208. package/dist/src/indexed/mst.d.ts +29 -1
  209. package/dist/src/indexed/mst.d.ts.map +1 -1
  210. package/dist/src/indexed/mst.js +62 -0
  211. package/dist/src/indexed/mst.js.map +1 -1
  212. package/dist/src/indexed/pagerank.d.ts +29 -6
  213. package/dist/src/indexed/pagerank.d.ts.map +1 -1
  214. package/dist/src/indexed/pagerank.js +107 -11
  215. package/dist/src/indexed/pagerank.js.map +1 -1
  216. package/dist/src/indexed/point-to-point.d.ts +53 -0
  217. package/dist/src/indexed/point-to-point.d.ts.map +1 -0
  218. package/dist/src/indexed/point-to-point.js +173 -0
  219. package/dist/src/indexed/point-to-point.js.map +1 -0
  220. package/dist/src/indexed/scc.d.ts +53 -0
  221. package/dist/src/indexed/scc.d.ts.map +1 -0
  222. package/dist/src/indexed/scc.js +110 -0
  223. package/dist/src/indexed/scc.js.map +1 -0
  224. package/dist/src/indexed/spectral.d.ts +61 -0
  225. package/dist/src/indexed/spectral.d.ts.map +1 -0
  226. package/dist/src/indexed/spectral.js +476 -0
  227. package/dist/src/indexed/spectral.js.map +1 -0
  228. package/dist/src/indexed/structures/bit-set.d.ts +45 -0
  229. package/dist/src/indexed/structures/bit-set.d.ts.map +1 -0
  230. package/dist/src/indexed/structures/bit-set.js +57 -0
  231. package/dist/src/indexed/structures/bit-set.js.map +1 -0
  232. package/dist/src/indexed/structures/max-heap.d.ts +52 -0
  233. package/dist/src/indexed/structures/max-heap.d.ts.map +1 -0
  234. package/dist/src/indexed/structures/max-heap.js +66 -0
  235. package/dist/src/indexed/structures/max-heap.js.map +1 -0
  236. package/dist/src/indexed/structures/min-heap.d.ts +30 -1
  237. package/dist/src/indexed/structures/min-heap.d.ts.map +1 -1
  238. package/dist/src/indexed/structures/min-heap.js +46 -6
  239. package/dist/src/indexed/structures/min-heap.js.map +1 -1
  240. package/dist/src/indexed/structures/ring-queue.d.ts +41 -0
  241. package/dist/src/indexed/structures/ring-queue.d.ts.map +1 -0
  242. package/dist/src/indexed/structures/ring-queue.js +64 -0
  243. package/dist/src/indexed/structures/ring-queue.js.map +1 -0
  244. package/dist/src/indexed/sync.d.ts +50 -0
  245. package/dist/src/indexed/sync.d.ts.map +1 -0
  246. package/dist/src/indexed/sync.js +175 -0
  247. package/dist/src/indexed/sync.js.map +1 -0
  248. package/dist/src/indexed/terahac.d.ts +58 -0
  249. package/dist/src/indexed/terahac.d.ts.map +1 -0
  250. package/dist/src/indexed/terahac.js +244 -0
  251. package/dist/src/indexed/terahac.js.map +1 -0
  252. package/dist/src/indexed/to-snapshot.d.ts +45 -2
  253. package/dist/src/indexed/to-snapshot.d.ts.map +1 -1
  254. package/dist/src/indexed/to-snapshot.js +140 -6
  255. package/dist/src/indexed/to-snapshot.js.map +1 -1
  256. package/dist/src/link-prediction/common-neighbors-legacy.d.ts +71 -0
  257. package/dist/src/link-prediction/common-neighbors-legacy.d.ts.map +1 -0
  258. package/dist/src/link-prediction/common-neighbors-legacy.js +172 -0
  259. package/dist/src/link-prediction/common-neighbors-legacy.js.map +1 -0
  260. package/dist/src/link-prediction/common-neighbors.d.ts +11 -19
  261. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  262. package/dist/src/link-prediction/common-neighbors.js +56 -123
  263. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  264. package/dist/src/optimized/direction-optimized-bfs.d.ts +6 -0
  265. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -1
  266. package/dist/src/optimized/direction-optimized-bfs.js +6 -0
  267. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -1
  268. package/dist/src/research/grsbm-legacy.d.ts +82 -0
  269. package/dist/src/research/grsbm-legacy.d.ts.map +1 -0
  270. package/dist/src/research/grsbm-legacy.js +416 -0
  271. package/dist/src/research/grsbm-legacy.js.map +1 -0
  272. package/dist/src/research/grsbm.d.ts +9 -66
  273. package/dist/src/research/grsbm.d.ts.map +1 -1
  274. package/dist/src/research/grsbm.js +67 -391
  275. package/dist/src/research/grsbm.js.map +1 -1
  276. package/dist/src/research/sync-legacy.d.ts +48 -0
  277. package/dist/src/research/sync-legacy.d.ts.map +1 -0
  278. package/dist/src/research/sync-legacy.js +337 -0
  279. package/dist/src/research/sync-legacy.js.map +1 -0
  280. package/dist/src/research/sync.d.ts +10 -32
  281. package/dist/src/research/sync.d.ts.map +1 -1
  282. package/dist/src/research/sync.js +28 -316
  283. package/dist/src/research/sync.js.map +1 -1
  284. package/package.json +4 -3
  285. package/src/algorithms/centrality/betweenness.ts +51 -1
  286. package/src/algorithms/centrality/closeness.ts +112 -12
  287. package/src/algorithms/centrality/degree.ts +19 -0
  288. package/src/algorithms/centrality/eigenvector.ts +34 -0
  289. package/src/algorithms/centrality/hits.ts +24 -0
  290. package/src/algorithms/centrality/katz.ts +32 -0
  291. package/src/algorithms/centrality/pagerank.ts +53 -13
  292. package/src/algorithms/community/girvan-newman-legacy.ts +414 -0
  293. package/src/algorithms/community/girvan-newman.ts +36 -386
  294. package/src/algorithms/community/label-propagation.ts +24 -133
  295. package/src/algorithms/components/connected.ts +28 -95
  296. package/src/algorithms/mst/kruskal.ts +65 -4
  297. package/src/algorithms/shortest-path/bellman-ford.ts +49 -0
  298. package/src/algorithms/shortest-path/dijkstra.ts +29 -0
  299. package/src/algorithms/shortest-path/floyd-warshall.ts +106 -136
  300. package/src/algorithms/traversal/bfs-unified.ts +52 -366
  301. package/src/algorithms/traversal/bfs-variants.ts +5 -28
  302. package/src/algorithms/traversal/bfs.ts +0 -4
  303. package/src/algorithms/traversal/dfs.ts +39 -182
  304. package/src/clustering/hierarchical-legacy.ts +551 -0
  305. package/src/clustering/hierarchical.ts +64 -520
  306. package/src/clustering/k-core-legacy.ts +229 -0
  307. package/src/clustering/k-core.ts +38 -209
  308. package/src/clustering/mcl-legacy.ts +498 -0
  309. package/src/clustering/mcl.ts +46 -479
  310. package/src/index.ts +90 -3
  311. package/src/indexed/accelerator.ts +502 -16
  312. package/src/indexed/all-pairs.ts +342 -0
  313. package/src/indexed/bellman-ford.ts +72 -0
  314. package/src/indexed/betweenness.ts +258 -0
  315. package/src/indexed/bfs.ts +189 -3
  316. package/src/indexed/bipartite.ts +77 -0
  317. package/src/indexed/closeness.ts +160 -0
  318. package/src/indexed/common-neighbors.ts +31 -14
  319. package/src/indexed/degree.ts +55 -0
  320. package/src/indexed/delta-pagerank.ts +513 -0
  321. package/src/indexed/dfs.ts +189 -0
  322. package/src/indexed/eigenvector.ts +202 -0
  323. package/src/indexed/facade.ts +229 -0
  324. package/src/indexed/flow.ts +500 -0
  325. package/src/indexed/girvan-newman.ts +120 -0
  326. package/src/indexed/grsbm.ts +390 -0
  327. package/src/indexed/hierarchical.ts +295 -0
  328. package/src/indexed/index.ts +94 -3
  329. package/src/indexed/isomorphism.ts +305 -0
  330. package/src/indexed/label-propagation.ts +582 -0
  331. package/src/indexed/leiden.ts +436 -0
  332. package/src/indexed/link-prediction.ts +399 -0
  333. package/src/indexed/markov.ts +324 -0
  334. package/src/indexed/matching.ts +192 -0
  335. package/src/indexed/min-cut.ts +201 -0
  336. package/src/indexed/modularity.ts +81 -0
  337. package/src/indexed/mst.ts +79 -1
  338. package/src/indexed/pagerank.ts +128 -15
  339. package/src/indexed/point-to-point.ts +220 -0
  340. package/src/indexed/scc.ts +132 -0
  341. package/src/indexed/spectral.ts +550 -0
  342. package/src/indexed/structures/bit-set.ts +65 -0
  343. package/src/indexed/structures/max-heap.ts +74 -0
  344. package/src/indexed/structures/min-heap.ts +51 -6
  345. package/src/indexed/structures/ring-queue.ts +70 -0
  346. package/src/indexed/sync.ts +229 -0
  347. package/src/indexed/terahac.ts +290 -0
  348. package/src/indexed/to-snapshot.ts +156 -6
  349. package/src/link-prediction/common-neighbors-legacy.ts +252 -0
  350. package/src/link-prediction/common-neighbors.ts +65 -168
  351. package/src/optimized/direction-optimized-bfs.ts +6 -1
  352. package/src/research/grsbm-legacy.ts +586 -0
  353. package/src/research/grsbm.ts +76 -555
  354. package/src/research/sync-legacy.ts +456 -0
  355. package/src/research/sync.ts +30 -431
@@ -1,4 +1,4 @@
1
- import { type AdjacencyView, INVALID_INDEX, type U32 } from "@graphty/graph-format";
1
+ import { type AdjacencyView, type GraphSnapshot, INVALID_INDEX, type U32 } from "@graphty/graph-format";
2
2
 
3
3
  /** Result of the index-based BFS (graph-format design 14.2 Port 1). @public */
4
4
  export interface BfsResult {
@@ -12,10 +12,93 @@ export interface BfsResult {
12
12
  readonly visitedCount: number;
13
13
  }
14
14
 
15
+ /** The neighbour-order option the order-sensitive traversals share. @public */
16
+ export interface ArcOrderOption {
17
+ /**
18
+ * A permutation of the arc indices, `arcCount` long, whose slice `[rowPtr[u], rowPtr[u + 1])`
19
+ * lists node `u`'s arcs in the order to try them. The default is row order (ascending neighbour
20
+ * index). A caller that must reproduce a traversal over another neighbour order -- a legacy
21
+ * `Graph` hands out neighbours in insertion order -- passes that order here.
22
+ */
23
+ readonly arcOrder?: U32 | undefined;
24
+ }
25
+
15
26
  /** Options of the index-based BFS. @public */
16
- export interface BfsOptions {
27
+ export interface BfsOptions extends ArcOrderOption {
17
28
  /** Stop expanding at this depth; unbounded when omitted. */
18
29
  readonly maxDepth?: number | undefined;
30
+ /**
31
+ * Stop when this node index is taken off the queue, before its neighbours are expanded. Every
32
+ * node discovered by then stays in `order`; the target's own position in `order` ends the
33
+ * prefix of nodes that were expanded.
34
+ * @throws RangeError when it is not a node index
35
+ */
36
+ readonly target?: number | undefined;
37
+ }
38
+
39
+ /**
40
+ * Check a start (or target) node index.
41
+ * @param g - The adjacency
42
+ * @param start - The node index
43
+ * @param what - What the index is, for the error message
44
+ * @throws RangeError when `start` is not a node index of `g`
45
+ */
46
+ export function checkStart(g: AdjacencyView, start: number, what = "start"): void {
47
+ if (!Number.isInteger(start) || start < 0 || start >= g.nodeCount) {
48
+ throw new RangeError(`${what} node index ${String(start)} is out of range for ${String(g.nodeCount)} nodes`);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Check an optional target node index.
54
+ * @param g - The adjacency
55
+ * @param target - The option, or undefined
56
+ * @returns The target, or INVALID_INDEX for none
57
+ * @throws RangeError when `target` is set and is not a node index of `g`
58
+ */
59
+ export function checkTarget(g: AdjacencyView, target: number | undefined): number {
60
+ if (target === undefined) {
61
+ return INVALID_INDEX;
62
+ }
63
+ checkStart(g, target, "target");
64
+ return target;
65
+ }
66
+
67
+ /**
68
+ * Check an `arcOrder` option: `arcCount` entries, and every row's slice a permutation of that row's
69
+ * arcs.
70
+ * @param g - The adjacency
71
+ * @param arcOrder - The option, or undefined
72
+ * @returns The order, or null for row order
73
+ * @throws RangeError when the order does not fit the adjacency
74
+ */
75
+ export function checkArcOrder(g: AdjacencyView, arcOrder: U32 | undefined): U32 | null {
76
+ if (arcOrder === undefined) {
77
+ return null;
78
+ }
79
+ const { rowPtr, nodeCount } = g;
80
+ if (arcOrder.length !== g.arcCount) {
81
+ throw new RangeError(`arcOrder has ${String(arcOrder.length)} entries, expected ${String(g.arcCount)}`);
82
+ }
83
+ const seen = new Uint8Array(arcOrder.length);
84
+ for (let u = 0; u < nodeCount; u++) {
85
+ const begin = rowPtr[u];
86
+ const end = rowPtr[u + 1];
87
+ for (let a = begin; a < end; a++) {
88
+ if (arcOrder[a] < begin || arcOrder[a] >= end) {
89
+ throw new RangeError(
90
+ `arcOrder[${String(a)}] = ${String(arcOrder[a])} is not an arc of node ${String(u)}`,
91
+ );
92
+ }
93
+ if (seen[arcOrder[a]] === 1) {
94
+ throw new RangeError(
95
+ `arcOrder[${String(a)}] = ${String(arcOrder[a])} repeats an arc of node ${String(u)}`,
96
+ );
97
+ }
98
+ seen[arcOrder[a]] = 1;
99
+ }
100
+ }
101
+ return arcOrder;
19
102
  }
20
103
 
21
104
  /**
@@ -28,24 +111,30 @@ export interface BfsOptions {
28
111
  * @public
29
112
  */
30
113
  export function breadthFirstSearch(g: AdjacencyView, start: number, options: BfsOptions = {}): BfsResult {
114
+ checkStart(g, start);
31
115
  const { nodeCount, rowPtr, colIdx } = g;
116
+ const arcOrder = checkArcOrder(g, options.arcOrder);
32
117
  const parent = new Uint32Array(nodeCount).fill(INVALID_INDEX);
33
118
  const depth = new Uint32Array(nodeCount).fill(INVALID_INDEX);
34
119
  const order = new Uint32Array(nodeCount);
35
120
  const maxDepth = options.maxDepth ?? INVALID_INDEX;
121
+ const target = checkTarget(g, options.target);
36
122
  let head = 0;
37
123
  let tail = 0;
38
124
  order[tail++] = start;
39
125
  depth[start] = 0;
40
126
  while (head < tail) {
41
127
  const u = order[head++];
128
+ if (u === target) {
129
+ break;
130
+ }
42
131
  const d = depth[u];
43
132
  if (d >= maxDepth) {
44
133
  continue;
45
134
  }
46
135
  const end = rowPtr[u + 1];
47
136
  for (let a = rowPtr[u]; a < end; a++) {
48
- const v = colIdx[a];
137
+ const v = colIdx[arcOrder === null ? a : arcOrder[a]];
49
138
  if (depth[v] === INVALID_INDEX) {
50
139
  depth[v] = d + 1;
51
140
  parent[v] = u;
@@ -55,3 +144,100 @@ export function breadthFirstSearch(g: AdjacencyView, start: number, options: Bfs
55
144
  }
56
145
  return { order: order.subarray(0, tail), parent, depth, visitedCount: tail };
57
146
  }
147
+
148
+ /** Options of {@link directionOptimizedBfs}. @public */
149
+ export interface DirectionOptimizedBfsOptions {
150
+ /**
151
+ * Switch from top-down to bottom-up once the frontier's out-arcs exceed the unvisited nodes'
152
+ * out-arcs divided by `alpha`. Default 15.
153
+ */
154
+ readonly alpha?: number | undefined;
155
+ /** Switch back to top-down once the frontier shrinks below `nodeCount / beta` nodes. Default 18. */
156
+ readonly beta?: number | undefined;
157
+ }
158
+
159
+ /**
160
+ * Direction-optimising breadth-first search (Beamer, Asanovic and Patterson, SC'12): a top-down
161
+ * step expands the frontier's out-arcs, a bottom-up step has every unvisited node look for a
162
+ * frontier node among its in-neighbours over `s.reverse()` (fetched only when a bottom-up step
163
+ * runs), and the search switches between the two
164
+ * by frontier size. Both steps give a node the LOWEST-index frontier node that reaches it as its
165
+ * parent, so the result does not depend on which steps ran: `depth` equals `breadthFirstSearch`'s,
166
+ * and `order` lists the visited nodes level by level, ascending within a level.
167
+ * @param s - The snapshot to traverse
168
+ * @param source - The node index to start from
169
+ * @param options - The switching thresholds
170
+ * @returns The visit order, the parent array, the depth array and the visited count
171
+ * @public
172
+ */
173
+ export function directionOptimizedBfs(
174
+ s: GraphSnapshot,
175
+ source: number,
176
+ options: DirectionOptimizedBfsOptions = {},
177
+ ): BfsResult {
178
+ checkStart(s, source);
179
+ const { nodeCount, rowPtr, colIdx } = s;
180
+ const alpha = options.alpha ?? 15;
181
+ const beta = options.beta ?? 18;
182
+ let reverse: AdjacencyView | null = null;
183
+ const parent = new Uint32Array(nodeCount).fill(INVALID_INDEX);
184
+ const depth = new Uint32Array(nodeCount).fill(INVALID_INDEX);
185
+ // order[levelStart, tail) is the current frontier, sorted ascending.
186
+ const order = new Uint32Array(nodeCount);
187
+ order[0] = source;
188
+ depth[source] = 0;
189
+ let levelStart = 0;
190
+ let tail = 1;
191
+ let unexploredArcs = s.arcCount - (rowPtr[source + 1] - rowPtr[source]);
192
+ let bottomUp = false;
193
+ for (let d = 0; levelStart < tail; d++) {
194
+ const frontierSize = tail - levelStart;
195
+ if (bottomUp) {
196
+ bottomUp = frontierSize >= nodeCount / beta;
197
+ } else {
198
+ let frontierArcs = 0;
199
+ for (let i = levelStart; i < tail; i++) {
200
+ frontierArcs += rowPtr[order[i] + 1] - rowPtr[order[i]];
201
+ }
202
+ bottomUp = frontierArcs > unexploredArcs / alpha;
203
+ }
204
+ const levelEnd = tail;
205
+ if (bottomUp) {
206
+ reverse ??= s.reverse();
207
+ for (let v = 0; v < nodeCount; v++) {
208
+ if (depth[v] !== INVALID_INDEX) {
209
+ continue;
210
+ }
211
+ const end = reverse.rowPtr[v + 1];
212
+ for (let a = reverse.rowPtr[v]; a < end; a++) {
213
+ const u = reverse.colIdx[a];
214
+ if (depth[u] === d) {
215
+ parent[v] = u;
216
+ depth[v] = d + 1;
217
+ order[tail++] = v;
218
+ break;
219
+ }
220
+ }
221
+ }
222
+ } else {
223
+ for (let i = levelStart; i < levelEnd; i++) {
224
+ const u = order[i];
225
+ const end = rowPtr[u + 1];
226
+ for (let a = rowPtr[u]; a < end; a++) {
227
+ const v = colIdx[a];
228
+ if (depth[v] === INVALID_INDEX) {
229
+ parent[v] = u;
230
+ depth[v] = d + 1;
231
+ order[tail++] = v;
232
+ }
233
+ }
234
+ }
235
+ order.subarray(levelEnd, tail).sort();
236
+ }
237
+ for (let i = levelEnd; i < tail; i++) {
238
+ unexploredArcs -= rowPtr[order[i] + 1] - rowPtr[order[i]];
239
+ }
240
+ levelStart = levelEnd;
241
+ }
242
+ return { order: order.subarray(0, tail), parent, depth, visitedCount: tail };
243
+ }
@@ -0,0 +1,77 @@
1
+ import { type AdjacencyView, type GraphSnapshot, makeMask, maskSet, type NodeMask } from "@graphty/graph-format";
2
+
3
+ /** Result of the index-based bipartiteness test. @public */
4
+ export interface BipartiteResult {
5
+ /** Whether the nodes split into two sides with no arc inside a side. */
6
+ readonly bipartite: boolean;
7
+ /**
8
+ * The two sides as one mask: a set bit puts the node on the second side, a clear bit on the
9
+ * first. The lowest-index node of every component is on the first side. Null when not bipartite.
10
+ */
11
+ readonly sides: NodeMask | null;
12
+ }
13
+
14
+ /** Options of {@link isBipartite}. @public */
15
+ export interface BipartiteOptions {
16
+ /**
17
+ * Which arcs of a directed snapshot to colour along. "both" (default) follows out- and in-arcs,
18
+ * which answers for the graph with direction ignored. "out" follows out-arcs only, as the legacy
19
+ * `bipartitePartition` does: roots are taken in index order and a node first reached through an
20
+ * in-arc is coloured as a new root, so the answer depends on node order. Ignored on an undirected
21
+ * snapshot.
22
+ */
23
+ readonly arcs?: "both" | "out" | undefined;
24
+ }
25
+
26
+ const UNSEEN = 2;
27
+
28
+ /**
29
+ * Two-colour the graph by BFS, one BFS per component in index order. A self-loop makes a graph
30
+ * non-bipartite; parallel arcs change nothing. Arc direction plays no part in bipartiteness, so on
31
+ * a directed snapshot every node is coloured against its in-neighbours (over `s.reverse()`) as well
32
+ * as its out-neighbours. Following out-arcs only -- as the legacy `bipartitePartition` does -- makes
33
+ * the answer depend on node order: with the single arc `a -> b`, a walk that starts at `b` never
34
+ * meets `a`, colours it as a new root and then finds the arc inside one side. `options.arcs: "out"`
35
+ * gives that legacy answer.
36
+ * @param s - The snapshot to test
37
+ * @param options - Which arcs to follow on a directed snapshot
38
+ * @returns Whether the graph is bipartite and, when it is, its two sides
39
+ * @public
40
+ */
41
+ export function isBipartite(s: GraphSnapshot, options: BipartiteOptions = {}): BipartiteResult {
42
+ const { nodeCount } = s;
43
+ const views: AdjacencyView[] = s.directed && options.arcs !== "out" ? [s, s.reverse()] : [s];
44
+ const side = new Uint8Array(nodeCount).fill(UNSEEN);
45
+ const queue = new Uint32Array(nodeCount);
46
+ for (let r = 0; r < nodeCount; r++) {
47
+ if (side[r] !== UNSEEN) {
48
+ continue;
49
+ }
50
+ side[r] = 0;
51
+ let head = 0;
52
+ let tail = 0;
53
+ queue[tail++] = r;
54
+ while (head < tail) {
55
+ const u = queue[head++];
56
+ for (const { rowPtr, colIdx } of views) {
57
+ const end = rowPtr[u + 1];
58
+ for (let a = rowPtr[u]; a < end; a++) {
59
+ const v = colIdx[a];
60
+ if (side[v] === UNSEEN) {
61
+ side[v] = side[u] ^ 1;
62
+ queue[tail++] = v;
63
+ } else if (side[v] === side[u]) {
64
+ return { bipartite: false, sides: null };
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ const sides = makeMask(nodeCount);
71
+ for (let i = 0; i < nodeCount; i++) {
72
+ if (side[i] === 1) {
73
+ maskSet(sides, i, true);
74
+ }
75
+ }
76
+ return { bipartite: true, sides };
77
+ }
@@ -0,0 +1,160 @@
1
+ import type { GraphSnapshot, NumericVector } from "@graphty/graph-format";
2
+
3
+ import type { ScoresResult } from "./betweenness.js";
4
+ import { IndexedMinHeap } from "./structures/min-heap.js";
5
+
6
+ /** Options of the index-based closeness centrality, matching the legacy `closenessCentrality`. @public */
7
+ export interface ClosenessOptions {
8
+ /**
9
+ * Scale by the fraction of other nodes reached (Wasserman and Faust), or with `harmonic` divide by
10
+ * `n - 1`. Default false.
11
+ */
12
+ readonly normalized?: boolean | undefined;
13
+ /** Sum `1 / distance` instead of taking `1 / sum(distance)`; better on disconnected graphs. Default false. */
14
+ readonly harmonic?: boolean | undefined;
15
+ /**
16
+ * Stop searching from nodes this far away or farther. A node past the cutoff is still counted when an edge
17
+ * reaches it from a node closer than the cutoff, as in the legacy functions. Default: every reachable node.
18
+ */
19
+ readonly cutoff?: number | undefined;
20
+ /**
21
+ * Measure distance by edge weight (Dijkstra) rather than by hops, as the legacy
22
+ * `weightedClosenessCentrality` does. Default false: weights are ignored, as the legacy
23
+ * `closenessCentrality` ignores them.
24
+ */
25
+ readonly weighted?: boolean | undefined;
26
+ /** Per-arc weight override, arcCount long, read when `weighted`; a facade passes the exact f64 weights. */
27
+ readonly weights?: NumericVector | undefined;
28
+ }
29
+
30
+ /** Scores one source at a time, reusing its scratch arrays across sources. */
31
+ type Scorer = (source: number) => number;
32
+
33
+ /**
34
+ * Build the per-source scorer: a breadth-first search, or Dijkstra when `weighted`, then the legacy formula.
35
+ * @param s - The snapshot
36
+ * @param o - The options
37
+ * @returns The scorer
38
+ */
39
+ function scorer(s: GraphSnapshot, o: ClosenessOptions): Scorer {
40
+ const { nodeCount: n, rowPtr, colIdx } = s;
41
+ const cutoff = o.cutoff ?? Infinity;
42
+ const weights = o.weighted === true ? (o.weights ?? s.weights) : null;
43
+ const dist = new Float64Array(n).fill(Infinity);
44
+ const reached = new Uint32Array(n);
45
+ const heap = weights === null ? null : new IndexedMinHeap(n);
46
+ // A popped node is final, as in the legacy search: without this a negative cycle never ends.
47
+ const settled = new Uint8Array(n);
48
+
49
+ /**
50
+ * Fill `dist` from the source and list the reached nodes in `reached`.
51
+ * @param source - The source index
52
+ * @returns How many nodes were reached, the source included
53
+ */
54
+ function search(source: number): number {
55
+ let tail = 0;
56
+ dist[source] = 0;
57
+ reached[tail++] = source;
58
+ if (heap === null || weights === null) {
59
+ for (let head = 0; head < tail; head++) {
60
+ const v = reached[head];
61
+ if (dist[v] >= cutoff) {
62
+ continue;
63
+ }
64
+ for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
65
+ const w = colIdx[a];
66
+ if (dist[w] === Infinity) {
67
+ dist[w] = dist[v] + 1;
68
+ reached[tail++] = w;
69
+ }
70
+ }
71
+ }
72
+ return tail;
73
+ }
74
+ heap.push(source, 0);
75
+ while (!heap.isEmpty()) {
76
+ const v = heap.pop();
77
+ settled[v] = 1;
78
+ if (dist[v] >= cutoff) {
79
+ continue;
80
+ }
81
+ for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
82
+ const w = colIdx[a];
83
+ if (settled[w] === 1) {
84
+ continue;
85
+ }
86
+ const d = dist[v] + weights[a];
87
+ if (d < dist[w]) {
88
+ if (dist[w] === Infinity) {
89
+ reached[tail++] = w;
90
+ }
91
+ dist[w] = d;
92
+ heap.pushOrDecrease(w, d);
93
+ }
94
+ }
95
+ }
96
+ return tail;
97
+ }
98
+
99
+ return (source: number): number => {
100
+ const count = search(source);
101
+ let score = 0;
102
+ let total = 0;
103
+ for (let i = 1; i < count; i++) {
104
+ const d = dist[reached[i]];
105
+ if (o.harmonic === true) {
106
+ score += d > 0 ? 1 / d : 0;
107
+ } else {
108
+ total += d;
109
+ }
110
+ }
111
+ for (let i = 0; i < count; i++) {
112
+ dist[reached[i]] = Infinity;
113
+ settled[reached[i]] = 0;
114
+ }
115
+ if (o.harmonic === true) {
116
+ return o.normalized === true && n > 1 ? score / (n - 1) : score;
117
+ }
118
+ if (total <= 0) {
119
+ return 0;
120
+ }
121
+ return o.normalized === true && n > 1 ? (count - 1) / total / (n - 1) : 1 / total;
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Closeness centrality of every node: `1 / sum(distance to each reached node)` by default, the legacy
127
+ * `closenessCentrality` (hops) and `weightedClosenessCentrality` (`weighted: true`) numbers. The weighted
128
+ * route reads the snapshot's f32 arc weights, so it matches legacy to f32 rounding; pass the f64 weights as
129
+ * `weights` for the exact legacy sums. As in legacy, a node's distance is final once it is searched from, so a
130
+ * negative weight gives the legacy (not the true shortest) distance and never loops. An unreached node adds
131
+ * nothing, and a node that reaches nothing scores 0.
132
+ * @param s - The snapshot
133
+ * @param options - Normalisation, harmonic form, cutoff and weights
134
+ * @returns One score per node index; `iterations` is the number of searches run
135
+ * @public
136
+ */
137
+ export function closenessCentrality(s: GraphSnapshot, options: ClosenessOptions = {}): ScoresResult {
138
+ const score = scorer(s, options);
139
+ const scores = new Float64Array(s.nodeCount);
140
+ for (let v = 0; v < s.nodeCount; v++) {
141
+ scores[v] = score(v);
142
+ }
143
+ return { scores, iterations: s.nodeCount, converged: true };
144
+ }
145
+
146
+ /**
147
+ * Closeness centrality of one node, with one search rather than n.
148
+ * @param s - The snapshot
149
+ * @param node - The node index
150
+ * @param options - As {@link closenessCentrality}
151
+ * @returns The node's score
152
+ * @throws RangeError for a node index outside `[0, nodeCount)`
153
+ * @public
154
+ */
155
+ export function nodeClosenessCentrality(s: GraphSnapshot, node: number, options: ClosenessOptions = {}): number {
156
+ if (!Number.isInteger(node) || node < 0 || node >= s.nodeCount) {
157
+ throw new RangeError(`nodeClosenessCentrality: node must be an index in [0, ${s.nodeCount}), got ${node}`);
158
+ }
159
+ return scorer(s, options)(node);
160
+ }
@@ -7,32 +7,36 @@ export interface CommonNeighborsOptions {
7
7
  }
8
8
 
9
9
  /**
10
- * The number of distinct common neighbours of two nodes, by a merge of their sorted rows.
11
- * @param s - The snapshot
10
+ * Merge row u of `fwd` with row v of `bwd` (both sorted) and add up each DISTINCT common neighbour
11
+ * once: 1 per neighbour, or `weight(z)` when a weight function is given. Skipping the repeats of a
12
+ * match in row v gives simple-graph semantics on a multigraph; a repeat in row u is then smaller
13
+ * than the next entry of row v and the ordinary advance passes it.
14
+ * @param fwd - The view whose row u is read
15
+ * @param bwd - The view whose row v is read
12
16
  * @param u - A node index
13
17
  * @param v - A node index
14
- * @param o - Options
15
- * @returns The count of distinct common neighbours
16
- * @public
18
+ * @param weight - Optional weight of a common neighbour
19
+ * @returns The count, or the weight sum, over distinct common neighbours
17
20
  */
18
- export function commonNeighborsScore(s: GraphSnapshot, u: number, v: number, o: CommonNeighborsOptions = {}): number {
19
- const fwd: AdjacencyView = s;
20
- const bwd: AdjacencyView = o.directed === true ? s.reverse() : s;
21
+ export function sortedRowMerge(
22
+ fwd: AdjacencyView,
23
+ bwd: AdjacencyView,
24
+ u: number,
25
+ v: number,
26
+ weight?: (z: number) => number,
27
+ ): number {
21
28
  let i = fwd.rowPtr[u];
22
29
  const iEnd = fwd.rowPtr[u + 1];
23
30
  let j = bwd.rowPtr[v];
24
31
  const jEnd = bwd.rowPtr[v + 1];
25
- let count = 0;
32
+ let sum = 0;
26
33
  while (i < iEnd && j < jEnd) {
27
34
  const a = fwd.colIdx[i];
28
35
  const b = bwd.colIdx[j];
29
36
  if (a === b) {
30
- count++;
37
+ sum += weight === undefined ? 1 : weight(a);
31
38
  i++;
32
39
  j++;
33
- while (i < iEnd && fwd.colIdx[i] === a) {
34
- i++;
35
- }
36
40
  while (j < jEnd && bwd.colIdx[j] === b) {
37
41
  j++;
38
42
  }
@@ -42,5 +46,18 @@ export function commonNeighborsScore(s: GraphSnapshot, u: number, v: number, o:
42
46
  j++;
43
47
  }
44
48
  }
45
- return count;
49
+ return sum;
50
+ }
51
+
52
+ /**
53
+ * The number of distinct common neighbours of two nodes, by a merge of their sorted rows.
54
+ * @param s - The snapshot
55
+ * @param u - A node index
56
+ * @param v - A node index
57
+ * @param o - Options
58
+ * @returns The count of distinct common neighbours
59
+ * @public
60
+ */
61
+ export function commonNeighborsScore(s: GraphSnapshot, u: number, v: number, o: CommonNeighborsOptions = {}): number {
62
+ return sortedRowMerge(s, o.directed === true ? s.reverse() : s, u, v);
46
63
  }
@@ -0,0 +1,55 @@
1
+ import type { AdjacencyView, F64, GraphSnapshot } from "@graphty/graph-format";
2
+
3
+ /** Options of the index-based degree centrality, matching the legacy `degreeCentrality`. @public */
4
+ export interface DegreeCentralityOptions {
5
+ /** On a directed snapshot, which neighbours to count; ignored when undirected. Default `"total"`. */
6
+ readonly mode?: "in" | "out" | "total" | undefined;
7
+ /** Divide by `n - 1`, the most neighbours a node can have. Default false. */
8
+ readonly normalized?: boolean | undefined;
9
+ }
10
+
11
+ /**
12
+ * Add each node's number of DISTINCT neighbours in `g` to `out`: parallel edges count once and a self-loop
13
+ * counts once, as the legacy `Graph.degree` counts them.
14
+ * @param g - The adjacency
15
+ * @param out - Per-node totals, added to in place
16
+ */
17
+ function addDistinctNeighbours(g: AdjacencyView, out: F64): void {
18
+ const { rowPtr, colIdx } = g;
19
+ for (let v = 0; v < g.nodeCount; v++) {
20
+ let prev = -1;
21
+ for (let a = rowPtr[v], end = rowPtr[v + 1]; a < end; a++) {
22
+ if (colIdx[a] !== prev) {
23
+ prev = colIdx[a];
24
+ out[v]++;
25
+ }
26
+ }
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Degree centrality: each node's number of distinct neighbours (in, out, or both on a directed snapshot),
32
+ * optionally divided by `n - 1`. Equals the legacy `degreeCentrality` on every graph it can hold, and on a
33
+ * multigraph equals it on the same graph with each pair's parallel edges merged.
34
+ * @param s - The snapshot
35
+ * @param options - Direction and normalisation
36
+ * @returns One score per node index
37
+ * @public
38
+ */
39
+ export function degreeCentrality(s: GraphSnapshot, options: DegreeCentralityOptions = {}): F64 {
40
+ const scores = new Float64Array(s.nodeCount);
41
+ const mode = s.directed ? (options.mode ?? "total") : "out";
42
+ if (mode !== "in") {
43
+ addDistinctNeighbours(s, scores);
44
+ }
45
+ if (mode !== "out") {
46
+ addDistinctNeighbours(s.reverse(), scores);
47
+ }
48
+ const max = s.nodeCount - 1;
49
+ if (options.normalized === true && max > 0) {
50
+ for (let v = 0; v < scores.length; v++) {
51
+ scores[v] /= max;
52
+ }
53
+ }
54
+ return scores;
55
+ }