@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,110 @@
1
+ import { INVALID_INDEX, } from "@graphty/graph-format";
2
+ import { checkArcOrder } from "./bfs.js";
3
+ import { withGroups } from "./components.js";
4
+ /**
5
+ * Strongly connected components by an iterative Tarjan: roots in index order, neighbours in row
6
+ * order unless `options.arcOrder` gives another, and component `c` is the `c`-th to complete -- the numbering the legacy
7
+ * `stronglyConnectedComponents` gives its result array. Completion order is a reverse topological
8
+ * order of the condensation.
9
+ * @param g - A directed adjacency
10
+ * @param options - The neighbour order
11
+ * @returns The partition, labels in completion order
12
+ * @throws Error when the graph is undirected
13
+ * @public
14
+ */
15
+ export function stronglyConnectedComponents(g, options = {}) {
16
+ const { labels, count } = tarjan(g, options);
17
+ return withGroups(labels, count);
18
+ }
19
+ /**
20
+ * The Tarjan walk behind {@link stronglyConnectedComponents}, also returning every node in the
21
+ * order it left the component stack. Each component leaves as one run, in label order, its members
22
+ * in reverse discovery order -- the order the legacy `stronglyConnectedComponents` lists them in.
23
+ * @param g - A directed adjacency
24
+ * @param options - The neighbour order
25
+ * @returns The labels, their count and the pop order
26
+ * @throws Error when the graph is undirected
27
+ */
28
+ export function tarjan(g, options = {}) {
29
+ if (!g.directed) {
30
+ throw new Error("Strongly connected components require a directed graph. Use connectedComponents for an undirected one.");
31
+ }
32
+ const { nodeCount, rowPtr, colIdx } = g;
33
+ const arcOrder = checkArcOrder(g, options.arcOrder);
34
+ const index = new Uint32Array(nodeCount).fill(INVALID_INDEX);
35
+ const low = new Uint32Array(nodeCount);
36
+ const cursor = new Uint32Array(nodeCount);
37
+ const onStack = new Uint8Array(nodeCount);
38
+ const callStack = new Uint32Array(nodeCount);
39
+ const sccStack = new Uint32Array(nodeCount);
40
+ const labels = new Uint32Array(nodeCount);
41
+ const popped = new Uint32Array(nodeCount);
42
+ let poppedCount = 0;
43
+ let next = 0;
44
+ let count = 0;
45
+ let sp = 0;
46
+ let top = 0;
47
+ // The body of the recursive call before its loop over neighbours.
48
+ const enter = (u) => {
49
+ index[u] = next;
50
+ low[u] = next;
51
+ next++;
52
+ cursor[u] = rowPtr[u];
53
+ sccStack[sp++] = u;
54
+ onStack[u] = 1;
55
+ callStack[top++] = u;
56
+ };
57
+ for (let r = 0; r < nodeCount; r++) {
58
+ if (index[r] !== INVALID_INDEX) {
59
+ continue;
60
+ }
61
+ enter(r);
62
+ while (top > 0) {
63
+ const x = callStack[top - 1];
64
+ if (cursor[x] < rowPtr[x + 1]) {
65
+ const a = cursor[x]++;
66
+ const v = colIdx[arcOrder === null ? a : arcOrder[a]];
67
+ if (index[v] === INVALID_INDEX) {
68
+ enter(v);
69
+ }
70
+ else if (onStack[v] === 1 && index[v] < low[x]) {
71
+ low[x] = index[v];
72
+ }
73
+ continue;
74
+ }
75
+ // x returns: close its component if it is a root, then pass its low link to the caller.
76
+ top--;
77
+ if (low[x] === index[x]) {
78
+ let w;
79
+ do {
80
+ w = sccStack[--sp];
81
+ onStack[w] = 0;
82
+ labels[w] = count;
83
+ popped[poppedCount++] = w;
84
+ } while (w !== x);
85
+ count++;
86
+ }
87
+ if (top > 0 && low[x] < low[callStack[top - 1]]) {
88
+ low[callStack[top - 1]] = low[x];
89
+ }
90
+ }
91
+ }
92
+ return { labels, count, popped };
93
+ }
94
+ /**
95
+ * The condensation of a directed graph: its strongly connected components, and the DAG with one
96
+ * node per component that `contract()` builds from their labels. The labels already run `0..k-1`,
97
+ * so contract keeps them as block indices and the condensed node numbering is the component
98
+ * numbering.
99
+ * @param s - A directed snapshot
100
+ * @param options - The neighbour order Tarjan tries arcs in, which fixes the component numbering
101
+ * @returns The components and the condensed graph
102
+ * @throws Error when the snapshot is undirected
103
+ * @public
104
+ */
105
+ export function condensation(s, options = {}) {
106
+ const components = stronglyConnectedComponents(s, options);
107
+ const condensed = s.contract(components.labels, { selfLoops: "drop", parallel: "merge", weights: "first" });
108
+ return { components, condensed };
109
+ }
110
+ //# sourceMappingURL=scc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scc.js","sourceRoot":"","sources":["../../../src/indexed/scc.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,aAAa,GAEhB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAuB,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAoB,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAc/D;;;;;;;;;;GAUG;AACH,MAAM,UAAU,2BAA2B,CAAC,CAAgB,EAAE,UAA0B,EAAE;IACtF,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CAAC,CAAgB,EAAE,UAA0B,EAAE;IACjE,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC;QACd,MAAM,IAAI,KAAK,CACX,wGAAwG,CAC3G,CAAC;IACN,CAAC;IACD,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,aAAa,CAAC,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC7D,MAAM,GAAG,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;IACvC,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,IAAI,UAAU,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,SAAS,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;IAC7C,MAAM,QAAQ,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;IAC5C,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC;IAC1C,IAAI,WAAW,GAAG,CAAC,CAAC;IACpB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,EAAE,GAAG,CAAC,CAAC;IACX,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,kEAAkE;IAClE,MAAM,KAAK,GAAG,CAAC,CAAS,EAAQ,EAAE;QAC9B,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QAChB,GAAG,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;QACd,IAAI,EAAE,CAAC;QACP,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACtB,QAAQ,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC;QACnB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACf,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;QACjC,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,aAAa,EAAE,CAAC;YAC7B,SAAS;QACb,CAAC;QACD,KAAK,CAAC,CAAC,CAAC,CAAC;QACT,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;YACb,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC;YAC7B,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtB,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;gBACtD,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,aAAa,EAAE,CAAC;oBAC7B,KAAK,CAAC,CAAC,CAAC,CAAC;gBACb,CAAC;qBAAM,IAAI,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;oBAC/C,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACtB,CAAC;gBACD,SAAS;YACb,CAAC;YACD,wFAAwF;YACxF,GAAG,EAAE,CAAC;YACN,IAAI,GAAG,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;gBACtB,IAAI,CAAS,CAAC;gBACd,GAAG,CAAC;oBACA,CAAC,GAAG,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC;oBACnB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;oBACf,MAAM,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;oBAClB,MAAM,CAAC,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;gBAC9B,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE;gBAClB,KAAK,EAAE,CAAC;YACZ,CAAC;YACD,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC9C,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;YACrC,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACrC,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,YAAY,CAAC,CAAgB,EAAE,UAA0B,EAAE;IACvE,MAAM,UAAU,GAAG,2BAA2B,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC3D,MAAM,SAAS,GAAG,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC;IAC5G,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { type GraphSnapshot, type NumericVector } from "@graphty/graph-format";
2
+ import { type LabelResult } from "./components.js";
3
+ /** Which graph Laplacian the embedding comes from. @public */
4
+ export type LaplacianType = "unnormalized" | "normalized" | "randomWalk";
5
+ /** Options of the index-based spectral clustering. @public */
6
+ export interface SpectralOptions {
7
+ /** Number of clusters, a positive integer. */
8
+ readonly k: number;
9
+ /** `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. */
10
+ readonly laplacianType?: LaplacianType | undefined;
11
+ /** Cap on k-means rounds; default 100. */
12
+ readonly maxIterations?: number | undefined;
13
+ /** k-means stops when no centroid moves further than this; default 1e-4. */
14
+ readonly tolerance?: number | undefined;
15
+ /** Seed of the starting block and of the k-means seeding; default 42. */
16
+ readonly seed?: number | undefined;
17
+ /** Per-arc weight override, arcCount long -- the facade passes `expandEdges(s, shadow.data)`. */
18
+ readonly weights?: NumericVector | undefined;
19
+ }
20
+ /** Result of the index-based spectral clustering. @public */
21
+ export interface SpectralResult extends LabelResult {
22
+ /** The k smallest eigenvalues of the Laplacian, ascending; empty when k >= nodeCount. */
23
+ readonly eigenvalues: Float64Array;
24
+ /** One nodeCount-long eigenvector per eigenvalue; for randomWalk, of `I - D^-1 A`. */
25
+ readonly eigenvectors: Float64Array[];
26
+ /**
27
+ * Whether the eigenpairs met the residual tolerance. False when the iteration stopped at its
28
+ * round cap -- on a graph whose small eigenvalues crowd together, such as a long path -- and
29
+ * the eigenpairs are then approximations (the labels usually still hold). True when there
30
+ * were no eigenpairs to compute.
31
+ */
32
+ readonly converged: boolean;
33
+ }
34
+ /**
35
+ * Spectral clustering, the index-based port of the legacy `spectralClustering`: the k eigenvectors
36
+ * of the graph Laplacian with the smallest eigenvalues embed every node as a point in k dimensions,
37
+ * and k-means splits the points into at most k clusters.
38
+ *
39
+ * The Laplacian is applied straight from the CSR rows and never built: `D - A`
40
+ * (`unnormalized`), `I - D^-1/2 A D^-1/2` (`normalized`, whose embedded rows are then scaled to
41
+ * unit length, as Ng, Jordan and Weiss do) or `I - D^-1 A` (`randomWalk`, whose eigenvectors are
42
+ * `D^-1/2` times the normalized ones). A node of degree 0 has a zero row. Self-loops are ignored
43
+ * (they cancel in `D - A`) and a directed snapshot is read as undirected, a reciprocal pair
44
+ * summing. A negative, NaN or infinite weight throws. With `k >= nodeCount` every node is its own
45
+ * cluster.
46
+ *
47
+ * The eigenpairs come from subspace iteration with Rayleigh-Ritz, so they are the true smallest
48
+ * ones when `converged` is true; `converged` is false when the iteration stopped at its round cap
49
+ * (a small spectral gap, such as a path of hundreds of nodes), and the eigenpairs are then only
50
+ * approximate. The legacy function uses fixed placeholder eigenvalues for k <= 3 and plain power
51
+ * iteration (which finds the LARGEST eigenvectors) above that, and draws its k-means seeds from
52
+ * `Math.random` unless given a seed. The partitions therefore differ from the legacy function's;
53
+ * one `seed` gives one result. Labels are dense in first-seen order, so `count` is below k when
54
+ * k-means leaves a cluster empty.
55
+ * @param s - Any snapshot
56
+ * @param options - k, the Laplacian, the k-means limits, the seed and the weight override
57
+ * @returns The partition and the eigenpairs it came from
58
+ * @public
59
+ */
60
+ export declare function spectralClustering(s: GraphSnapshot, options: SpectralOptions): SpectralResult;
61
+ //# sourceMappingURL=spectral.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spectral.d.ts","sourceRoot":"","sources":["../../../src/indexed/spectral.ts"],"names":[],"mappings":"AAAA,OAAO,EAEH,KAAK,aAAa,EAClB,KAAK,aAAa,EAGrB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,KAAK,WAAW,EAAc,MAAM,iBAAiB,CAAC;AAE/D,8DAA8D;AAC9D,MAAM,MAAM,aAAa,GAAG,cAAc,GAAG,YAAY,GAAG,YAAY,CAAC;AAEzE,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC5B,8CAA8C;IAC9C,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,wHAAwH;IACxH,QAAQ,CAAC,aAAa,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;IACnD,0CAA0C;IAC1C,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5C,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACxC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,iGAAiG;IACjG,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAChD;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAe,SAAQ,WAAW;IAC/C,yFAAyF;IACzF,QAAQ,CAAC,WAAW,EAAE,YAAY,CAAC;IACnC,sFAAsF;IACtF,QAAQ,CAAC,YAAY,EAAE,YAAY,EAAE,CAAC;IACtC;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;CAC/B;AAkYD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,cAAc,CA+F7F"}
@@ -0,0 +1,476 @@
1
+ import { renumberPartition, } from "@graphty/graph-format";
2
+ import { withGroups } from "./components.js";
3
+ /** Relative eigenvector residual at which subspace iteration stops. */
4
+ const RESIDUAL_TOLERANCE = 1e-11;
5
+ /** Subspace-iteration cap; each round costs one sparse product per block vector. */
6
+ const MAX_SUBSPACE_ROUNDS = 3000;
7
+ /**
8
+ * mulberry32, the generator the label propagation port uses.
9
+ * @param seed - Generator seed; only its low 32 bits are used
10
+ * @returns The generator, output in [0, 1)
11
+ */
12
+ function mulberry32(seed) {
13
+ let a = seed >>> 0;
14
+ return () => {
15
+ a = (a + 0x6d2b79f5) | 0;
16
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
17
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
18
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
19
+ };
20
+ }
21
+ /**
22
+ * The snapshot's adjacency made symmetric -- a directed snapshot's out-row and in-row together, so
23
+ * a reciprocal pair sums -- with self-loops dropped (they cancel in `D - A`) and weights checked.
24
+ * @param s - The snapshot
25
+ * @param weights - Per-arc weights, or null for all ones
26
+ * @returns The rows and the weighted degrees
27
+ */
28
+ function symmetricRows(s, weights) {
29
+ const n = s.nodeCount;
30
+ const views = [{ view: s, arcOf: (a) => a }];
31
+ if (s.directed) {
32
+ const rev = s.reverse();
33
+ views.push({ view: rev, arcOf: (k) => rev.fwdArc[k] });
34
+ }
35
+ const rowPtr = new Uint32Array(n + 1);
36
+ for (let u = 0; u < n; u++) {
37
+ let kept = 0;
38
+ for (const { view } of views) {
39
+ for (let a = view.rowPtr[u]; a < view.rowPtr[u + 1]; a++) {
40
+ kept += view.colIdx[a] === u ? 0 : 1;
41
+ }
42
+ }
43
+ rowPtr[u + 1] = rowPtr[u] + kept;
44
+ }
45
+ const colIdx = new Uint32Array(rowPtr[n]);
46
+ const w = new Float64Array(rowPtr[n]);
47
+ const degree = new Float64Array(n);
48
+ let k = 0;
49
+ for (let u = 0; u < n; u++) {
50
+ for (const { view, arcOf } of views) {
51
+ for (let a = view.rowPtr[u]; a < view.rowPtr[u + 1]; a++) {
52
+ const weight = weights === null ? 1 : weights[arcOf(a)];
53
+ if (!(weight >= 0) || weight === Infinity) {
54
+ throw new RangeError(`an arc has weight ${weight}; spectral clustering needs finite, non-negative weights`);
55
+ }
56
+ if (view.colIdx[a] !== u) {
57
+ colIdx[k] = view.colIdx[a];
58
+ w[k++] = weight;
59
+ degree[u] += weight;
60
+ }
61
+ }
62
+ }
63
+ }
64
+ return { rowPtr, colIdx, w, degree };
65
+ }
66
+ /**
67
+ * Orthonormalise the columns of `block` in place (Gram-Schmidt, applied twice for stability). A
68
+ * column that collapses is replaced by a fresh random one.
69
+ * @param block - p columns of length n
70
+ * @param random - The generator for replacements
71
+ */
72
+ function orthonormalise(block, random) {
73
+ for (let j = 0; j < block.length; j++) {
74
+ const v = block[j];
75
+ for (let attempt = 0;; attempt++) {
76
+ for (let pass = 0; pass < 2; pass++) {
77
+ for (let i = 0; i < j; i++) {
78
+ const q = block[i];
79
+ let dot = 0;
80
+ for (let r = 0; r < v.length; r++) {
81
+ dot += q[r] * v[r];
82
+ }
83
+ for (let r = 0; r < v.length; r++) {
84
+ v[r] -= dot * q[r];
85
+ }
86
+ }
87
+ }
88
+ let norm = 0;
89
+ for (let r = 0; r < v.length; r++) {
90
+ norm += v[r] * v[r];
91
+ }
92
+ norm = Math.sqrt(norm);
93
+ if (norm > 1e-10 || attempt === 3) {
94
+ for (let r = 0; r < v.length; r++) {
95
+ v[r] = norm > 0 ? v[r] / norm : 0;
96
+ }
97
+ break;
98
+ }
99
+ for (let r = 0; r < v.length; r++) {
100
+ v[r] = random() - 0.5;
101
+ }
102
+ }
103
+ }
104
+ }
105
+ /**
106
+ * Eigen-decomposition of a small symmetric matrix by cyclic Jacobi rotations.
107
+ * @param a - p x p row-major, destroyed
108
+ * @param p - The order
109
+ * @returns The eigenvalues and the eigenvectors as the columns of a p x p row-major matrix
110
+ */
111
+ function jacobi(a, p) {
112
+ const v = new Float64Array(p * p);
113
+ for (let i = 0; i < p; i++) {
114
+ v[i * p + i] = 1;
115
+ }
116
+ for (let sweep = 0; sweep < 100; sweep++) {
117
+ let off = 0;
118
+ for (let i = 0; i < p; i++) {
119
+ for (let j = i + 1; j < p; j++) {
120
+ off += a[i * p + j] * a[i * p + j];
121
+ }
122
+ }
123
+ if (off < 1e-30) {
124
+ break;
125
+ }
126
+ for (let i = 0; i < p; i++) {
127
+ for (let j = i + 1; j < p; j++) {
128
+ const aij = a[i * p + j];
129
+ if (aij === 0) {
130
+ continue;
131
+ }
132
+ const theta = (a[j * p + j] - a[i * p + i]) / (2 * aij);
133
+ const t = Math.sign(theta || 1) / (Math.abs(theta) + Math.sqrt(theta * theta + 1));
134
+ const c = 1 / Math.sqrt(t * t + 1);
135
+ const sn = t * c;
136
+ for (let r = 0; r < p; r++) {
137
+ const ari = a[r * p + i];
138
+ const arj = a[r * p + j];
139
+ a[r * p + i] = c * ari - sn * arj;
140
+ a[r * p + j] = sn * ari + c * arj;
141
+ }
142
+ for (let r = 0; r < p; r++) {
143
+ const air = a[i * p + r];
144
+ const ajr = a[j * p + r];
145
+ a[i * p + r] = c * air - sn * ajr;
146
+ a[j * p + r] = sn * air + c * ajr;
147
+ }
148
+ for (let r = 0; r < p; r++) {
149
+ const vri = v[r * p + i];
150
+ const vrj = v[r * p + j];
151
+ v[r * p + i] = c * vri - sn * vrj;
152
+ v[r * p + j] = sn * vri + c * vrj;
153
+ }
154
+ }
155
+ }
156
+ }
157
+ return { values: Float64Array.from({ length: p }, (_, i) => a[i * p + i]), vectors: v };
158
+ }
159
+ /**
160
+ * The k smallest eigenpairs of a symmetric Laplacian, by subspace iteration on `shift * I - L` (whose
161
+ * largest eigenpairs they are) with a Rayleigh-Ritz step, over a block a few vectors wider than k.
162
+ * @param apply - Writes `L x` into `out`
163
+ * @param n - Order
164
+ * @param k - Eigenpairs wanted
165
+ * @param shift - An upper bound on the largest eigenvalue of L
166
+ * @param random - The generator of the starting block
167
+ * @returns Eigenvalues ascending, their eigenvectors, and whether they met the tolerance
168
+ */
169
+ function smallestEigenpairs(apply, n, k, shift, random) {
170
+ const p = Math.min(n, k + Math.max(k, 8));
171
+ let block = Array.from({ length: p }, () => Float64Array.from({ length: n }, () => random() - 0.5));
172
+ orthonormalise(block, random);
173
+ const lx = new Float64Array(n);
174
+ const shifted = (x) => {
175
+ apply(x, lx);
176
+ return x.map((xi, r) => shift * xi - lx[r]);
177
+ };
178
+ let ritz = new Float64Array(p);
179
+ let order = [];
180
+ let vectors = new Float64Array(p * p);
181
+ let converged = false;
182
+ // ponytail: plain subspace iteration converges at the ratio of neighbouring shifted eigenvalues,
183
+ // so a small spectral gap hits the cap; Chebyshev filtering would converge far faster there.
184
+ for (let round = 0; round < MAX_SUBSPACE_ROUNDS; round++) {
185
+ const image = block.map(shifted);
186
+ // Rayleigh-Ritz: the block's projection of the shifted operator, diagonalised.
187
+ const h = new Float64Array(p * p);
188
+ for (let i = 0; i < p; i++) {
189
+ for (let j = i; j < p; j++) {
190
+ let dot = 0;
191
+ for (let r = 0; r < n; r++) {
192
+ dot += block[i][r] * image[j][r];
193
+ }
194
+ h[i * p + j] = dot;
195
+ h[j * p + i] = dot;
196
+ }
197
+ }
198
+ const eig = jacobi(h, p);
199
+ ({ values: ritz, vectors } = eig);
200
+ order = Array.from({ length: p }, (_, i) => i).sort((a, b) => ritz[b] - ritz[a]);
201
+ // Converged when every wanted Ritz pair (y = X q, theta) has a residual |B y - theta y|
202
+ // below RESIDUAL_TOLERANCE * shift; B y is the image block times q.
203
+ converged = order.slice(0, k).every((col) => {
204
+ let residual = 0;
205
+ for (let r = 0; r < n; r++) {
206
+ let diff = 0;
207
+ for (let i = 0; i < p; i++) {
208
+ diff += vectors[i * p + col] * (image[i][r] - ritz[col] * block[i][r]);
209
+ }
210
+ residual += diff * diff;
211
+ }
212
+ return Math.sqrt(residual) <= RESIDUAL_TOLERANCE * shift;
213
+ });
214
+ if (converged || round === MAX_SUBSPACE_ROUNDS - 1) {
215
+ break; // at the cap the Ritz pairs of this block are the best estimate there is
216
+ }
217
+ block = image;
218
+ orthonormalise(block, random);
219
+ }
220
+ const values = new Float64Array(k);
221
+ const out = [];
222
+ for (let rank = 0; rank < k; rank++) {
223
+ const col = order[rank];
224
+ values[rank] = Math.max(0, shift - ritz[col]);
225
+ const v = new Float64Array(n);
226
+ for (let i = 0; i < p; i++) {
227
+ const c = vectors[i * p + col];
228
+ for (let r = 0; r < n; r++) {
229
+ v[r] += c * block[i][r];
230
+ }
231
+ }
232
+ out.push(v);
233
+ }
234
+ return { values, vectors: out, converged };
235
+ }
236
+ /** k-means runs per clustering; the one with the lowest inertia is kept. */
237
+ const K_MEANS_RUNS = 10;
238
+ /**
239
+ * The k-means objective: the summed squared distance of every row from its cluster's mean.
240
+ * @param points - n rows of length d, row-major
241
+ * @param assign - The cluster of every row
242
+ * @param n - Row count
243
+ * @param d - Row length
244
+ * @param k - Cluster count
245
+ * @returns The inertia
246
+ */
247
+ function inertiaOf(points, assign, n, d, k) {
248
+ const sums = new Float64Array(k * d);
249
+ const sizes = new Float64Array(k);
250
+ for (let i = 0; i < n; i++) {
251
+ sizes[assign[i]]++;
252
+ for (let t = 0; t < d; t++) {
253
+ sums[assign[i] * d + t] += points[i * d + t];
254
+ }
255
+ }
256
+ let total = 0;
257
+ for (let i = 0; i < n; i++) {
258
+ const c = assign[i];
259
+ for (let t = 0; t < d; t++) {
260
+ total += (points[i * d + t] - sums[c * d + t] / sizes[c]) ** 2;
261
+ }
262
+ }
263
+ return total;
264
+ }
265
+ /**
266
+ * k-means over the rows of an n x d embedding, seeded with k-means++ and refined by Lloyd rounds.
267
+ * @param points - n rows of length d, row-major
268
+ * @param n - Row count
269
+ * @param d - Row length
270
+ * @param k - Cluster count
271
+ * @param maxIterations - Round cap
272
+ * @param tolerance - Stop when no centroid moves further than this
273
+ * @param random - The generator
274
+ * @returns The cluster of every row
275
+ */
276
+ function kMeans(points, n, d, k, maxIterations, tolerance, random) {
277
+ const dist2 = (i, centres, c) => {
278
+ let sum = 0;
279
+ for (let t = 0; t < d; t++) {
280
+ const diff = points[i * d + t] - centres[c * d + t];
281
+ sum += diff * diff;
282
+ }
283
+ return sum;
284
+ };
285
+ const centres = new Float64Array(k * d);
286
+ const nearest = new Float64Array(n).fill(Infinity);
287
+ let pick = Math.floor(random() * n);
288
+ for (let c = 0; c < k; c++) {
289
+ centres.set(points.subarray(pick * d, pick * d + d), c * d);
290
+ let total = 0;
291
+ for (let i = 0; i < n; i++) {
292
+ nearest[i] = Math.min(nearest[i], dist2(i, centres, c));
293
+ total += nearest[i];
294
+ }
295
+ // k-means++: the next centre is a row drawn with probability proportional to its squared
296
+ // distance from the nearest centre so far (the first row when every row sits on a centre).
297
+ let target = random() * total;
298
+ pick = 0;
299
+ for (let i = 0; i < n && total > 0; i++) {
300
+ target -= nearest[i];
301
+ if (target < 0) {
302
+ pick = i;
303
+ break;
304
+ }
305
+ }
306
+ }
307
+ const assign = new Uint32Array(n);
308
+ for (let round = 0; round < maxIterations; round++) {
309
+ let changed = round === 0;
310
+ for (let i = 0; i < n; i++) {
311
+ let best = 0;
312
+ let bestDist = Infinity;
313
+ for (let c = 0; c < k; c++) {
314
+ const dd = dist2(i, centres, c);
315
+ if (dd < bestDist) {
316
+ bestDist = dd;
317
+ best = c;
318
+ }
319
+ }
320
+ changed || (changed = assign[i] !== best);
321
+ assign[i] = best;
322
+ }
323
+ if (!changed) {
324
+ break;
325
+ }
326
+ const sums = new Float64Array(k * d);
327
+ const sizes = new Float64Array(k);
328
+ for (let i = 0; i < n; i++) {
329
+ sizes[assign[i]]++;
330
+ for (let t = 0; t < d; t++) {
331
+ sums[assign[i] * d + t] += points[i * d + t];
332
+ }
333
+ }
334
+ let shift = 0;
335
+ for (let c = 0; c < k; c++) {
336
+ if (sizes[c] === 0) {
337
+ continue; // an emptied cluster keeps its centre
338
+ }
339
+ let moved = 0;
340
+ for (let t = 0; t < d; t++) {
341
+ const next = sums[c * d + t] / sizes[c];
342
+ moved += (next - centres[c * d + t]) ** 2;
343
+ centres[c * d + t] = next;
344
+ }
345
+ shift = Math.max(shift, Math.sqrt(moved));
346
+ }
347
+ if (shift < tolerance) {
348
+ break;
349
+ }
350
+ }
351
+ return assign;
352
+ }
353
+ /**
354
+ * Spectral clustering, the index-based port of the legacy `spectralClustering`: the k eigenvectors
355
+ * of the graph Laplacian with the smallest eigenvalues embed every node as a point in k dimensions,
356
+ * and k-means splits the points into at most k clusters.
357
+ *
358
+ * The Laplacian is applied straight from the CSR rows and never built: `D - A`
359
+ * (`unnormalized`), `I - D^-1/2 A D^-1/2` (`normalized`, whose embedded rows are then scaled to
360
+ * unit length, as Ng, Jordan and Weiss do) or `I - D^-1 A` (`randomWalk`, whose eigenvectors are
361
+ * `D^-1/2` times the normalized ones). A node of degree 0 has a zero row. Self-loops are ignored
362
+ * (they cancel in `D - A`) and a directed snapshot is read as undirected, a reciprocal pair
363
+ * summing. A negative, NaN or infinite weight throws. With `k >= nodeCount` every node is its own
364
+ * cluster.
365
+ *
366
+ * The eigenpairs come from subspace iteration with Rayleigh-Ritz, so they are the true smallest
367
+ * ones when `converged` is true; `converged` is false when the iteration stopped at its round cap
368
+ * (a small spectral gap, such as a path of hundreds of nodes), and the eigenpairs are then only
369
+ * approximate. The legacy function uses fixed placeholder eigenvalues for k <= 3 and plain power
370
+ * iteration (which finds the LARGEST eigenvectors) above that, and draws its k-means seeds from
371
+ * `Math.random` unless given a seed. The partitions therefore differ from the legacy function's;
372
+ * one `seed` gives one result. Labels are dense in first-seen order, so `count` is below k when
373
+ * k-means leaves a cluster empty.
374
+ * @param s - Any snapshot
375
+ * @param options - k, the Laplacian, the k-means limits, the seed and the weight override
376
+ * @returns The partition and the eigenpairs it came from
377
+ * @public
378
+ */
379
+ export function spectralClustering(s, options) {
380
+ const { k } = options;
381
+ const type = options.laplacianType ?? "normalized";
382
+ const maxIterations = options.maxIterations ?? 100;
383
+ const tolerance = options.tolerance ?? 1e-4;
384
+ const seed = options.seed ?? 42;
385
+ if (!Number.isInteger(k) || k < 1) {
386
+ throw new RangeError(`k must be a positive integer, got ${k}`);
387
+ }
388
+ if (!["unnormalized", "normalized", "randomWalk"].includes(type)) {
389
+ throw new RangeError(`unknown laplacianType "${type}"`);
390
+ }
391
+ if (!Number.isInteger(maxIterations) || maxIterations < 0 || !(tolerance >= 0) || !Number.isInteger(seed)) {
392
+ throw new RangeError("maxIterations and seed must be integers, maxIterations and tolerance non-negative");
393
+ }
394
+ const weights = options.weights ?? s.weights;
395
+ if (weights !== null && weights.length !== s.arcCount) {
396
+ throw new RangeError(`weights has ${weights.length} entries; the snapshot has ${s.arcCount} arcs`);
397
+ }
398
+ const n = s.nodeCount;
399
+ if (k >= n) {
400
+ const labels = Uint32Array.from({ length: n }, (_, i) => i);
401
+ return { ...withGroups(labels, n), eigenvalues: new Float64Array(0), eigenvectors: [], converged: true };
402
+ }
403
+ const g = symmetricRows(s, weights);
404
+ const scale = g.degree.map((d) => (d > 0 ? 1 / Math.sqrt(d) : 0));
405
+ const normalised = type !== "unnormalized";
406
+ const apply = (x, out) => {
407
+ for (let u = 0; u < n; u++) {
408
+ let ax = 0;
409
+ for (let a = g.rowPtr[u]; a < g.rowPtr[u + 1]; a++) {
410
+ const v = g.colIdx[a];
411
+ ax += normalised ? g.w[a] * scale[v] * x[v] : g.w[a] * x[v];
412
+ }
413
+ if (normalised) {
414
+ out[u] = g.degree[u] > 0 ? x[u] - scale[u] * ax : 0;
415
+ }
416
+ else {
417
+ out[u] = g.degree[u] * x[u] - ax;
418
+ }
419
+ }
420
+ };
421
+ // The largest eigenvalue is at most 2 for the normalised Laplacian, and at most the largest
422
+ // d(u) + d(v) over the edges for D - A (Anderson and Morley, 1985).
423
+ let shift = 2;
424
+ if (!normalised) {
425
+ shift = 1;
426
+ for (let u = 0; u < n; u++) {
427
+ for (let a = g.rowPtr[u]; a < g.rowPtr[u + 1]; a++) {
428
+ shift = Math.max(shift, g.degree[u] + g.degree[g.colIdx[a]]);
429
+ }
430
+ }
431
+ }
432
+ const random = mulberry32(seed);
433
+ const eig = smallestEigenpairs(apply, n, k, shift, random);
434
+ if (type === "randomWalk") {
435
+ for (const v of eig.vectors) {
436
+ for (let i = 0; i < n; i++) {
437
+ v[i] *= g.degree[i] > 0 ? scale[i] : 1;
438
+ }
439
+ }
440
+ }
441
+ const points = new Float64Array(n * k);
442
+ for (let c = 0; c < k; c++) {
443
+ for (let i = 0; i < n; i++) {
444
+ points[i * k + c] = eig.vectors[c][i];
445
+ }
446
+ }
447
+ if (type === "normalized") {
448
+ for (let i = 0; i < n; i++) {
449
+ const row = points.subarray(i * k, i * k + k);
450
+ const norm = Math.hypot(...row);
451
+ if (norm > 0) {
452
+ row.forEach((x, c) => (row[c] = x / norm));
453
+ }
454
+ }
455
+ }
456
+ // Ten seeded k-means runs, keeping the tightest (the scikit-learn default): one run from an
457
+ // unlucky seeding can split a clean cluster.
458
+ let best = new Uint32Array(n);
459
+ let bestInertia = Infinity;
460
+ for (let run = 0; run < K_MEANS_RUNS; run++) {
461
+ const assign = kMeans(points, n, k, k, maxIterations, tolerance, random);
462
+ const inertia = inertiaOf(points, assign, n, k, k);
463
+ if (inertia < bestInertia) {
464
+ bestInertia = inertia;
465
+ best = assign;
466
+ }
467
+ }
468
+ const { labels, count } = renumberPartition(best);
469
+ return {
470
+ ...withGroups(labels, count),
471
+ eigenvalues: eig.values,
472
+ eigenvectors: eig.vectors,
473
+ converged: eig.converged,
474
+ };
475
+ }
476
+ //# sourceMappingURL=spectral.js.map