@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
@@ -0,0 +1,550 @@
1
+ import {
2
+ type AdjacencyView,
3
+ type GraphSnapshot,
4
+ type NumericVector,
5
+ renumberPartition,
6
+ type U32,
7
+ } from "@graphty/graph-format";
8
+
9
+ import { type LabelResult, withGroups } from "./components.js";
10
+
11
+ /** Which graph Laplacian the embedding comes from. @public */
12
+ export type LaplacianType = "unnormalized" | "normalized" | "randomWalk";
13
+
14
+ /** Options of the index-based spectral clustering. @public */
15
+ export interface SpectralOptions {
16
+ /** Number of clusters, a positive integer. */
17
+ readonly k: number;
18
+ /** `D - A`, `I - D^-1/2 A D^-1/2` (rows of the embedding scaled to unit length) or `I - D^-1 A`; default normalized. */
19
+ readonly laplacianType?: LaplacianType | undefined;
20
+ /** Cap on k-means rounds; default 100. */
21
+ readonly maxIterations?: number | undefined;
22
+ /** k-means stops when no centroid moves further than this; default 1e-4. */
23
+ readonly tolerance?: number | undefined;
24
+ /** Seed of the starting block and of the k-means seeding; default 42. */
25
+ readonly seed?: number | undefined;
26
+ /** Per-arc weight override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`. */
27
+ readonly weights?: NumericVector | undefined;
28
+ }
29
+
30
+ /** Result of the index-based spectral clustering. @public */
31
+ export interface SpectralResult extends LabelResult {
32
+ /** The k smallest eigenvalues of the Laplacian, ascending; empty when k >= nodeCount. */
33
+ readonly eigenvalues: Float64Array;
34
+ /** One nodeCount-long eigenvector per eigenvalue; for randomWalk, of `I - D^-1 A`. */
35
+ readonly eigenvectors: Float64Array[];
36
+ /**
37
+ * Whether the eigenpairs met the residual tolerance. False when the iteration stopped at its
38
+ * round cap -- on a graph whose small eigenvalues crowd together, such as a long path -- and
39
+ * the eigenpairs are then approximations (the labels usually still hold). True when there
40
+ * were no eigenpairs to compute.
41
+ */
42
+ readonly converged: boolean;
43
+ }
44
+
45
+ /** Relative eigenvector residual at which subspace iteration stops. */
46
+ const RESIDUAL_TOLERANCE = 1e-11;
47
+
48
+ /** Subspace-iteration cap; each round costs one sparse product per block vector. */
49
+ const MAX_SUBSPACE_ROUNDS = 3000;
50
+
51
+ /**
52
+ * mulberry32, the generator the label propagation port uses.
53
+ * @param seed - Generator seed; only its low 32 bits are used
54
+ * @returns The generator, output in [0, 1)
55
+ */
56
+ function mulberry32(seed: number): () => number {
57
+ let a = seed >>> 0;
58
+ return () => {
59
+ a = (a + 0x6d2b79f5) | 0;
60
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
61
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
62
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
63
+ };
64
+ }
65
+
66
+ /** The symmetric weighted adjacency without self-loops, as rows of (neighbour, weight). */
67
+ interface SymmetricRows {
68
+ readonly rowPtr: Uint32Array;
69
+ readonly colIdx: Uint32Array;
70
+ readonly w: Float64Array;
71
+ readonly degree: Float64Array;
72
+ }
73
+
74
+ /**
75
+ * The snapshot's adjacency made symmetric -- a directed snapshot's out-row and in-row together, so
76
+ * a reciprocal pair sums -- with self-loops dropped (they cancel in `D - A`) and weights checked.
77
+ * @param s - The snapshot
78
+ * @param weights - Per-arc weights, or null for all ones
79
+ * @returns The rows and the weighted degrees
80
+ */
81
+ function symmetricRows(s: GraphSnapshot, weights: NumericVector | null): SymmetricRows {
82
+ const n = s.nodeCount;
83
+ const views: { view: AdjacencyView; arcOf: (k: number) => number }[] = [{ view: s, arcOf: (a) => a }];
84
+ if (s.directed) {
85
+ const rev = s.reverse();
86
+ views.push({ view: rev, arcOf: (k) => rev.fwdArc[k] });
87
+ }
88
+ const rowPtr = new Uint32Array(n + 1);
89
+ for (let u = 0; u < n; u++) {
90
+ let kept = 0;
91
+ for (const { view } of views) {
92
+ for (let a = view.rowPtr[u]; a < view.rowPtr[u + 1]; a++) {
93
+ kept += view.colIdx[a] === u ? 0 : 1;
94
+ }
95
+ }
96
+ rowPtr[u + 1] = rowPtr[u] + kept;
97
+ }
98
+ const colIdx = new Uint32Array(rowPtr[n]);
99
+ const w = new Float64Array(rowPtr[n]);
100
+ const degree = new Float64Array(n);
101
+ let k = 0;
102
+ for (let u = 0; u < n; u++) {
103
+ for (const { view, arcOf } of views) {
104
+ for (let a = view.rowPtr[u]; a < view.rowPtr[u + 1]; a++) {
105
+ const weight = weights === null ? 1 : weights[arcOf(a)];
106
+ if (!(weight >= 0) || weight === Infinity) {
107
+ throw new RangeError(
108
+ `an arc has weight ${weight}; spectral clustering needs finite, non-negative weights`,
109
+ );
110
+ }
111
+ if (view.colIdx[a] !== u) {
112
+ colIdx[k] = view.colIdx[a];
113
+ w[k++] = weight;
114
+ degree[u] += weight;
115
+ }
116
+ }
117
+ }
118
+ }
119
+ return { rowPtr, colIdx, w, degree };
120
+ }
121
+
122
+ /**
123
+ * Orthonormalise the columns of `block` in place (Gram-Schmidt, applied twice for stability). A
124
+ * column that collapses is replaced by a fresh random one.
125
+ * @param block - p columns of length n
126
+ * @param random - The generator for replacements
127
+ */
128
+ function orthonormalise(block: Float64Array[], random: () => number): void {
129
+ for (let j = 0; j < block.length; j++) {
130
+ const v = block[j];
131
+ for (let attempt = 0; ; attempt++) {
132
+ for (let pass = 0; pass < 2; pass++) {
133
+ for (let i = 0; i < j; i++) {
134
+ const q = block[i];
135
+ let dot = 0;
136
+ for (let r = 0; r < v.length; r++) {
137
+ dot += q[r] * v[r];
138
+ }
139
+ for (let r = 0; r < v.length; r++) {
140
+ v[r] -= dot * q[r];
141
+ }
142
+ }
143
+ }
144
+ let norm = 0;
145
+ for (let r = 0; r < v.length; r++) {
146
+ norm += v[r] * v[r];
147
+ }
148
+ norm = Math.sqrt(norm);
149
+ if (norm > 1e-10 || attempt === 3) {
150
+ for (let r = 0; r < v.length; r++) {
151
+ v[r] = norm > 0 ? v[r] / norm : 0;
152
+ }
153
+ break;
154
+ }
155
+ for (let r = 0; r < v.length; r++) {
156
+ v[r] = random() - 0.5;
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Eigen-decomposition of a small symmetric matrix by cyclic Jacobi rotations.
164
+ * @param a - p x p row-major, destroyed
165
+ * @param p - The order
166
+ * @returns The eigenvalues and the eigenvectors as the columns of a p x p row-major matrix
167
+ */
168
+ function jacobi(a: Float64Array, p: number): { values: Float64Array; vectors: Float64Array } {
169
+ const v = new Float64Array(p * p);
170
+ for (let i = 0; i < p; i++) {
171
+ v[i * p + i] = 1;
172
+ }
173
+ for (let sweep = 0; sweep < 100; sweep++) {
174
+ let off = 0;
175
+ for (let i = 0; i < p; i++) {
176
+ for (let j = i + 1; j < p; j++) {
177
+ off += a[i * p + j] * a[i * p + j];
178
+ }
179
+ }
180
+ if (off < 1e-30) {
181
+ break;
182
+ }
183
+ for (let i = 0; i < p; i++) {
184
+ for (let j = i + 1; j < p; j++) {
185
+ const aij = a[i * p + j];
186
+ if (aij === 0) {
187
+ continue;
188
+ }
189
+ const theta = (a[j * p + j] - a[i * p + i]) / (2 * aij);
190
+ const t = Math.sign(theta || 1) / (Math.abs(theta) + Math.sqrt(theta * theta + 1));
191
+ const c = 1 / Math.sqrt(t * t + 1);
192
+ const sn = t * c;
193
+ for (let r = 0; r < p; r++) {
194
+ const ari = a[r * p + i];
195
+ const arj = a[r * p + j];
196
+ a[r * p + i] = c * ari - sn * arj;
197
+ a[r * p + j] = sn * ari + c * arj;
198
+ }
199
+ for (let r = 0; r < p; r++) {
200
+ const air = a[i * p + r];
201
+ const ajr = a[j * p + r];
202
+ a[i * p + r] = c * air - sn * ajr;
203
+ a[j * p + r] = sn * air + c * ajr;
204
+ }
205
+ for (let r = 0; r < p; r++) {
206
+ const vri = v[r * p + i];
207
+ const vrj = v[r * p + j];
208
+ v[r * p + i] = c * vri - sn * vrj;
209
+ v[r * p + j] = sn * vri + c * vrj;
210
+ }
211
+ }
212
+ }
213
+ }
214
+ return { values: Float64Array.from({ length: p }, (_, i) => a[i * p + i]), vectors: v };
215
+ }
216
+
217
+ /**
218
+ * The k smallest eigenpairs of a symmetric Laplacian, by subspace iteration on `shift * I - L` (whose
219
+ * largest eigenpairs they are) with a Rayleigh-Ritz step, over a block a few vectors wider than k.
220
+ * @param apply - Writes `L x` into `out`
221
+ * @param n - Order
222
+ * @param k - Eigenpairs wanted
223
+ * @param shift - An upper bound on the largest eigenvalue of L
224
+ * @param random - The generator of the starting block
225
+ * @returns Eigenvalues ascending, their eigenvectors, and whether they met the tolerance
226
+ */
227
+ function smallestEigenpairs(
228
+ apply: (x: Float64Array, out: Float64Array) => void,
229
+ n: number,
230
+ k: number,
231
+ shift: number,
232
+ random: () => number,
233
+ ): { values: Float64Array; vectors: Float64Array[]; converged: boolean } {
234
+ const p = Math.min(n, k + Math.max(k, 8));
235
+ let block: Float64Array[] = Array.from({ length: p }, () => Float64Array.from({ length: n }, () => random() - 0.5));
236
+ orthonormalise(block, random);
237
+ const lx = new Float64Array(n);
238
+ const shifted = (x: Float64Array): Float64Array => {
239
+ apply(x, lx);
240
+ return x.map((xi, r) => shift * xi - lx[r]);
241
+ };
242
+ let ritz: Float64Array = new Float64Array(p);
243
+ let order: number[] = [];
244
+ let vectors: Float64Array = new Float64Array(p * p);
245
+ let converged = false;
246
+ // ponytail: plain subspace iteration converges at the ratio of neighbouring shifted eigenvalues,
247
+ // so a small spectral gap hits the cap; Chebyshev filtering would converge far faster there.
248
+ for (let round = 0; round < MAX_SUBSPACE_ROUNDS; round++) {
249
+ const image = block.map(shifted);
250
+ // Rayleigh-Ritz: the block's projection of the shifted operator, diagonalised.
251
+ const h = new Float64Array(p * p);
252
+ for (let i = 0; i < p; i++) {
253
+ for (let j = i; j < p; j++) {
254
+ let dot = 0;
255
+ for (let r = 0; r < n; r++) {
256
+ dot += block[i][r] * image[j][r];
257
+ }
258
+ h[i * p + j] = dot;
259
+ h[j * p + i] = dot;
260
+ }
261
+ }
262
+ const eig = jacobi(h, p);
263
+ ({ values: ritz, vectors } = eig);
264
+ order = Array.from({ length: p }, (_, i) => i).sort((a, b) => ritz[b] - ritz[a]);
265
+ // Converged when every wanted Ritz pair (y = X q, theta) has a residual |B y - theta y|
266
+ // below RESIDUAL_TOLERANCE * shift; B y is the image block times q.
267
+ converged = order.slice(0, k).every((col) => {
268
+ let residual = 0;
269
+ for (let r = 0; r < n; r++) {
270
+ let diff = 0;
271
+ for (let i = 0; i < p; i++) {
272
+ diff += vectors[i * p + col] * (image[i][r] - ritz[col] * block[i][r]);
273
+ }
274
+ residual += diff * diff;
275
+ }
276
+ return Math.sqrt(residual) <= RESIDUAL_TOLERANCE * shift;
277
+ });
278
+ if (converged || round === MAX_SUBSPACE_ROUNDS - 1) {
279
+ break; // at the cap the Ritz pairs of this block are the best estimate there is
280
+ }
281
+ block = image;
282
+ orthonormalise(block, random);
283
+ }
284
+ const values = new Float64Array(k);
285
+ const out: Float64Array[] = [];
286
+ for (let rank = 0; rank < k; rank++) {
287
+ const col = order[rank];
288
+ values[rank] = Math.max(0, shift - ritz[col]);
289
+ const v = new Float64Array(n);
290
+ for (let i = 0; i < p; i++) {
291
+ const c = vectors[i * p + col];
292
+ for (let r = 0; r < n; r++) {
293
+ v[r] += c * block[i][r];
294
+ }
295
+ }
296
+ out.push(v);
297
+ }
298
+ return { values, vectors: out, converged };
299
+ }
300
+
301
+ /** k-means runs per clustering; the one with the lowest inertia is kept. */
302
+ const K_MEANS_RUNS = 10;
303
+
304
+ /**
305
+ * The k-means objective: the summed squared distance of every row from its cluster's mean.
306
+ * @param points - n rows of length d, row-major
307
+ * @param assign - The cluster of every row
308
+ * @param n - Row count
309
+ * @param d - Row length
310
+ * @param k - Cluster count
311
+ * @returns The inertia
312
+ */
313
+ function inertiaOf(points: Float64Array, assign: Uint32Array, n: number, d: number, k: number): number {
314
+ const sums = new Float64Array(k * d);
315
+ const sizes = new Float64Array(k);
316
+ for (let i = 0; i < n; i++) {
317
+ sizes[assign[i]]++;
318
+ for (let t = 0; t < d; t++) {
319
+ sums[assign[i] * d + t] += points[i * d + t];
320
+ }
321
+ }
322
+ let total = 0;
323
+ for (let i = 0; i < n; i++) {
324
+ const c = assign[i];
325
+ for (let t = 0; t < d; t++) {
326
+ total += (points[i * d + t] - sums[c * d + t] / sizes[c]) ** 2;
327
+ }
328
+ }
329
+ return total;
330
+ }
331
+
332
+ /**
333
+ * k-means over the rows of an n x d embedding, seeded with k-means++ and refined by Lloyd rounds.
334
+ * @param points - n rows of length d, row-major
335
+ * @param n - Row count
336
+ * @param d - Row length
337
+ * @param k - Cluster count
338
+ * @param maxIterations - Round cap
339
+ * @param tolerance - Stop when no centroid moves further than this
340
+ * @param random - The generator
341
+ * @returns The cluster of every row
342
+ */
343
+ function kMeans(
344
+ points: Float64Array,
345
+ n: number,
346
+ d: number,
347
+ k: number,
348
+ maxIterations: number,
349
+ tolerance: number,
350
+ random: () => number,
351
+ ): U32 {
352
+ const dist2 = (i: number, centres: Float64Array, c: number): number => {
353
+ let sum = 0;
354
+ for (let t = 0; t < d; t++) {
355
+ const diff = points[i * d + t] - centres[c * d + t];
356
+ sum += diff * diff;
357
+ }
358
+ return sum;
359
+ };
360
+ const centres = new Float64Array(k * d);
361
+ const nearest = new Float64Array(n).fill(Infinity);
362
+ let pick = Math.floor(random() * n);
363
+ for (let c = 0; c < k; c++) {
364
+ centres.set(points.subarray(pick * d, pick * d + d), c * d);
365
+ let total = 0;
366
+ for (let i = 0; i < n; i++) {
367
+ nearest[i] = Math.min(nearest[i], dist2(i, centres, c));
368
+ total += nearest[i];
369
+ }
370
+ // k-means++: the next centre is a row drawn with probability proportional to its squared
371
+ // distance from the nearest centre so far (the first row when every row sits on a centre).
372
+ let target = random() * total;
373
+ pick = 0;
374
+ for (let i = 0; i < n && total > 0; i++) {
375
+ target -= nearest[i];
376
+ if (target < 0) {
377
+ pick = i;
378
+ break;
379
+ }
380
+ }
381
+ }
382
+ const assign = new Uint32Array(n);
383
+ for (let round = 0; round < maxIterations; round++) {
384
+ let changed = round === 0;
385
+ for (let i = 0; i < n; i++) {
386
+ let best = 0;
387
+ let bestDist = Infinity;
388
+ for (let c = 0; c < k; c++) {
389
+ const dd = dist2(i, centres, c);
390
+ if (dd < bestDist) {
391
+ bestDist = dd;
392
+ best = c;
393
+ }
394
+ }
395
+ changed ||= assign[i] !== best;
396
+ assign[i] = best;
397
+ }
398
+ if (!changed) {
399
+ break;
400
+ }
401
+ const sums = new Float64Array(k * d);
402
+ const sizes = new Float64Array(k);
403
+ for (let i = 0; i < n; i++) {
404
+ sizes[assign[i]]++;
405
+ for (let t = 0; t < d; t++) {
406
+ sums[assign[i] * d + t] += points[i * d + t];
407
+ }
408
+ }
409
+ let shift = 0;
410
+ for (let c = 0; c < k; c++) {
411
+ if (sizes[c] === 0) {
412
+ continue; // an emptied cluster keeps its centre
413
+ }
414
+ let moved = 0;
415
+ for (let t = 0; t < d; t++) {
416
+ const next = sums[c * d + t] / sizes[c];
417
+ moved += (next - centres[c * d + t]) ** 2;
418
+ centres[c * d + t] = next;
419
+ }
420
+ shift = Math.max(shift, Math.sqrt(moved));
421
+ }
422
+ if (shift < tolerance) {
423
+ break;
424
+ }
425
+ }
426
+ return assign;
427
+ }
428
+
429
+ /**
430
+ * Spectral clustering, the index-based port of the legacy `spectralClustering`: the k eigenvectors
431
+ * of the graph Laplacian with the smallest eigenvalues embed every node as a point in k dimensions,
432
+ * and k-means splits the points into at most k clusters.
433
+ *
434
+ * The Laplacian is applied straight from the CSR rows and never built: `D - A`
435
+ * (`unnormalized`), `I - D^-1/2 A D^-1/2` (`normalized`, whose embedded rows are then scaled to
436
+ * unit length, as Ng, Jordan and Weiss do) or `I - D^-1 A` (`randomWalk`, whose eigenvectors are
437
+ * `D^-1/2` times the normalized ones). A node of degree 0 has a zero row. Self-loops are ignored
438
+ * (they cancel in `D - A`) and a directed snapshot is read as undirected, a reciprocal pair
439
+ * summing. A negative, NaN or infinite weight throws. With `k >= nodeCount` every node is its own
440
+ * cluster.
441
+ *
442
+ * The eigenpairs come from subspace iteration with Rayleigh-Ritz, so they are the true smallest
443
+ * ones when `converged` is true; `converged` is false when the iteration stopped at its round cap
444
+ * (a small spectral gap, such as a path of hundreds of nodes), and the eigenpairs are then only
445
+ * approximate. The legacy function uses fixed placeholder eigenvalues for k <= 3 and plain power
446
+ * iteration (which finds the LARGEST eigenvectors) above that, and draws its k-means seeds from
447
+ * `Math.random` unless given a seed. The partitions therefore differ from the legacy function's;
448
+ * one `seed` gives one result. Labels are dense in first-seen order, so `count` is below k when
449
+ * k-means leaves a cluster empty.
450
+ * @param s - Any snapshot
451
+ * @param options - k, the Laplacian, the k-means limits, the seed and the weight override
452
+ * @returns The partition and the eigenpairs it came from
453
+ * @public
454
+ */
455
+ export function spectralClustering(s: GraphSnapshot, options: SpectralOptions): SpectralResult {
456
+ const { k } = options;
457
+ const type = options.laplacianType ?? "normalized";
458
+ const maxIterations = options.maxIterations ?? 100;
459
+ const tolerance = options.tolerance ?? 1e-4;
460
+ const seed = options.seed ?? 42;
461
+ if (!Number.isInteger(k) || k < 1) {
462
+ throw new RangeError(`k must be a positive integer, got ${k}`);
463
+ }
464
+ if (!["unnormalized", "normalized", "randomWalk"].includes(type)) {
465
+ throw new RangeError(`unknown laplacianType "${type}"`);
466
+ }
467
+ if (!Number.isInteger(maxIterations) || maxIterations < 0 || !(tolerance >= 0) || !Number.isInteger(seed)) {
468
+ throw new RangeError("maxIterations and seed must be integers, maxIterations and tolerance non-negative");
469
+ }
470
+ const weights = options.weights ?? s.weights;
471
+ if (weights !== null && weights.length !== s.arcCount) {
472
+ throw new RangeError(`weights has ${weights.length} entries; the snapshot has ${s.arcCount} arcs`);
473
+ }
474
+ const n = s.nodeCount;
475
+ if (k >= n) {
476
+ const labels = Uint32Array.from({ length: n }, (_, i) => i);
477
+ return { ...withGroups(labels, n), eigenvalues: new Float64Array(0), eigenvectors: [], converged: true };
478
+ }
479
+ const g = symmetricRows(s, weights);
480
+ const scale = g.degree.map((d) => (d > 0 ? 1 / Math.sqrt(d) : 0));
481
+ const normalised = type !== "unnormalized";
482
+ const apply = (x: Float64Array, out: Float64Array): void => {
483
+ for (let u = 0; u < n; u++) {
484
+ let ax = 0;
485
+ for (let a = g.rowPtr[u]; a < g.rowPtr[u + 1]; a++) {
486
+ const v = g.colIdx[a];
487
+ ax += normalised ? g.w[a] * scale[v] * x[v] : g.w[a] * x[v];
488
+ }
489
+ if (normalised) {
490
+ out[u] = g.degree[u] > 0 ? x[u] - scale[u] * ax : 0;
491
+ } else {
492
+ out[u] = g.degree[u] * x[u] - ax;
493
+ }
494
+ }
495
+ };
496
+ // The largest eigenvalue is at most 2 for the normalised Laplacian, and at most the largest
497
+ // d(u) + d(v) over the edges for D - A (Anderson and Morley, 1985).
498
+ let shift = 2;
499
+ if (!normalised) {
500
+ shift = 1;
501
+ for (let u = 0; u < n; u++) {
502
+ for (let a = g.rowPtr[u]; a < g.rowPtr[u + 1]; a++) {
503
+ shift = Math.max(shift, g.degree[u] + g.degree[g.colIdx[a]]);
504
+ }
505
+ }
506
+ }
507
+ const random = mulberry32(seed);
508
+ const eig = smallestEigenpairs(apply, n, k, shift, random);
509
+ if (type === "randomWalk") {
510
+ for (const v of eig.vectors) {
511
+ for (let i = 0; i < n; i++) {
512
+ v[i] *= g.degree[i] > 0 ? scale[i] : 1;
513
+ }
514
+ }
515
+ }
516
+ const points = new Float64Array(n * k);
517
+ for (let c = 0; c < k; c++) {
518
+ for (let i = 0; i < n; i++) {
519
+ points[i * k + c] = eig.vectors[c][i];
520
+ }
521
+ }
522
+ if (type === "normalized") {
523
+ for (let i = 0; i < n; i++) {
524
+ const row = points.subarray(i * k, i * k + k);
525
+ const norm = Math.hypot(...row);
526
+ if (norm > 0) {
527
+ row.forEach((x, c) => (row[c] = x / norm));
528
+ }
529
+ }
530
+ }
531
+ // Ten seeded k-means runs, keeping the tightest (the scikit-learn default): one run from an
532
+ // unlucky seeding can split a clean cluster.
533
+ let best: U32 = new Uint32Array(n);
534
+ let bestInertia = Infinity;
535
+ for (let run = 0; run < K_MEANS_RUNS; run++) {
536
+ const assign = kMeans(points, n, k, k, maxIterations, tolerance, random);
537
+ const inertia = inertiaOf(points, assign, n, k, k);
538
+ if (inertia < bestInertia) {
539
+ bestInertia = inertia;
540
+ best = assign;
541
+ }
542
+ }
543
+ const { labels, count } = renumberPartition(best);
544
+ return {
545
+ ...withGroups(labels, count),
546
+ eigenvalues: eig.values,
547
+ eigenvectors: eig.vectors,
548
+ converged: eig.converged,
549
+ };
550
+ }
@@ -0,0 +1,65 @@
1
+ import { makeMask, maskCount, maskSet, maskTest, maskToIndices, type NodeMask } from "@graphty/graph-format";
2
+
3
+ /**
4
+ * A fixed-length set of indices over graph-format's packed mask words, so `words` can be handed
5
+ * to anything that takes a `NodeMask` or `EdgeMask` (`maskToIndices`, `filterEdges`,
6
+ * `inducedSubgraph({ mask })`) without a copy.
7
+ */
8
+ export class BitSet {
9
+ /** The packed words, `ceil(length / 32)` of them; shared, not copied. */
10
+ readonly words: NodeMask;
11
+
12
+ /**
13
+ * Create an empty set over indices in `[0, length)`.
14
+ * @param length - The number of indices the set covers
15
+ */
16
+ constructor(readonly length: number) {
17
+ this.words = makeMask(length);
18
+ }
19
+
20
+ /**
21
+ * Whether an index is in the set.
22
+ * @param i - An index below `length`
23
+ * @returns True when present
24
+ */
25
+ has(i: number): boolean {
26
+ return maskTest(this.words, i);
27
+ }
28
+
29
+ /**
30
+ * Include an index.
31
+ * @param i - An index below `length`
32
+ */
33
+ add(i: number): void {
34
+ maskSet(this.words, i, true);
35
+ }
36
+
37
+ /**
38
+ * Exclude an index.
39
+ * @param i - An index below `length`
40
+ */
41
+ delete(i: number): void {
42
+ maskSet(this.words, i, false);
43
+ }
44
+
45
+ /** Exclude every index. */
46
+ clear(): void {
47
+ this.words.fill(0);
48
+ }
49
+
50
+ /**
51
+ * The number of included indices.
52
+ * @returns The population count
53
+ */
54
+ count(): number {
55
+ return maskCount(this.words, this.length);
56
+ }
57
+
58
+ /**
59
+ * The included indices in ascending order.
60
+ * @returns A fresh array of indices
61
+ */
62
+ toIndices(): Uint32Array {
63
+ return maskToIndices(this.words, this.length);
64
+ }
65
+ }
@@ -0,0 +1,74 @@
1
+ import { IndexedMinHeap } from "./min-heap.js";
2
+
3
+ /**
4
+ * A binary max-heap over node indices with O(log n) increase-key: an {@link IndexedMinHeap} over
5
+ * negated keys. Negation is exact in f64, so keys come back bit-for-bit. Stoer-Wagner's
6
+ * maximum-adjacency ordering is the caller it exists for: every node starts at 0 and gains the
7
+ * weight of each edge to the growing set, `pushOrIncrease(v, keyOf(v) + w)`. Of two equal keys the
8
+ * lower node index pops first, which is the order the legacy Stoer-Wagner scan picks in.
9
+ * @public
10
+ */
11
+ export class IndexedMaxHeap {
12
+ private readonly min: IndexedMinHeap;
13
+
14
+ /**
15
+ * Create an empty heap over node indices in `[0, capacity)`.
16
+ * @param capacity - The number of distinct node indices the heap may hold
17
+ */
18
+ constructor(capacity: number) {
19
+ this.min = new IndexedMinHeap(capacity, true);
20
+ }
21
+
22
+ /**
23
+ * Whether the heap holds no entries.
24
+ * @returns True when the heap holds no entries
25
+ */
26
+ isEmpty(): boolean {
27
+ return this.min.isEmpty();
28
+ }
29
+
30
+ /**
31
+ * Whether a node is currently in the heap.
32
+ * @param node - The node index
33
+ * @returns True when the node was pushed and has not been popped since
34
+ */
35
+ has(node: number): boolean {
36
+ return this.min.has(node);
37
+ }
38
+
39
+ /**
40
+ * The key a node was last given. Meaningful only while `has(node)` is true.
41
+ * @param node - The node index
42
+ * @returns Its key
43
+ */
44
+ keyOf(node: number): number {
45
+ return -this.min.keyOf(node);
46
+ }
47
+
48
+ /**
49
+ * Insert a node that is not in the heap.
50
+ * @param node - The node index
51
+ * @param key - Its key
52
+ */
53
+ push(node: number, key: number): void {
54
+ this.min.push(node, -key);
55
+ }
56
+
57
+ /**
58
+ * Insert the node, or raise its key if it is already present and the new key is larger.
59
+ * @param node - The node index
60
+ * @param key - Its candidate key
61
+ */
62
+ pushOrIncrease(node: number, key: number): void {
63
+ this.min.pushOrDecrease(node, -key);
64
+ }
65
+
66
+ /**
67
+ * Remove and return the node with the largest key. Undefined behaviour on an empty heap;
68
+ * callers guard with `isEmpty()`.
69
+ * @returns The node index
70
+ */
71
+ pop(): number {
72
+ return this.min.pop();
73
+ }
74
+ }