graphwise 1.11.0 → 1.12.1

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 (293) hide show
  1. package/README.md +17 -17
  2. package/dist/__test__/fixtures/graphs/linear-chain.d.ts +3 -3
  3. package/dist/__test__/fixtures/helpers.d.ts +12 -12
  4. package/dist/__test__/fixtures/helpers.d.ts.map +1 -1
  5. package/dist/__test__/fixtures/index.d.ts +1 -1
  6. package/dist/__test__/fixtures/metrics.d.ts +10 -10
  7. package/dist/__test__/fixtures/metrics.d.ts.map +1 -1
  8. package/dist/__test__/fixtures/types.d.ts +2 -2
  9. package/dist/__test__/fixtures/types.d.ts.map +1 -1
  10. package/dist/async/protocol.d.ts +1 -1
  11. package/dist/async/protocol.d.ts.map +1 -1
  12. package/dist/{expansion → exploration}/base-core.d.ts +5 -5
  13. package/dist/exploration/base-core.d.ts.map +1 -0
  14. package/dist/exploration/base-core.unit.test.d.ts.map +1 -0
  15. package/dist/{expansion → exploration}/base-helpers.d.ts +11 -11
  16. package/dist/exploration/base-helpers.d.ts.map +1 -0
  17. package/dist/{expansion → exploration}/base.d.ts +11 -11
  18. package/dist/exploration/base.d.ts.map +1 -0
  19. package/dist/exploration/base.unit.test.d.ts.map +1 -0
  20. package/dist/{expansion → exploration}/comparison.integration.test.d.ts +2 -2
  21. package/dist/exploration/comparison.integration.test.d.ts.map +1 -0
  22. package/dist/{expansion → exploration}/dfs-priority.d.ts +8 -8
  23. package/dist/exploration/dfs-priority.d.ts.map +1 -0
  24. package/dist/exploration/dfs-priority.unit.test.d.ts.map +1 -0
  25. package/dist/{expansion → exploration}/dome.d.ts +13 -13
  26. package/dist/exploration/dome.d.ts.map +1 -0
  27. package/dist/{expansion → exploration}/dome.integration.test.d.ts +2 -2
  28. package/dist/exploration/dome.integration.test.d.ts.map +1 -0
  29. package/dist/exploration/dome.unit.test.d.ts.map +1 -0
  30. package/dist/{expansion → exploration}/edge.d.ts +8 -8
  31. package/dist/exploration/edge.d.ts.map +1 -0
  32. package/dist/{expansion → exploration}/edge.integration.test.d.ts +1 -1
  33. package/dist/exploration/edge.integration.test.d.ts.map +1 -0
  34. package/dist/exploration/edge.unit.test.d.ts.map +1 -0
  35. package/dist/{expansion → exploration}/flux.d.ts +11 -11
  36. package/dist/exploration/flux.d.ts.map +1 -0
  37. package/dist/{expansion → exploration}/flux.integration.test.d.ts +1 -1
  38. package/dist/exploration/flux.integration.test.d.ts.map +1 -0
  39. package/dist/exploration/flux.unit.test.d.ts.map +1 -0
  40. package/dist/{expansion → exploration}/frontier-balanced.d.ts +8 -8
  41. package/dist/exploration/frontier-balanced.d.ts.map +1 -0
  42. package/dist/exploration/frontier-balanced.unit.test.d.ts.map +1 -0
  43. package/dist/{expansion → exploration}/fuse.d.ts +10 -10
  44. package/dist/exploration/fuse.d.ts.map +1 -0
  45. package/dist/{expansion → exploration}/fuse.integration.test.d.ts +1 -1
  46. package/dist/exploration/fuse.integration.test.d.ts.map +1 -0
  47. package/dist/exploration/fuse.unit.test.d.ts.map +1 -0
  48. package/dist/{expansion → exploration}/hae.d.ts +10 -10
  49. package/dist/exploration/hae.d.ts.map +1 -0
  50. package/dist/{expansion → exploration}/hae.integration.test.d.ts +2 -2
  51. package/dist/exploration/hae.integration.test.d.ts.map +1 -0
  52. package/dist/exploration/hae.unit.test.d.ts.map +1 -0
  53. package/dist/exploration/index.cjs +51 -0
  54. package/dist/{expansion → exploration}/index.d.ts +3 -3
  55. package/dist/exploration/index.d.ts.map +1 -0
  56. package/dist/{expansion → exploration}/index.js +1 -1
  57. package/dist/{expansion → exploration}/k-hop.d.ts +6 -6
  58. package/dist/exploration/k-hop.d.ts.map +1 -0
  59. package/dist/exploration/k-hop.unit.test.d.ts.map +1 -0
  60. package/dist/{expansion → exploration}/lace.d.ts +11 -11
  61. package/dist/exploration/lace.d.ts.map +1 -0
  62. package/dist/{expansion → exploration}/lace.integration.test.d.ts +1 -1
  63. package/dist/exploration/lace.integration.test.d.ts.map +1 -0
  64. package/dist/exploration/lace.unit.test.d.ts.map +1 -0
  65. package/dist/{expansion → exploration}/maze.d.ts +9 -9
  66. package/dist/exploration/maze.d.ts.map +1 -0
  67. package/dist/{expansion → exploration}/maze.integration.test.d.ts +1 -1
  68. package/dist/exploration/maze.integration.test.d.ts.map +1 -0
  69. package/dist/exploration/maze.unit.test.d.ts.map +1 -0
  70. package/dist/{expansion → exploration}/pipe.d.ts +8 -8
  71. package/dist/exploration/pipe.d.ts.map +1 -0
  72. package/dist/{expansion → exploration}/pipe.integration.test.d.ts +2 -2
  73. package/dist/exploration/pipe.integration.test.d.ts.map +1 -0
  74. package/dist/exploration/pipe.unit.test.d.ts.map +1 -0
  75. package/dist/{expansion → exploration}/priority-helpers.d.ts +2 -2
  76. package/dist/exploration/priority-helpers.d.ts.map +1 -0
  77. package/dist/{expansion → exploration}/random-priority.d.ts +11 -11
  78. package/dist/exploration/random-priority.d.ts.map +1 -0
  79. package/dist/exploration/random-priority.unit.test.d.ts.map +1 -0
  80. package/dist/{expansion → exploration}/random-walk.d.ts +6 -6
  81. package/dist/exploration/random-walk.d.ts.map +1 -0
  82. package/dist/exploration/random-walk.unit.test.d.ts.map +1 -0
  83. package/dist/{expansion → exploration}/reach.d.ts +9 -9
  84. package/dist/exploration/reach.d.ts.map +1 -0
  85. package/dist/{expansion → exploration}/reach.integration.test.d.ts +1 -1
  86. package/dist/exploration/reach.integration.test.d.ts.map +1 -0
  87. package/dist/exploration/reach.unit.test.d.ts.map +1 -0
  88. package/dist/{expansion → exploration}/sage.d.ts +9 -9
  89. package/dist/exploration/sage.d.ts.map +1 -0
  90. package/dist/{expansion → exploration}/sage.integration.test.d.ts +1 -1
  91. package/dist/exploration/sage.integration.test.d.ts.map +1 -0
  92. package/dist/exploration/sage.unit.test.d.ts.map +1 -0
  93. package/dist/{expansion → exploration}/sift.d.ts +12 -12
  94. package/dist/exploration/sift.d.ts.map +1 -0
  95. package/dist/{expansion → exploration}/sift.integration.test.d.ts +1 -1
  96. package/dist/exploration/sift.integration.test.d.ts.map +1 -0
  97. package/dist/exploration/sift.unit.test.d.ts.map +1 -0
  98. package/dist/{expansion → exploration}/standard-bfs.d.ts +8 -8
  99. package/dist/exploration/standard-bfs.d.ts.map +1 -0
  100. package/dist/exploration/standard-bfs.unit.test.d.ts.map +1 -0
  101. package/dist/{expansion → exploration}/tide.d.ts +8 -8
  102. package/dist/exploration/tide.d.ts.map +1 -0
  103. package/dist/{expansion → exploration}/tide.integration.test.d.ts +1 -1
  104. package/dist/exploration/tide.integration.test.d.ts.map +1 -0
  105. package/dist/exploration/tide.unit.test.d.ts.map +1 -0
  106. package/dist/{expansion → exploration}/types.d.ts +17 -17
  107. package/dist/exploration/types.d.ts.map +1 -0
  108. package/dist/{expansion → exploration}/warp.d.ts +8 -8
  109. package/dist/exploration/warp.d.ts.map +1 -0
  110. package/dist/{expansion → exploration}/warp.integration.test.d.ts +1 -1
  111. package/dist/exploration/warp.integration.test.d.ts.map +1 -0
  112. package/dist/exploration/warp.unit.test.d.ts.map +1 -0
  113. package/dist/{expansion--UuRowv-.cjs → exploration-BfDi17av.cjs} +136 -136
  114. package/dist/exploration-BfDi17av.cjs.map +1 -0
  115. package/dist/{expansion-CZLNK6Pr.js → exploration-DKjRNxS5.js} +136 -136
  116. package/dist/exploration-DKjRNxS5.js.map +1 -0
  117. package/dist/gpu/index.cjs +1 -1
  118. package/dist/gpu/index.js +1 -1
  119. package/dist/index/index.cjs +57 -53
  120. package/dist/index/index.js +5 -5
  121. package/dist/index.d.ts +1 -1
  122. package/dist/index.d.ts.map +1 -1
  123. package/dist/{kernel-CigCjrts.js → kernel-BLwhyVSV.js} +1 -1
  124. package/dist/{kernel-CigCjrts.js.map → kernel-BLwhyVSV.js.map} +1 -1
  125. package/dist/{kernel-2oH4Cn32.cjs → kernel-BffKjhZS.cjs} +1 -1
  126. package/dist/{kernel-2oH4Cn32.cjs.map → kernel-BffKjhZS.cjs.map} +1 -1
  127. package/dist/{kernel-CXeGBH3s.cjs → kernel-CbP715Sq.cjs} +1 -1
  128. package/dist/{kernel-CXeGBH3s.cjs.map → kernel-CbP715Sq.cjs.map} +1 -1
  129. package/dist/{kernel-CvnRsF7E.js → kernel-DolEKSSx.js} +1 -1
  130. package/dist/{kernel-CvnRsF7E.js.map → kernel-DolEKSSx.js.map} +1 -1
  131. package/dist/{kernel-DukrXtVb.cjs → kernel-E_h47HjZ.cjs} +1 -1
  132. package/dist/{kernel-DukrXtVb.cjs.map → kernel-E_h47HjZ.cjs.map} +1 -1
  133. package/dist/{kernel-6deK9fh1.js → kernel-lYa4TYth.js} +1 -1
  134. package/dist/{kernel-6deK9fh1.js.map → kernel-lYa4TYth.js.map} +1 -1
  135. package/dist/{operations-D-RB67WP.cjs → operations-CSU0yFPr.cjs} +4 -4
  136. package/dist/{operations-D-RB67WP.cjs.map → operations-CSU0yFPr.cjs.map} +1 -1
  137. package/dist/{operations-D9otVlIH.js → operations-CdrA87Au.js} +4 -4
  138. package/dist/{operations-D9otVlIH.js.map → operations-CdrA87Au.js.map} +1 -1
  139. package/dist/ranking/baselines/betweenness.d.ts +2 -2
  140. package/dist/ranking/baselines/betweenness.d.ts.map +1 -1
  141. package/dist/ranking/baselines/communicability.d.ts +3 -3
  142. package/dist/ranking/baselines/communicability.d.ts.map +1 -1
  143. package/dist/ranking/baselines/degree-sum.d.ts +2 -2
  144. package/dist/ranking/baselines/degree-sum.d.ts.map +1 -1
  145. package/dist/ranking/baselines/hitting-time.d.ts +2 -2
  146. package/dist/ranking/baselines/hitting-time.d.ts.map +1 -1
  147. package/dist/ranking/baselines/jaccard-arithmetic.d.ts +2 -2
  148. package/dist/ranking/baselines/jaccard-arithmetic.d.ts.map +1 -1
  149. package/dist/ranking/baselines/katz.d.ts +3 -3
  150. package/dist/ranking/baselines/katz.d.ts.map +1 -1
  151. package/dist/ranking/baselines/pagerank.d.ts +3 -3
  152. package/dist/ranking/baselines/pagerank.d.ts.map +1 -1
  153. package/dist/ranking/baselines/random-ranking.d.ts +2 -2
  154. package/dist/ranking/baselines/random-ranking.d.ts.map +1 -1
  155. package/dist/ranking/baselines/resistance-distance.d.ts +2 -2
  156. package/dist/ranking/baselines/resistance-distance.d.ts.map +1 -1
  157. package/dist/ranking/baselines/shortest.d.ts +2 -2
  158. package/dist/ranking/baselines/shortest.d.ts.map +1 -1
  159. package/dist/ranking/baselines/types.d.ts +3 -3
  160. package/dist/ranking/baselines/types.d.ts.map +1 -1
  161. package/dist/ranking/baselines/utils.d.ts +3 -3
  162. package/dist/ranking/baselines/utils.d.ts.map +1 -1
  163. package/dist/ranking/baselines/widest-path.d.ts +2 -2
  164. package/dist/ranking/baselines/widest-path.d.ts.map +1 -1
  165. package/dist/ranking/index.cjs +1 -1
  166. package/dist/ranking/index.js +1 -1
  167. package/dist/ranking/parse-gpu.d.ts +2 -2
  168. package/dist/ranking/parse-gpu.d.ts.map +1 -1
  169. package/dist/ranking/parse.d.ts +4 -4
  170. package/dist/ranking/parse.d.ts.map +1 -1
  171. package/dist/{ranking-pe5UaxKg.cjs → ranking-BQqrH26-.cjs} +2 -2
  172. package/dist/ranking-BQqrH26-.cjs.map +1 -0
  173. package/dist/{ranking-DOKDBcIR.js → ranking-B_KdM8Wq.js} +2 -2
  174. package/dist/ranking-B_KdM8Wq.js.map +1 -0
  175. package/dist/schemas/graph.d.ts +1 -1
  176. package/dist/schemas/index.cjs +1 -1
  177. package/dist/schemas/index.cjs.map +1 -1
  178. package/dist/schemas/index.js +1 -1
  179. package/dist/schemas/index.js.map +1 -1
  180. package/dist/seeds/basil.d.ts +12 -0
  181. package/dist/seeds/basil.d.ts.map +1 -0
  182. package/dist/seeds/brisk.d.ts +18 -0
  183. package/dist/seeds/brisk.d.ts.map +1 -0
  184. package/dist/seeds/hybrid-core.d.ts +32 -0
  185. package/dist/seeds/hybrid-core.d.ts.map +1 -0
  186. package/dist/seeds/hybrid-ensembles.unit.test.d.ts +2 -0
  187. package/dist/seeds/hybrid-ensembles.unit.test.d.ts.map +1 -0
  188. package/dist/seeds/index.cjs +11 -1108
  189. package/dist/seeds/index.d.ts +4 -0
  190. package/dist/seeds/index.d.ts.map +1 -1
  191. package/dist/seeds/index.js +2 -1103
  192. package/dist/seeds/omnia.d.ts +12 -0
  193. package/dist/seeds/omnia.d.ts.map +1 -0
  194. package/dist/seeds/prism.d.ts +12 -0
  195. package/dist/seeds/prism.d.ts.map +1 -0
  196. package/dist/seeds--fLhoBaG.cjs +1762 -0
  197. package/dist/seeds--fLhoBaG.cjs.map +1 -0
  198. package/dist/seeds-ihozTw4J.js +1703 -0
  199. package/dist/seeds-ihozTw4J.js.map +1 -0
  200. package/dist/utils/entropy.d.ts +1 -1
  201. package/dist/utils/index.cjs +1 -1
  202. package/dist/utils/index.cjs.map +1 -1
  203. package/dist/utils/index.js +1 -1
  204. package/dist/utils/index.js.map +1 -1
  205. package/package.json +7 -7
  206. package/dist/expansion/base-core.d.ts.map +0 -1
  207. package/dist/expansion/base-core.unit.test.d.ts.map +0 -1
  208. package/dist/expansion/base-helpers.d.ts.map +0 -1
  209. package/dist/expansion/base.d.ts.map +0 -1
  210. package/dist/expansion/base.unit.test.d.ts.map +0 -1
  211. package/dist/expansion/comparison.integration.test.d.ts.map +0 -1
  212. package/dist/expansion/dfs-priority.d.ts.map +0 -1
  213. package/dist/expansion/dfs-priority.unit.test.d.ts.map +0 -1
  214. package/dist/expansion/dome.d.ts.map +0 -1
  215. package/dist/expansion/dome.integration.test.d.ts.map +0 -1
  216. package/dist/expansion/dome.unit.test.d.ts.map +0 -1
  217. package/dist/expansion/edge.d.ts.map +0 -1
  218. package/dist/expansion/edge.integration.test.d.ts.map +0 -1
  219. package/dist/expansion/edge.unit.test.d.ts.map +0 -1
  220. package/dist/expansion/flux.d.ts.map +0 -1
  221. package/dist/expansion/flux.integration.test.d.ts.map +0 -1
  222. package/dist/expansion/flux.unit.test.d.ts.map +0 -1
  223. package/dist/expansion/frontier-balanced.d.ts.map +0 -1
  224. package/dist/expansion/frontier-balanced.unit.test.d.ts.map +0 -1
  225. package/dist/expansion/fuse.d.ts.map +0 -1
  226. package/dist/expansion/fuse.integration.test.d.ts.map +0 -1
  227. package/dist/expansion/fuse.unit.test.d.ts.map +0 -1
  228. package/dist/expansion/hae.d.ts.map +0 -1
  229. package/dist/expansion/hae.integration.test.d.ts.map +0 -1
  230. package/dist/expansion/hae.unit.test.d.ts.map +0 -1
  231. package/dist/expansion/index.cjs +0 -51
  232. package/dist/expansion/index.d.ts.map +0 -1
  233. package/dist/expansion/k-hop.d.ts.map +0 -1
  234. package/dist/expansion/k-hop.unit.test.d.ts.map +0 -1
  235. package/dist/expansion/lace.d.ts.map +0 -1
  236. package/dist/expansion/lace.integration.test.d.ts.map +0 -1
  237. package/dist/expansion/lace.unit.test.d.ts.map +0 -1
  238. package/dist/expansion/maze.d.ts.map +0 -1
  239. package/dist/expansion/maze.integration.test.d.ts.map +0 -1
  240. package/dist/expansion/maze.unit.test.d.ts.map +0 -1
  241. package/dist/expansion/pipe.d.ts.map +0 -1
  242. package/dist/expansion/pipe.integration.test.d.ts.map +0 -1
  243. package/dist/expansion/pipe.unit.test.d.ts.map +0 -1
  244. package/dist/expansion/priority-helpers.d.ts.map +0 -1
  245. package/dist/expansion/random-priority.d.ts.map +0 -1
  246. package/dist/expansion/random-priority.unit.test.d.ts.map +0 -1
  247. package/dist/expansion/random-walk.d.ts.map +0 -1
  248. package/dist/expansion/random-walk.unit.test.d.ts.map +0 -1
  249. package/dist/expansion/reach.d.ts.map +0 -1
  250. package/dist/expansion/reach.integration.test.d.ts.map +0 -1
  251. package/dist/expansion/reach.unit.test.d.ts.map +0 -1
  252. package/dist/expansion/sage.d.ts.map +0 -1
  253. package/dist/expansion/sage.integration.test.d.ts.map +0 -1
  254. package/dist/expansion/sage.unit.test.d.ts.map +0 -1
  255. package/dist/expansion/sift.d.ts.map +0 -1
  256. package/dist/expansion/sift.integration.test.d.ts.map +0 -1
  257. package/dist/expansion/sift.unit.test.d.ts.map +0 -1
  258. package/dist/expansion/standard-bfs.d.ts.map +0 -1
  259. package/dist/expansion/standard-bfs.unit.test.d.ts.map +0 -1
  260. package/dist/expansion/tide.d.ts.map +0 -1
  261. package/dist/expansion/tide.integration.test.d.ts.map +0 -1
  262. package/dist/expansion/tide.unit.test.d.ts.map +0 -1
  263. package/dist/expansion/types.d.ts.map +0 -1
  264. package/dist/expansion/warp.d.ts.map +0 -1
  265. package/dist/expansion/warp.integration.test.d.ts.map +0 -1
  266. package/dist/expansion/warp.unit.test.d.ts.map +0 -1
  267. package/dist/expansion--UuRowv-.cjs.map +0 -1
  268. package/dist/expansion-CZLNK6Pr.js.map +0 -1
  269. package/dist/ranking-DOKDBcIR.js.map +0 -1
  270. package/dist/ranking-pe5UaxKg.cjs.map +0 -1
  271. package/dist/seeds/index.cjs.map +0 -1
  272. package/dist/seeds/index.js.map +0 -1
  273. /package/dist/{expansion → exploration}/base-core.unit.test.d.ts +0 -0
  274. /package/dist/{expansion → exploration}/base.unit.test.d.ts +0 -0
  275. /package/dist/{expansion → exploration}/dfs-priority.unit.test.d.ts +0 -0
  276. /package/dist/{expansion → exploration}/dome.unit.test.d.ts +0 -0
  277. /package/dist/{expansion → exploration}/edge.unit.test.d.ts +0 -0
  278. /package/dist/{expansion → exploration}/flux.unit.test.d.ts +0 -0
  279. /package/dist/{expansion → exploration}/frontier-balanced.unit.test.d.ts +0 -0
  280. /package/dist/{expansion → exploration}/fuse.unit.test.d.ts +0 -0
  281. /package/dist/{expansion → exploration}/hae.unit.test.d.ts +0 -0
  282. /package/dist/{expansion → exploration}/k-hop.unit.test.d.ts +0 -0
  283. /package/dist/{expansion → exploration}/lace.unit.test.d.ts +0 -0
  284. /package/dist/{expansion → exploration}/maze.unit.test.d.ts +0 -0
  285. /package/dist/{expansion → exploration}/pipe.unit.test.d.ts +0 -0
  286. /package/dist/{expansion → exploration}/random-priority.unit.test.d.ts +0 -0
  287. /package/dist/{expansion → exploration}/random-walk.unit.test.d.ts +0 -0
  288. /package/dist/{expansion → exploration}/reach.unit.test.d.ts +0 -0
  289. /package/dist/{expansion → exploration}/sage.unit.test.d.ts +0 -0
  290. /package/dist/{expansion → exploration}/sift.unit.test.d.ts +0 -0
  291. /package/dist/{expansion → exploration}/standard-bfs.unit.test.d.ts +0 -0
  292. /package/dist/{expansion → exploration}/tide.unit.test.d.ts +0 -0
  293. /package/dist/{expansion → exploration}/warp.unit.test.d.ts +0 -0
@@ -0,0 +1,1762 @@
1
+ const require_kmeans = require("./kmeans-CZ7tJFYw.cjs");
2
+ //#region src/seeds/crest.ts
3
+ /** Default configuration values */
4
+ var DEFAULTS$9 = {
5
+ nPairs: 100,
6
+ rngSeed: 42,
7
+ diversityThreshold: .5,
8
+ sampleSize: 5e3
9
+ };
10
+ /**
11
+ * Simple seeded pseudo-random number generator using mulberry32.
12
+ */
13
+ function createRNG$6(seed) {
14
+ let state = seed >>> 0;
15
+ return () => {
16
+ state = state + 1831565813 >>> 0;
17
+ let t = Math.imul(state ^ state >>> 15, state | 1);
18
+ t = (t ^ t >>> 7) * (t | 1640531527);
19
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
20
+ };
21
+ }
22
+ /**
23
+ * Compute community-bridge score for a pair of nodes.
24
+ *
25
+ * High score = pair connects different communities.
26
+ * Ratio = exclusive_neighbours / shared_neighbours.
27
+ *
28
+ * Nodes with many shared neighbours are in the same dense community.
29
+ * Nodes with few shared neighbours relative to exclusive ones span communities.
30
+ */
31
+ function bridgeScore(aNbrs, bNbrs, bId, aId) {
32
+ const shared = [...aNbrs].filter((x) => bNbrs.has(x)).length;
33
+ const exclusive = [...aNbrs].filter((x) => !bNbrs.has(x) && x !== bId).length + [...bNbrs].filter((x) => !aNbrs.has(x) && x !== aId).length;
34
+ if (shared === 0) return 1 / Math.max(exclusive, 1);
35
+ return exclusive / shared;
36
+ }
37
+ /**
38
+ * Compute Jaccard similarity between two sets.
39
+ */
40
+ function jaccard$3(a, b) {
41
+ const intersection = [...a].filter((x) => b.has(x)).length;
42
+ const union = new Set([...a, ...b]).size;
43
+ return union === 0 ? 0 : intersection / union;
44
+ }
45
+ /**
46
+ * CREST — Community-Revealing Edge Sampling Technique.
47
+ *
48
+ * Samples random node pairs and scores them by the ratio of exclusive
49
+ * to shared neighbours. Pairs with high ratios connect different
50
+ * communities (few shared neighbours, many exclusive). Greedy
51
+ * selection with Jaccard diversity ensures selected pairs are
52
+ * spread across different structural regions.
53
+ *
54
+ * @param graph - The graph to sample seeds from
55
+ * @param options - Configuration options
56
+ * @returns Selected seed pairs with bridge score metadata
57
+ */
58
+ function crest(graph, options = {}) {
59
+ const config = {
60
+ ...DEFAULTS$9,
61
+ ...options
62
+ };
63
+ const rng = createRNG$6(config.rngSeed);
64
+ const allNodes = [...graph.nodeIds()];
65
+ if (allNodes.length < 2) return { pairs: [] };
66
+ const candidates = [];
67
+ const sampledPairs = /* @__PURE__ */ new Set();
68
+ const maxAttempts = config.sampleSize * 10;
69
+ let attempts = 0;
70
+ while (sampledPairs.size < config.sampleSize && attempts < maxAttempts) {
71
+ attempts++;
72
+ const a = allNodes[Math.floor(rng() * allNodes.length)];
73
+ const b = allNodes[Math.floor(rng() * allNodes.length)];
74
+ if (a === void 0 || b === void 0 || a === b) continue;
75
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
76
+ if (sampledPairs.has(pairKey)) continue;
77
+ sampledPairs.add(pairKey);
78
+ const score = bridgeScore(new Set(graph.neighbours(a)), new Set(graph.neighbours(b)), b, a);
79
+ candidates.push({
80
+ score,
81
+ a,
82
+ b
83
+ });
84
+ }
85
+ candidates.sort((x, y) => y.score - x.score);
86
+ const selected = [];
87
+ const selectedPairKeys = /* @__PURE__ */ new Set();
88
+ for (const { score, a, b } of candidates) {
89
+ if (selected.length >= config.nPairs) break;
90
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
91
+ if (selectedPairKeys.has(pairKey)) continue;
92
+ const aNbrs = new Set(graph.neighbours(a));
93
+ const bNbrs = new Set(graph.neighbours(b));
94
+ let isDiverse = true;
95
+ for (const prev of selected) if (jaccard$3(aNbrs, new Set(graph.neighbours(prev.source.id))) >= config.diversityThreshold) {
96
+ if (jaccard$3(bNbrs, new Set(graph.neighbours(prev.target.id))) >= config.diversityThreshold) {
97
+ isDiverse = false;
98
+ break;
99
+ }
100
+ }
101
+ if (!isDiverse) continue;
102
+ selectedPairKeys.add(pairKey);
103
+ selected.push({
104
+ source: { id: a },
105
+ target: { id: b },
106
+ bridgeScore: score
107
+ });
108
+ }
109
+ let fillAttempts = 0;
110
+ while (selected.length < config.nPairs && allNodes.length >= 2 && fillAttempts < config.nPairs * 20) {
111
+ fillAttempts++;
112
+ const i1 = Math.floor(rng() * allNodes.length);
113
+ const i2 = Math.floor(rng() * allNodes.length);
114
+ const a = allNodes[i1];
115
+ const b = allNodes[i2];
116
+ if (a === void 0 || b === void 0 || a === b) continue;
117
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
118
+ if (selectedPairKeys.has(pairKey)) continue;
119
+ selectedPairKeys.add(pairKey);
120
+ selected.push({
121
+ source: { id: a },
122
+ target: { id: b },
123
+ bridgeScore: 0
124
+ });
125
+ }
126
+ return { pairs: selected.slice(0, config.nPairs) };
127
+ }
128
+ //#endregion
129
+ //#region src/seeds/crisp.ts
130
+ /** Default configuration values */
131
+ var DEFAULTS$8 = {
132
+ nPairs: 100,
133
+ rngSeed: 42,
134
+ minDistance: 2,
135
+ maxDistance: 4,
136
+ minCommonNeighbours: 2,
137
+ diversityThreshold: .5,
138
+ sampleSize: 5e3
139
+ };
140
+ /**
141
+ * Simple seeded pseudo-random number generator using mulberry32.
142
+ */
143
+ function createRNG$5(seed) {
144
+ let state = seed >>> 0;
145
+ return () => {
146
+ state = state + 1831565813 >>> 0;
147
+ let t = Math.imul(state ^ state >>> 15, state | 1);
148
+ t = (t ^ t >>> 7) * (t | 1640531527);
149
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
150
+ };
151
+ }
152
+ /**
153
+ * Compute shortest-path distance between two nodes via BFS.
154
+ * Returns -1 if no path exists.
155
+ */
156
+ function bfsDistance(graph, source, target) {
157
+ if (source === target) return 0;
158
+ const visited = new Set([source]);
159
+ const queue = [{
160
+ node: source,
161
+ dist: 0
162
+ }];
163
+ while (queue.length > 0) {
164
+ const item = queue.shift();
165
+ if (!item) break;
166
+ const { node, dist } = item;
167
+ for (const neighbour of graph.neighbours(node)) {
168
+ if (neighbour === target) return dist + 1;
169
+ if (!visited.has(neighbour)) {
170
+ visited.add(neighbour);
171
+ queue.push({
172
+ node: neighbour,
173
+ dist: dist + 1
174
+ });
175
+ }
176
+ }
177
+ }
178
+ return -1;
179
+ }
180
+ /**
181
+ * Get the 1-hop neighbour set of a node.
182
+ */
183
+ function neighbourSet(graph, node) {
184
+ return new Set(graph.neighbours(node));
185
+ }
186
+ /**
187
+ * Count common neighbours between two nodes.
188
+ */
189
+ function commonNeighbours(graph, a, b) {
190
+ const na = neighbourSet(graph, a);
191
+ const nb = neighbourSet(graph, b);
192
+ let count = 0;
193
+ for (const n of na) if (nb.has(n)) count++;
194
+ return count;
195
+ }
196
+ /**
197
+ * Compute Jaccard similarity between two sets.
198
+ */
199
+ function jaccard$2(a, b) {
200
+ let intersection = 0;
201
+ for (const x of a) if (b.has(x)) intersection++;
202
+ const union = new Set([...a, ...b]).size;
203
+ return union === 0 ? 0 : intersection / union;
204
+ }
205
+ /**
206
+ * Compute distance score peaking at distance 3.
207
+ * Score ranges from 0 to 1, with maximum at dist=3.
208
+ */
209
+ function distanceScore(dist) {
210
+ return 1 - Math.abs(dist - 3) / 3;
211
+ }
212
+ /**
213
+ * CRISP — Connectivity-Rich Informed Seed Pairing.
214
+ *
215
+ * Samples random node pairs and scores them by common neighbour count
216
+ * and BFS distance (preferring 2–4 hops, peaking at 3). Greedy
217
+ * selection with Jaccard diversity ensures selected pairs are
218
+ * spread across different structural regions.
219
+ *
220
+ * @param graph - The graph to sample seeds from
221
+ * @param options - Configuration options
222
+ * @returns Selected seed pairs with connectivity metadata
223
+ */
224
+ function crisp(graph, options = {}) {
225
+ const config = {
226
+ ...DEFAULTS$8,
227
+ ...options
228
+ };
229
+ const rng = createRNG$5(config.rngSeed);
230
+ const allNodes = [...graph.nodeIds()];
231
+ if (allNodes.length < 2) return { pairs: [] };
232
+ if (allNodes.length < 4) {
233
+ const a = allNodes[0];
234
+ const b = allNodes[1];
235
+ if (a !== void 0 && b !== void 0) return { pairs: [{
236
+ source: { id: a },
237
+ target: { id: b },
238
+ distance: 1,
239
+ commonNeighbours: 0,
240
+ score: 0
241
+ }] };
242
+ return { pairs: [] };
243
+ }
244
+ const candidates = [];
245
+ const sampledPairs = /* @__PURE__ */ new Set();
246
+ const maxAttempts = config.sampleSize * 10;
247
+ let attempts = 0;
248
+ while (sampledPairs.size < config.sampleSize && attempts < maxAttempts) {
249
+ attempts++;
250
+ const a = allNodes[Math.floor(rng() * allNodes.length)];
251
+ const b = allNodes[Math.floor(rng() * allNodes.length)];
252
+ if (a === void 0 || b === void 0 || a === b) continue;
253
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
254
+ if (sampledPairs.has(pairKey)) continue;
255
+ sampledPairs.add(pairKey);
256
+ const dist = bfsDistance(graph, a, b);
257
+ if (dist < config.minDistance || dist > config.maxDistance) continue;
258
+ const cn = commonNeighbours(graph, a, b);
259
+ if (cn < config.minCommonNeighbours) continue;
260
+ const score = cn * (1 + distanceScore(dist));
261
+ candidates.push({
262
+ score,
263
+ distance: dist,
264
+ commonNeighbours: cn,
265
+ a,
266
+ b
267
+ });
268
+ }
269
+ if (candidates.length < config.nPairs) for (const pairKey of sampledPairs) {
270
+ const parts = pairKey.split("|");
271
+ if (parts.length !== 2) continue;
272
+ const a = parts[0];
273
+ const b = parts[1];
274
+ if (a === void 0 || b === void 0) continue;
275
+ const dist = bfsDistance(graph, a, b);
276
+ if (dist < 1 || dist > 6) continue;
277
+ const cn = commonNeighbours(graph, a, b);
278
+ const score = cn + .1;
279
+ candidates.push({
280
+ score,
281
+ distance: dist,
282
+ commonNeighbours: cn,
283
+ a,
284
+ b
285
+ });
286
+ }
287
+ candidates.sort((x, y) => y.score - x.score);
288
+ const selected = [];
289
+ const selectedPairKeys = /* @__PURE__ */ new Set();
290
+ for (const { score, distance, commonNeighbours, a, b } of candidates) {
291
+ if (selected.length >= config.nPairs) break;
292
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
293
+ if (selectedPairKeys.has(pairKey)) continue;
294
+ const aNbrs = neighbourSet(graph, a);
295
+ const bNbrs = neighbourSet(graph, b);
296
+ let isDiverse = true;
297
+ for (const prev of selected) {
298
+ const paNbrs = neighbourSet(graph, prev.source.id);
299
+ const pbNbrs = neighbourSet(graph, prev.target.id);
300
+ if (jaccard$2(aNbrs, paNbrs) >= config.diversityThreshold && jaccard$2(bNbrs, pbNbrs) >= config.diversityThreshold) {
301
+ isDiverse = false;
302
+ break;
303
+ }
304
+ }
305
+ if (!isDiverse) continue;
306
+ selectedPairKeys.add(pairKey);
307
+ selected.push({
308
+ source: { id: a },
309
+ target: { id: b },
310
+ distance,
311
+ commonNeighbours,
312
+ score
313
+ });
314
+ }
315
+ let fillAttempts = 0;
316
+ while (selected.length < config.nPairs && allNodes.length >= 2 && fillAttempts < config.nPairs * 20) {
317
+ fillAttempts++;
318
+ const i1 = Math.floor(rng() * allNodes.length);
319
+ const i2 = Math.floor(rng() * allNodes.length);
320
+ const a = allNodes[i1];
321
+ const b = allNodes[i2];
322
+ if (a === void 0 || b === void 0 || a === b) continue;
323
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
324
+ if (selectedPairKeys.has(pairKey)) continue;
325
+ selectedPairKeys.add(pairKey);
326
+ selected.push({
327
+ source: { id: a },
328
+ target: { id: b },
329
+ distance: 0,
330
+ commonNeighbours: 0,
331
+ score: 0
332
+ });
333
+ }
334
+ return { pairs: selected.slice(0, config.nPairs) };
335
+ }
336
+ //#endregion
337
+ //#region src/seeds/grasp.ts
338
+ /** Default configuration values */
339
+ var DEFAULTS$7 = {
340
+ nClusters: 100,
341
+ pairsPerCluster: 10,
342
+ withinClusterRatio: .5,
343
+ sampleSize: 2e5,
344
+ rngSeed: 42,
345
+ pagerankIterations: 10
346
+ };
347
+ /**
348
+ * Simple seeded pseudo-random number generator using mulberry32.
349
+ */
350
+ function createRNG$4(seed) {
351
+ let state = seed >>> 0;
352
+ return () => {
353
+ state = state + 1831565813 >>> 0;
354
+ let t = Math.imul(state ^ state >>> 15, state | 1);
355
+ t = (t ^ t >>> 7) * (t | 1640531527);
356
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
357
+ };
358
+ }
359
+ /**
360
+ * Reservoir sampling (Vitter's Algorithm R) for streaming node selection.
361
+ *
362
+ * Maintains a uniform sample of nodes as edges are streamed, without
363
+ * requiring the full graph in memory.
364
+ */
365
+ function reservoirSample(graph, sampleSize, rng) {
366
+ const reservoir = [];
367
+ const neighbourMap = /* @__PURE__ */ new Map();
368
+ const inReservoir = /* @__PURE__ */ new Set();
369
+ let nodesSeen = 0;
370
+ for (const edge of graph.edges()) {
371
+ const source = edge.source;
372
+ if (!inReservoir.has(source)) {
373
+ nodesSeen++;
374
+ if (reservoir.length < sampleSize) {
375
+ reservoir.push(source);
376
+ inReservoir.add(source);
377
+ neighbourMap.set(source, /* @__PURE__ */ new Set());
378
+ } else {
379
+ const j = Math.floor(rng() * nodesSeen);
380
+ if (j < sampleSize) {
381
+ const oldNode = reservoir[j];
382
+ if (oldNode !== void 0) {
383
+ inReservoir.delete(oldNode);
384
+ neighbourMap.delete(oldNode);
385
+ }
386
+ reservoir[j] = source;
387
+ inReservoir.add(source);
388
+ neighbourMap.set(source, /* @__PURE__ */ new Set());
389
+ }
390
+ }
391
+ }
392
+ const target = edge.target;
393
+ if (!inReservoir.has(target)) {
394
+ nodesSeen++;
395
+ if (reservoir.length < sampleSize) {
396
+ reservoir.push(target);
397
+ inReservoir.add(target);
398
+ neighbourMap.set(target, /* @__PURE__ */ new Set());
399
+ } else {
400
+ const j = Math.floor(rng() * nodesSeen);
401
+ if (j < sampleSize) {
402
+ const oldNode = reservoir[j];
403
+ if (oldNode !== void 0) {
404
+ inReservoir.delete(oldNode);
405
+ neighbourMap.delete(oldNode);
406
+ }
407
+ reservoir[j] = target;
408
+ inReservoir.add(target);
409
+ neighbourMap.set(target, /* @__PURE__ */ new Set());
410
+ }
411
+ }
412
+ }
413
+ if (inReservoir.has(source) && inReservoir.has(target)) {
414
+ const sourceNeighbours = neighbourMap.get(source);
415
+ const targetNeighbours = neighbourMap.get(target);
416
+ sourceNeighbours?.add(target);
417
+ targetNeighbours?.add(source);
418
+ }
419
+ }
420
+ return {
421
+ nodeIds: inReservoir,
422
+ neighbourMap
423
+ };
424
+ }
425
+ /**
426
+ * Compute approximate PageRank scores using power iteration on the reservoir subgraph.
427
+ *
428
+ * This is an approximation since it only considers the sampled nodes and their
429
+ * connections within the reservoir, not the full graph.
430
+ */
431
+ function approximatePageRank(nodeIds, neighbourMap, iterations, dampingFactor = .85) {
432
+ const n = nodeIds.size;
433
+ if (n === 0) return /* @__PURE__ */ new Map();
434
+ const nodeIdList = [...nodeIds];
435
+ const nodeIndex = new Map(nodeIdList.map((id, i) => [id, i]));
436
+ const scores = new Float64Array(n).fill(1 / n);
437
+ const newScores = new Float64Array(n);
438
+ for (let iter = 0; iter < iterations; iter++) {
439
+ newScores.fill((1 - dampingFactor) / n);
440
+ for (let i = 0; i < n; i++) {
441
+ const nodeId = nodeIdList[i];
442
+ if (nodeId === void 0) continue;
443
+ const neighbours = neighbourMap.get(nodeId);
444
+ if (neighbours === void 0) continue;
445
+ const outDegree = neighbours.size;
446
+ if (outDegree === 0) continue;
447
+ const contribution = dampingFactor * (scores[i] ?? 0) / outDegree;
448
+ for (const neighbour of neighbours) {
449
+ const neighbourIdx = nodeIndex.get(neighbour);
450
+ if (neighbourIdx !== void 0) newScores[neighbourIdx] = (newScores[neighbourIdx] ?? 0) + contribution;
451
+ }
452
+ }
453
+ for (let i = 0; i < n; i++) scores[i] = newScores[i] ?? 0;
454
+ }
455
+ const result = /* @__PURE__ */ new Map();
456
+ for (let i = 0; i < n; i++) {
457
+ const nodeId = nodeIdList[i];
458
+ const score = scores[i];
459
+ if (nodeId !== void 0 && score !== void 0) result.set(nodeId, score);
460
+ }
461
+ return result;
462
+ }
463
+ /**
464
+ * Compute structural features for sampled nodes.
465
+ *
466
+ * Features:
467
+ * - f1: log(deg(v) + 1) — scale-normalised connectivity
468
+ * - f2: clustering_coefficient(v) — local density
469
+ * - f3: approx_pagerank(v) — positional importance
470
+ */
471
+ function computeFeatures(graph, nodeIds, neighbourMap, pagerankScores) {
472
+ const features = [];
473
+ for (const nodeId of nodeIds) {
474
+ const degree = graph.degree(nodeId, "both");
475
+ const neighbours = neighbourMap.get(nodeId);
476
+ let clusteringCoef = 0;
477
+ if (neighbours !== void 0 && neighbours.size >= 2) {
478
+ let triangleCount = 0;
479
+ const neighbourList = [...neighbours];
480
+ for (let i = 0; i < neighbourList.length; i++) for (let j = i + 1; j < neighbourList.length; j++) {
481
+ const u = neighbourList[i];
482
+ const w = neighbourList[j];
483
+ if (u !== void 0 && w !== void 0) {
484
+ if (neighbourMap.get(u)?.has(w) === true) triangleCount++;
485
+ }
486
+ }
487
+ const possibleTriangles = degree * (degree - 1) / 2;
488
+ clusteringCoef = triangleCount / possibleTriangles;
489
+ }
490
+ const pagerank = pagerankScores.get(nodeId) ?? 0;
491
+ features.push({
492
+ nodeId,
493
+ f1: Math.log(degree + 1),
494
+ f2: clusteringCoef,
495
+ f3: pagerank
496
+ });
497
+ }
498
+ return features;
499
+ }
500
+ /**
501
+ * Sample seed pairs from clusters.
502
+ *
503
+ * For each cluster, samples a mix of within-cluster and cross-cluster pairs.
504
+ */
505
+ function samplePairs(features, clusterAssignments, nClusters, pairsPerCluster, withinClusterRatio, rng) {
506
+ const pairs = [];
507
+ const clusterNodes = /* @__PURE__ */ new Map();
508
+ for (const feature of features) {
509
+ const cluster = clusterAssignments.get(feature.nodeId);
510
+ if (cluster === void 0) continue;
511
+ let nodes = clusterNodes.get(cluster);
512
+ if (nodes === void 0) {
513
+ nodes = [];
514
+ clusterNodes.set(cluster, nodes);
515
+ }
516
+ nodes.push(feature);
517
+ }
518
+ const withinCount = Math.floor(pairsPerCluster * withinClusterRatio);
519
+ const crossCount = pairsPerCluster - withinCount;
520
+ for (let clusterIdx = 0; clusterIdx < nClusters; clusterIdx++) {
521
+ const nodes = clusterNodes.get(clusterIdx);
522
+ if (nodes === void 0 || nodes.length < 2) continue;
523
+ for (let i = 0; i < withinCount; i++) {
524
+ const idx1 = Math.floor(rng() * nodes.length);
525
+ let idx2 = Math.floor(rng() * nodes.length);
526
+ while (idx1 === idx2) idx2 = Math.floor(rng() * nodes.length);
527
+ const source = nodes[idx1];
528
+ const target = nodes[idx2];
529
+ if (source === void 0 || target === void 0) continue;
530
+ const distance = computeFeatureDistance(source, target);
531
+ pairs.push({
532
+ source: { id: source.nodeId },
533
+ target: { id: target.nodeId },
534
+ featureDistance: distance,
535
+ sameCluster: true,
536
+ sourceCluster: clusterIdx,
537
+ targetCluster: clusterIdx
538
+ });
539
+ }
540
+ for (let i = 0; i < crossCount; i++) {
541
+ const source = nodes[Math.floor(rng() * nodes.length)];
542
+ if (source === void 0) continue;
543
+ const otherClusterIdx = Math.floor(rng() * nClusters);
544
+ if (otherClusterIdx === clusterIdx) continue;
545
+ const otherNodes = clusterNodes.get(otherClusterIdx);
546
+ if (otherNodes === void 0 || otherNodes.length === 0) continue;
547
+ const target = otherNodes[Math.floor(rng() * otherNodes.length)];
548
+ if (target === void 0) continue;
549
+ const distance = computeFeatureDistance(source, target);
550
+ pairs.push({
551
+ source: { id: source.nodeId },
552
+ target: { id: target.nodeId },
553
+ featureDistance: distance,
554
+ sameCluster: false,
555
+ sourceCluster: clusterIdx,
556
+ targetCluster: otherClusterIdx
557
+ });
558
+ }
559
+ }
560
+ return pairs;
561
+ }
562
+ /**
563
+ * Compute Euclidean distance between two feature vectors.
564
+ */
565
+ function computeFeatureDistance(a, b) {
566
+ const d1 = a.f1 - b.f1;
567
+ const d2 = a.f2 - b.f2;
568
+ const d3 = a.f3 - b.f3;
569
+ return Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);
570
+ }
571
+ /**
572
+ * GRASP — Graph-agnostic Representative Seed pAir Sampling.
573
+ *
574
+ * Selects structurally representative seed pairs without domain knowledge.
575
+ * The algorithm streams edges, samples nodes via reservoir sampling, computes
576
+ * structural features, clusters nodes, and samples pairs within/across clusters.
577
+ *
578
+ * @param graph - The graph to sample seeds from
579
+ * @param options - Configuration options
580
+ * @returns Sampled seed pairs with structural metadata
581
+ *
582
+ * @example
583
+ * ```typescript
584
+ * const graph = new AdjacencyMapGraph();
585
+ * // ... populate graph ...
586
+ *
587
+ * const result = grasp(graph, {
588
+ * nClusters: 50,
589
+ * pairsPerCluster: 20,
590
+ * sampleSize: 100000,
591
+ * });
592
+ *
593
+ * console.log(`Sampled ${result.pairs.length} pairs from ${result.sampledNodeCount} nodes`);
594
+ * ```
595
+ */
596
+ function grasp(graph, options = {}) {
597
+ const config = {
598
+ ...DEFAULTS$7,
599
+ ...options
600
+ };
601
+ const rng = createRNG$4(config.rngSeed);
602
+ const { nodeIds, neighbourMap } = reservoirSample(graph, config.sampleSize, rng);
603
+ let features = computeFeatures(graph, nodeIds, neighbourMap, approximatePageRank(nodeIds, neighbourMap, config.pagerankIterations));
604
+ if (features.length > 0) features = require_kmeans.normaliseFeatures(features);
605
+ const k = Math.min(config.nClusters, features.length);
606
+ const kmeansResult = require_kmeans.miniBatchKMeans(features, {
607
+ k,
608
+ seed: config.rngSeed,
609
+ maxIterations: 100
610
+ });
611
+ return {
612
+ pairs: samplePairs(features, kmeansResult.assignments, kmeansResult.k, config.pairsPerCluster, config.withinClusterRatio, rng),
613
+ nClusters: kmeansResult.k,
614
+ sampledNodeCount: nodeIds.size,
615
+ features,
616
+ clusterAssignments: kmeansResult.assignments
617
+ };
618
+ }
619
+ //#endregion
620
+ //#region src/seeds/hybrid-core.ts
621
+ function createRNG$3(seed) {
622
+ let state = seed >>> 0;
623
+ return () => {
624
+ state = state + 1831565813 >>> 0;
625
+ let t = Math.imul(state ^ state >>> 15, state | 1);
626
+ t = (t ^ t >>> 7) * (t | 1640531527);
627
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
628
+ };
629
+ }
630
+ function canonicalPair(a, b) {
631
+ return a < b ? [a, b] : [b, a];
632
+ }
633
+ function pairKey(a, b) {
634
+ const [x, y] = canonicalPair(a, b);
635
+ return `${x}|${y}`;
636
+ }
637
+ function bfsDistances(graph, source) {
638
+ const dist = new Map([[source, 0]]);
639
+ const queue = [source];
640
+ while (queue.length > 0) {
641
+ const node = queue.shift();
642
+ if (node === void 0) continue;
643
+ const baseDist = dist.get(node) ?? 0;
644
+ for (const neighbour of graph.neighbours(node)) if (!dist.has(neighbour)) {
645
+ dist.set(neighbour, baseDist + 1);
646
+ queue.push(neighbour);
647
+ }
648
+ }
649
+ return dist;
650
+ }
651
+ function degreeDiversePairs(graph, nPairs, rngSeed) {
652
+ const rng = createRNG$3(rngSeed);
653
+ const allNodes = [...graph.nodeIds()];
654
+ if (allNodes.length < 2 || nPairs <= 0) return [];
655
+ const byDegree = allNodes.map((id) => ({
656
+ id,
657
+ d: graph.degree(id)
658
+ })).sort((a, b) => a.d - b.d);
659
+ const n = byDegree.length;
660
+ const low = byDegree.slice(0, Math.max(1, Math.floor(n / 3))).map((x) => x.id);
661
+ const high = byDegree.slice(Math.max(0, Math.floor(2 * n / 3))).map((x) => x.id);
662
+ const mid = byDegree.slice(Math.max(1, Math.floor(n / 3)), Math.max(1, Math.floor(2 * n / 3))).map((x) => x.id);
663
+ const pairs = [];
664
+ const seen = /* @__PURE__ */ new Set();
665
+ const addRandomPair = (aPool, bPool) => {
666
+ if (aPool.length === 0) return;
667
+ const right = bPool ?? aPool;
668
+ if (right.length === 0) return;
669
+ for (let attempts = 0; attempts < 40; attempts++) {
670
+ const a = aPool[Math.floor(rng() * aPool.length)];
671
+ const b = right[Math.floor(rng() * right.length)];
672
+ if (a === void 0 || b === void 0 || a === b) continue;
673
+ const key = pairKey(a, b);
674
+ if (seen.has(key)) continue;
675
+ seen.add(key);
676
+ const [x, y] = canonicalPair(a, b);
677
+ pairs.push({
678
+ source: { id: x },
679
+ target: { id: y }
680
+ });
681
+ return;
682
+ }
683
+ };
684
+ const lowHighCount = Math.floor(nPairs * .5);
685
+ const lowLowCount = Math.floor(nPairs * .25);
686
+ const highHighCount = nPairs - lowHighCount - lowLowCount;
687
+ for (let i = 0; i < lowHighCount; i++) addRandomPair(low, high);
688
+ for (let i = 0; i < lowLowCount; i++) addRandomPair(low);
689
+ for (let i = 0; i < highHighCount; i++) addRandomPair(high);
690
+ while (pairs.length < nPairs) {
691
+ if (mid.length > 0) addRandomPair(mid);
692
+ else addRandomPair(allNodes);
693
+ if (pairs.length >= Math.min(nPairs, allNodes.length * (allNodes.length - 1) / 2)) break;
694
+ }
695
+ return pairs.slice(0, nPairs);
696
+ }
697
+ function maxDistancePairs(graph, nPairs, rngSeed) {
698
+ const rng = createRNG$3(rngSeed);
699
+ const nodes = [...graph.nodeIds()];
700
+ if (nodes.length < 2 || nPairs <= 0) return [];
701
+ const sourceLimit = Math.min(nodes.length, 64);
702
+ const shuffled = [...nodes];
703
+ for (let i = shuffled.length - 1; i > 0; i--) {
704
+ const j = Math.floor(rng() * (i + 1));
705
+ const a = shuffled[i];
706
+ const b = shuffled[j];
707
+ if (a !== void 0 && b !== void 0) {
708
+ shuffled[i] = b;
709
+ shuffled[j] = a;
710
+ }
711
+ }
712
+ const ranked = [];
713
+ const seen = /* @__PURE__ */ new Set();
714
+ for (const source of shuffled.slice(0, sourceLimit)) {
715
+ const dist = bfsDistances(graph, source);
716
+ for (const [target, d] of dist) {
717
+ if (source === target || d <= 0) continue;
718
+ const key = pairKey(source, target);
719
+ if (seen.has(key)) continue;
720
+ seen.add(key);
721
+ const [a, b] = canonicalPair(source, target);
722
+ ranked.push({
723
+ a,
724
+ b,
725
+ d
726
+ });
727
+ }
728
+ }
729
+ ranked.sort((x, y) => y.d - x.d);
730
+ return ranked.slice(0, nPairs).map((x) => ({
731
+ source: { id: x.a },
732
+ target: { id: x.b }
733
+ }));
734
+ }
735
+ function labelPropagation(graph, rngSeed, maxIterations = 30) {
736
+ const rng = createRNG$3(rngSeed);
737
+ const nodes = [...graph.nodeIds()];
738
+ const labels = new Map(nodes.map((id, i) => [id, i]));
739
+ const order = [...nodes];
740
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
741
+ let changed = false;
742
+ for (let i = order.length - 1; i > 0; i--) {
743
+ const j = Math.floor(rng() * (i + 1));
744
+ const a = order[i];
745
+ const b = order[j];
746
+ if (a !== void 0 && b !== void 0) {
747
+ order[i] = b;
748
+ order[j] = a;
749
+ }
750
+ }
751
+ for (const node of order) {
752
+ const counts = /* @__PURE__ */ new Map();
753
+ for (const n of graph.neighbours(node)) {
754
+ const label = labels.get(n);
755
+ if (label === void 0) continue;
756
+ counts.set(label, (counts.get(label) ?? 0) + 1);
757
+ }
758
+ if (counts.size === 0) continue;
759
+ let bestLabel = labels.get(node) ?? 0;
760
+ let bestCount = -1;
761
+ for (const [label, count] of counts) if (count > bestCount || count === bestCount && rng() < .5) {
762
+ bestCount = count;
763
+ bestLabel = label;
764
+ }
765
+ if ((labels.get(node) ?? -1) !== bestLabel) {
766
+ labels.set(node, bestLabel);
767
+ changed = true;
768
+ }
769
+ }
770
+ if (!changed) break;
771
+ }
772
+ return labels;
773
+ }
774
+ function communityBridgePairs(graph, nPairs, rngSeed) {
775
+ const labels = labelPropagation(graph, rngSeed);
776
+ const nodes = [...graph.nodeIds()];
777
+ if (nodes.length < 2 || nPairs <= 0) return [];
778
+ const size = /* @__PURE__ */ new Map();
779
+ for (const label of labels.values()) size.set(label, (size.get(label) ?? 0) + 1);
780
+ const ranked = [];
781
+ for (let i = 0; i < nodes.length; i++) {
782
+ const a = nodes[i];
783
+ if (a === void 0) continue;
784
+ for (let j = i + 1; j < nodes.length; j++) {
785
+ const b = nodes[j];
786
+ if (b === void 0) continue;
787
+ const la = labels.get(a);
788
+ const lb = labels.get(b);
789
+ if (la === void 0 || lb === void 0 || la === lb) continue;
790
+ const score = (size.get(la) ?? 1) * (size.get(lb) ?? 1);
791
+ ranked.push({
792
+ a,
793
+ b,
794
+ score
795
+ });
796
+ }
797
+ }
798
+ ranked.sort((x, y) => y.score - x.score);
799
+ return ranked.slice(0, nPairs).map((x) => ({
800
+ source: { id: x.a },
801
+ target: { id: x.b }
802
+ }));
803
+ }
804
+ function runEnsemble(graph, components, options) {
805
+ const nPairs = Math.max(0, options.nPairs);
806
+ if (nPairs === 0) return [];
807
+ const rng = createRNG$3(options.rngSeed);
808
+ const oversampleMultiplier = options.oversampleMultiplier ?? 3;
809
+ const blindPriorityBias = options.blindPriorityBias ?? 0;
810
+ const nodeReusePenalty = options.nodeReusePenalty ?? .15;
811
+ const consensusBonus = options.consensusBonus ?? .05;
812
+ const oversample = Math.max(nPairs * oversampleMultiplier, 30);
813
+ const pairMap = /* @__PURE__ */ new Map();
814
+ components.forEach((component, index) => {
815
+ let pairs;
816
+ try {
817
+ pairs = component.select(graph, oversample, options.rngSeed + (index + 1) * 97);
818
+ } catch {
819
+ pairs = [];
820
+ }
821
+ const m = pairs.length;
822
+ if (m === 0) return;
823
+ for (let rank = 0; rank < m; rank++) {
824
+ const pair = pairs[rank];
825
+ if (pair === void 0) continue;
826
+ const a = pair.source.id;
827
+ const b = pair.target.id;
828
+ if (a === b) continue;
829
+ const [x, y] = canonicalPair(a, b);
830
+ const key = `${x}|${y}`;
831
+ const rankWeight = m <= 1 ? 1 : 1 - rank / (m - 1);
832
+ const componentBonus = component.isBlind ? 1 + blindPriorityBias : 1;
833
+ const score = component.weight * componentBonus * (.5 + .5 * rankWeight);
834
+ const existing = pairMap.get(key);
835
+ if (existing === void 0) pairMap.set(key, {
836
+ a: x,
837
+ b: y,
838
+ score,
839
+ support: 1,
840
+ blindVotes: component.isBlind ? 1 : 0,
841
+ components: new Set([component.id])
842
+ });
843
+ else {
844
+ existing.score += score;
845
+ existing.support += 1;
846
+ existing.blindVotes += component.isBlind ? 1 : 0;
847
+ existing.components.add(component.id);
848
+ }
849
+ }
850
+ });
851
+ for (const meta of pairMap.values()) meta.score *= 1 + consensusBonus * Math.max(0, meta.support - 1);
852
+ const selected = [];
853
+ const nodeUsage = /* @__PURE__ */ new Map();
854
+ const candidates = [...pairMap.values()];
855
+ while (candidates.length > 0 && selected.length < nPairs) {
856
+ let bestIndex = -1;
857
+ let bestValue = Number.NEGATIVE_INFINITY;
858
+ for (let i = 0; i < candidates.length; i++) {
859
+ const candidate = candidates[i];
860
+ if (candidate === void 0) continue;
861
+ const usage = (nodeUsage.get(candidate.a) ?? 0) + (nodeUsage.get(candidate.b) ?? 0);
862
+ const value = candidate.score - nodeReusePenalty * usage;
863
+ if (value > bestValue) {
864
+ bestValue = value;
865
+ bestIndex = i;
866
+ }
867
+ }
868
+ if (bestIndex < 0) break;
869
+ const [best] = candidates.splice(bestIndex, 1);
870
+ if (best === void 0) break;
871
+ nodeUsage.set(best.a, (nodeUsage.get(best.a) ?? 0) + 1);
872
+ nodeUsage.set(best.b, (nodeUsage.get(best.b) ?? 0) + 1);
873
+ selected.push({
874
+ source: { id: best.a },
875
+ target: { id: best.b },
876
+ score: best.score,
877
+ support: best.support,
878
+ blindVotes: best.blindVotes,
879
+ components: [...best.components].sort()
880
+ });
881
+ }
882
+ if (selected.length < nPairs) {
883
+ const allNodes = [...graph.nodeIds()];
884
+ const seen = new Set(selected.map((p) => pairKey(p.source.id, p.target.id)));
885
+ const maxAttempts = nPairs * 40;
886
+ let attempts = 0;
887
+ while (selected.length < nPairs && allNodes.length >= 2 && attempts < maxAttempts) {
888
+ attempts++;
889
+ const a = allNodes[Math.floor(rng() * allNodes.length)];
890
+ const b = allNodes[Math.floor(rng() * allNodes.length)];
891
+ if (a === void 0 || b === void 0 || a === b) continue;
892
+ const [x, y] = canonicalPair(a, b);
893
+ const key = `${x}|${y}`;
894
+ if (seen.has(key)) continue;
895
+ seen.add(key);
896
+ selected.push({
897
+ source: { id: x },
898
+ target: { id: y },
899
+ score: 0,
900
+ support: 0,
901
+ blindVotes: 0,
902
+ components: []
903
+ });
904
+ }
905
+ }
906
+ return selected.slice(0, nPairs);
907
+ }
908
+ //#endregion
909
+ //#region src/seeds/spine.ts
910
+ /** Default configuration values */
911
+ var DEFAULTS$6 = {
912
+ nPairs: 100,
913
+ rngSeed: 42,
914
+ diversityThreshold: .5
915
+ };
916
+ /**
917
+ * Simple seeded pseudo-random number generator using mulberry32.
918
+ */
919
+ function createRNG$2(seed) {
920
+ let state = seed >>> 0;
921
+ return () => {
922
+ state = state + 1831565813 >>> 0;
923
+ let t = Math.imul(state ^ state >>> 15, state | 1);
924
+ t = (t ^ t >>> 7) * (t | 1640531527);
925
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
926
+ };
927
+ }
928
+ /**
929
+ * Compute skewness of the 2-hop degree distribution for a node.
930
+ *
931
+ * For each neighbour u of node, collect the degree of u.
932
+ * Skewness = E[(X - μ)³] / σ³ where X is the degree distribution.
933
+ *
934
+ * High skewness = neighbour degrees are heavily concentrated at one end.
935
+ * Low skewness = neighbour degrees are relatively uniform.
936
+ *
937
+ * Returns 0.0 for nodes with fewer than 3 neighbours.
938
+ */
939
+ function degreeSkewness(graph, node) {
940
+ const neighbours = [...graph.neighbours(node)];
941
+ if (neighbours.length < 3) return 0;
942
+ const degrees = neighbours.map((n) => graph.degree(n, "both"));
943
+ const n = degrees.length;
944
+ const mean = degrees.reduce((s, d) => s + d, 0) / n;
945
+ const variance = degrees.reduce((s, d) => s + (d - mean) ** 2, 0) / n;
946
+ if (variance < 1e-10) return 0;
947
+ const std = Math.sqrt(variance);
948
+ return degrees.reduce((s, d) => s + (d - mean) ** 3, 0) / n / std ** 3;
949
+ }
950
+ /**
951
+ * Compute Jaccard similarity between two sets.
952
+ */
953
+ function jaccard$1(a, b) {
954
+ const intersection = [...a].filter((x) => b.has(x)).length;
955
+ const union = new Set([...a, ...b]).size;
956
+ return union === 0 ? 0 : intersection / union;
957
+ }
958
+ /**
959
+ * SPINE — Structural Position-Informed Node Extraction.
960
+ *
961
+ * Computes 2-hop degree distribution skewness for each node. Nodes with
962
+ * high positive skewness have structurally diverse neighbours (hub-periphery
963
+ * mix), while nodes with low skewness have uniform neighbours. Pairs
964
+ * connecting high-skewness and low-skewness nodes explore structurally
965
+ * varied terrain.
966
+ *
967
+ * @param graph - The graph to sample seeds from
968
+ * @param options - Configuration options
969
+ * @returns Selected seed pairs with skewness metadata
970
+ */
971
+ function spine(graph, options = {}) {
972
+ const config = {
973
+ ...DEFAULTS$6,
974
+ ...options
975
+ };
976
+ const rng = createRNG$2(config.rngSeed);
977
+ const allNodes = [...graph.nodeIds()];
978
+ if (allNodes.length < 2) return {
979
+ pairs: [],
980
+ skewness: /* @__PURE__ */ new Map()
981
+ };
982
+ const skewnessMap = /* @__PURE__ */ new Map();
983
+ for (const node of allNodes) skewnessMap.set(node, degreeSkewness(graph, node));
984
+ const sortedNodes = [...allNodes].sort((a, b) => (skewnessMap.get(a) ?? 0) - (skewnessMap.get(b) ?? 0));
985
+ const n = sortedNodes.length;
986
+ const lowSkew = sortedNodes.slice(0, Math.floor(n / 3));
987
+ const midSkew = sortedNodes.slice(Math.floor(n / 3), Math.floor(2 * n / 3));
988
+ const highSkew = sortedNodes.slice(Math.floor(2 * n / 3));
989
+ const candidates = [];
990
+ const sampleSize = Math.max(config.nPairs * 20, 200);
991
+ for (let i = 0; i < sampleSize; i++) {
992
+ if (!highSkew.length || !lowSkew.length) break;
993
+ const a = highSkew[Math.floor(rng() * highSkew.length)];
994
+ const b = lowSkew[Math.floor(rng() * lowSkew.length)];
995
+ if (a === void 0 || b === void 0 || a === b) continue;
996
+ const skA = skewnessMap.get(a) ?? 0;
997
+ const skB = skewnessMap.get(b) ?? 0;
998
+ candidates.push({
999
+ score: Math.abs(skA - skB),
1000
+ a,
1001
+ b
1002
+ });
1003
+ }
1004
+ const halfSample = Math.floor(sampleSize / 2);
1005
+ for (let i = 0; i < halfSample; i++) {
1006
+ if (midSkew.length && highSkew.length) {
1007
+ const a = highSkew[Math.floor(rng() * highSkew.length)];
1008
+ const b = midSkew[Math.floor(rng() * midSkew.length)];
1009
+ if (a !== void 0 && b !== void 0 && a !== b) {
1010
+ const skA = skewnessMap.get(a) ?? 0;
1011
+ const skB = skewnessMap.get(b) ?? 0;
1012
+ candidates.push({
1013
+ score: Math.abs(skA - skB) * .8,
1014
+ a,
1015
+ b
1016
+ });
1017
+ }
1018
+ }
1019
+ if (midSkew.length && lowSkew.length) {
1020
+ const a = midSkew[Math.floor(rng() * midSkew.length)];
1021
+ const b = lowSkew[Math.floor(rng() * lowSkew.length)];
1022
+ if (a !== void 0 && b !== void 0 && a !== b) {
1023
+ const skA = skewnessMap.get(a) ?? 0;
1024
+ const skB = skewnessMap.get(b) ?? 0;
1025
+ candidates.push({
1026
+ score: Math.abs(skA - skB) * .8,
1027
+ a,
1028
+ b
1029
+ });
1030
+ }
1031
+ }
1032
+ }
1033
+ const quarterSample = Math.floor(sampleSize / 4);
1034
+ for (let i = 0; i < quarterSample; i++) if (highSkew.length >= 2) {
1035
+ const i1 = Math.floor(rng() * highSkew.length);
1036
+ let i2 = Math.floor(rng() * highSkew.length);
1037
+ while (i1 === i2) i2 = Math.floor(rng() * highSkew.length);
1038
+ const a = highSkew[i1];
1039
+ const b = highSkew[i2];
1040
+ if (a !== void 0 && b !== void 0) {
1041
+ const skA = skewnessMap.get(a) ?? 0;
1042
+ const skB = skewnessMap.get(b) ?? 0;
1043
+ candidates.push({
1044
+ score: Math.abs(skA - skB) * .5,
1045
+ a,
1046
+ b
1047
+ });
1048
+ }
1049
+ }
1050
+ candidates.sort((x, y) => y.score - x.score);
1051
+ const selected = [];
1052
+ const selectedPairKeys = /* @__PURE__ */ new Set();
1053
+ for (const { a, b } of candidates) {
1054
+ if (selected.length >= config.nPairs) break;
1055
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
1056
+ if (selectedPairKeys.has(pairKey)) continue;
1057
+ const aNbrs = new Set(graph.neighbours(a));
1058
+ const bNbrs = new Set(graph.neighbours(b));
1059
+ let isDiverse = true;
1060
+ for (const prev of selected) if (jaccard$1(aNbrs, new Set(graph.neighbours(prev.source.id))) >= config.diversityThreshold) {
1061
+ if (jaccard$1(bNbrs, new Set(graph.neighbours(prev.target.id))) >= config.diversityThreshold) {
1062
+ isDiverse = false;
1063
+ break;
1064
+ }
1065
+ }
1066
+ if (!isDiverse) continue;
1067
+ selectedPairKeys.add(pairKey);
1068
+ selected.push({
1069
+ source: { id: a },
1070
+ target: { id: b },
1071
+ sourceSkewness: skewnessMap.get(a) ?? 0,
1072
+ targetSkewness: skewnessMap.get(b) ?? 0
1073
+ });
1074
+ }
1075
+ let fillAttempts = 0;
1076
+ while (selected.length < config.nPairs && allNodes.length >= 2 && fillAttempts < config.nPairs * 20) {
1077
+ fillAttempts++;
1078
+ const i1 = Math.floor(rng() * allNodes.length);
1079
+ const i2 = Math.floor(rng() * allNodes.length);
1080
+ const a = allNodes[i1];
1081
+ const b = allNodes[i2];
1082
+ if (a === void 0 || b === void 0 || a === b) continue;
1083
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
1084
+ if (selectedPairKeys.has(pairKey)) continue;
1085
+ selectedPairKeys.add(pairKey);
1086
+ selected.push({
1087
+ source: { id: a },
1088
+ target: { id: b },
1089
+ sourceSkewness: skewnessMap.get(a) ?? 0,
1090
+ targetSkewness: skewnessMap.get(b) ?? 0
1091
+ });
1092
+ }
1093
+ return {
1094
+ pairs: selected.slice(0, config.nPairs),
1095
+ skewness: skewnessMap
1096
+ };
1097
+ }
1098
+ //#endregion
1099
+ //#region src/seeds/stride.ts
1100
+ /** Default configuration values */
1101
+ var DEFAULTS$5 = {
1102
+ nPairs: 100,
1103
+ rngSeed: 42,
1104
+ diversityThreshold: .5
1105
+ };
1106
+ /**
1107
+ * Simple seeded pseudo-random number generator using mulberry32.
1108
+ */
1109
+ function createRNG$1(seed) {
1110
+ let state = seed >>> 0;
1111
+ return () => {
1112
+ state = state + 1831565813 >>> 0;
1113
+ let t = Math.imul(state ^ state >>> 15, state | 1);
1114
+ t = (t ^ t >>> 7) * (t | 1640531527);
1115
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1116
+ };
1117
+ }
1118
+ /**
1119
+ * Count closed triads (3-cycles) involving a node.
1120
+ *
1121
+ * A triad is a triple (node, u, v) where u and v are both neighbours
1122
+ * of node AND u-v is an edge.
1123
+ */
1124
+ function countTriads(graph, node) {
1125
+ const neighbours = [...graph.neighbours(node)];
1126
+ if (neighbours.length < 2) return 0;
1127
+ const neighbourSet = new Set(neighbours);
1128
+ let count = 0;
1129
+ for (let i = 0; i < neighbours.length; i++) for (let j = i + 1; j < neighbours.length; j++) {
1130
+ const u = neighbours[i];
1131
+ const v = neighbours[j];
1132
+ if (u !== void 0 && v !== void 0) {
1133
+ if (new Set(graph.neighbours(u)).has(v) && neighbourSet.has(v)) count++;
1134
+ }
1135
+ }
1136
+ return count;
1137
+ }
1138
+ /**
1139
+ * Classify a node by triad count into core/bridge/periphery.
1140
+ */
1141
+ function triadCategory(triadCount, p33, p66) {
1142
+ if (triadCount <= p33) return "periphery";
1143
+ if (triadCount <= p66) return "bridge";
1144
+ return "core";
1145
+ }
1146
+ /**
1147
+ * Compute Jaccard similarity between two sets.
1148
+ */
1149
+ function jaccard(a, b) {
1150
+ const intersection = [...a].filter((x) => b.has(x)).length;
1151
+ const union = new Set([...a, ...b]).size;
1152
+ return union === 0 ? 0 : intersection / union;
1153
+ }
1154
+ /**
1155
+ * STRIDE — Shortest-TRIangle Diversity seed selection.
1156
+ *
1157
+ * Nodes are categorised by local triad count into core, bridge, and
1158
+ * periphery. Pairs spanning different categories capture structural
1159
+ * diversity: core-periphery pairs traverse community interiors to
1160
+ * boundaries, bridge-bridge pairs span community gaps.
1161
+ *
1162
+ * @param graph - The graph to sample seeds from
1163
+ * @param options - Configuration options
1164
+ * @returns Selected seed pairs with triad metadata
1165
+ */
1166
+ function stride(graph, options = {}) {
1167
+ const config = {
1168
+ ...DEFAULTS$5,
1169
+ ...options
1170
+ };
1171
+ const rng = createRNG$1(config.rngSeed);
1172
+ const allNodes = [...graph.nodeIds()];
1173
+ if (allNodes.length < 2) return {
1174
+ pairs: [],
1175
+ triadCounts: /* @__PURE__ */ new Map(),
1176
+ categories: /* @__PURE__ */ new Map()
1177
+ };
1178
+ const triadCounts = /* @__PURE__ */ new Map();
1179
+ for (const node of allNodes) triadCounts.set(node, countTriads(graph, node));
1180
+ const countsSorted = [...triadCounts.values()].sort((a, b) => a - b);
1181
+ const n = countsSorted.length;
1182
+ const p33 = countsSorted[Math.floor(n / 3)] ?? 0;
1183
+ const p66 = countsSorted[Math.floor(2 * n / 3)] ?? 0;
1184
+ const categories = /* @__PURE__ */ new Map();
1185
+ const categoryGroups = /* @__PURE__ */ new Map();
1186
+ for (const [node, tc] of triadCounts) {
1187
+ const cat = triadCategory(tc, p33, p66);
1188
+ categories.set(node, cat);
1189
+ let group = categoryGroups.get(cat);
1190
+ if (group === void 0) {
1191
+ group = [];
1192
+ categoryGroups.set(cat, group);
1193
+ }
1194
+ group.push(node);
1195
+ }
1196
+ const catNames = [
1197
+ "core",
1198
+ "bridge",
1199
+ "periphery"
1200
+ ];
1201
+ const candidates = [];
1202
+ for (const catA of catNames) for (const catB of catNames) {
1203
+ const groupA = categoryGroups.get(catA);
1204
+ const groupB = categoryGroups.get(catB);
1205
+ if (groupA === void 0 || groupB === void 0) continue;
1206
+ const aLen = groupA.length;
1207
+ const bLen = groupB.length;
1208
+ if (aLen === 0 || bLen === 0) continue;
1209
+ const crossBonus = catA === catB ? 0 : 1;
1210
+ const sampleSize = Math.max(config.nPairs * 3, 30);
1211
+ for (let s = 0; s < sampleSize; s++) {
1212
+ const a = groupA[Math.floor(rng() * groupA.length)];
1213
+ const b = catA === catB ? groupA[Math.floor(rng() * groupA.length)] : groupB[Math.floor(rng() * groupB.length)];
1214
+ if (a === void 0 || b === void 0 || a === b) continue;
1215
+ const tcA = triadCounts.get(a) ?? 0;
1216
+ const tcB = triadCounts.get(b) ?? 0;
1217
+ const score = Math.abs(tcA - tcB) + crossBonus * Math.max(tcA, tcB, 1);
1218
+ candidates.push({
1219
+ score,
1220
+ a,
1221
+ b
1222
+ });
1223
+ }
1224
+ }
1225
+ candidates.sort((x, y) => y.score - x.score);
1226
+ const selected = [];
1227
+ const selectedPairKeys = /* @__PURE__ */ new Set();
1228
+ for (const candidate of candidates) {
1229
+ const { a, b } = candidate;
1230
+ if (selected.length >= config.nPairs) break;
1231
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
1232
+ if (selectedPairKeys.has(pairKey)) continue;
1233
+ const aNbrs = new Set(graph.neighbours(a));
1234
+ const bNbrs = new Set(graph.neighbours(b));
1235
+ let isDiverse = true;
1236
+ for (const prev of selected) if (jaccard(aNbrs, new Set(graph.neighbours(prev.source.id))) >= config.diversityThreshold) {
1237
+ if (jaccard(bNbrs, new Set(graph.neighbours(prev.target.id))) >= config.diversityThreshold) {
1238
+ isDiverse = false;
1239
+ break;
1240
+ }
1241
+ }
1242
+ if (!isDiverse) continue;
1243
+ selectedPairKeys.add(pairKey);
1244
+ selected.push({
1245
+ source: { id: a },
1246
+ target: { id: b },
1247
+ sourceTriads: triadCounts.get(a) ?? 0,
1248
+ targetTriads: triadCounts.get(b) ?? 0,
1249
+ sourceCategory: categories.get(a) ?? "periphery",
1250
+ targetCategory: categories.get(b) ?? "periphery"
1251
+ });
1252
+ }
1253
+ let fillAttempts = 0;
1254
+ while (selected.length < config.nPairs && allNodes.length >= 2 && fillAttempts < config.nPairs * 20) {
1255
+ fillAttempts++;
1256
+ const i1 = Math.floor(rng() * allNodes.length);
1257
+ const i2 = Math.floor(rng() * allNodes.length);
1258
+ const a = allNodes[i1];
1259
+ const b = allNodes[i2];
1260
+ if (a === void 0 || b === void 0 || a === b) continue;
1261
+ const pairKey = a < b ? `${a}|${b}` : `${b}|${a}`;
1262
+ if (selectedPairKeys.has(pairKey)) continue;
1263
+ selectedPairKeys.add(pairKey);
1264
+ selected.push({
1265
+ source: { id: a },
1266
+ target: { id: b },
1267
+ sourceTriads: triadCounts.get(a) ?? 0,
1268
+ targetTriads: triadCounts.get(b) ?? 0,
1269
+ sourceCategory: categories.get(a) ?? "periphery",
1270
+ targetCategory: categories.get(b) ?? "periphery"
1271
+ });
1272
+ }
1273
+ return {
1274
+ pairs: selected.slice(0, config.nPairs),
1275
+ triadCounts,
1276
+ categories
1277
+ };
1278
+ }
1279
+ //#endregion
1280
+ //#region src/seeds/basil.ts
1281
+ var DEFAULTS$4 = {
1282
+ nPairs: 100,
1283
+ rngSeed: 42,
1284
+ blindPriorityBias: .2
1285
+ };
1286
+ function basil(graph, options = {}) {
1287
+ const config = {
1288
+ ...DEFAULTS$4,
1289
+ ...options
1290
+ };
1291
+ return { pairs: runEnsemble(graph, [
1292
+ {
1293
+ id: "stride",
1294
+ weight: 1,
1295
+ isBlind: true,
1296
+ select: (g, nPairs, rngSeed) => stride(g, {
1297
+ nPairs,
1298
+ rngSeed
1299
+ }).pairs
1300
+ },
1301
+ {
1302
+ id: "crest",
1303
+ weight: 1,
1304
+ isBlind: true,
1305
+ select: (g, nPairs, rngSeed) => crest(g, {
1306
+ nPairs,
1307
+ rngSeed
1308
+ }).pairs
1309
+ },
1310
+ {
1311
+ id: "spine",
1312
+ weight: .95,
1313
+ isBlind: true,
1314
+ select: (g, nPairs, rngSeed) => spine(g, {
1315
+ nPairs,
1316
+ rngSeed
1317
+ }).pairs
1318
+ },
1319
+ {
1320
+ id: "grasp",
1321
+ weight: .85,
1322
+ isBlind: true,
1323
+ select: (g, nPairs, rngSeed) => {
1324
+ const k = Math.min(24, Math.max(8, Math.round(Math.sqrt(g.nodeCount))));
1325
+ return grasp(g, {
1326
+ rngSeed,
1327
+ nClusters: k,
1328
+ pairsPerCluster: Math.max(2, Math.ceil(nPairs / Math.max(1, k))),
1329
+ sampleSize: Math.min(2e4, Math.max(2e3, g.nodeCount * 20))
1330
+ }).pairs;
1331
+ }
1332
+ },
1333
+ {
1334
+ id: "degree_diverse",
1335
+ weight: .6,
1336
+ isBlind: true,
1337
+ select: degreeDiversePairs
1338
+ },
1339
+ {
1340
+ id: "crisp",
1341
+ weight: .25,
1342
+ isBlind: false,
1343
+ select: (g, nPairs, rngSeed) => crisp(g, {
1344
+ nPairs,
1345
+ rngSeed
1346
+ }).pairs
1347
+ }
1348
+ ], {
1349
+ nPairs: config.nPairs,
1350
+ rngSeed: config.rngSeed,
1351
+ blindPriorityBias: config.blindPriorityBias
1352
+ }) };
1353
+ }
1354
+ //#endregion
1355
+ //#region src/seeds/brisk.ts
1356
+ var DEFAULTS$3 = {
1357
+ nPairs: 100,
1358
+ rngSeed: 42,
1359
+ blindPriorityBias: .1,
1360
+ includeGrasp: true
1361
+ };
1362
+ function brisk(graph, options = {}) {
1363
+ const config = {
1364
+ ...DEFAULTS$3,
1365
+ ...options
1366
+ };
1367
+ const components = [
1368
+ {
1369
+ id: "stride",
1370
+ weight: 1,
1371
+ isBlind: true,
1372
+ select: (g, nPairs, rngSeed) => stride(g, {
1373
+ nPairs,
1374
+ rngSeed
1375
+ }).pairs
1376
+ },
1377
+ {
1378
+ id: "crest",
1379
+ weight: 1,
1380
+ isBlind: true,
1381
+ select: (g, nPairs, rngSeed) => crest(g, {
1382
+ nPairs,
1383
+ rngSeed
1384
+ }).pairs
1385
+ },
1386
+ {
1387
+ id: "spine",
1388
+ weight: .95,
1389
+ isBlind: true,
1390
+ select: (g, nPairs, rngSeed) => spine(g, {
1391
+ nPairs,
1392
+ rngSeed
1393
+ }).pairs
1394
+ },
1395
+ {
1396
+ id: "degree_diverse",
1397
+ weight: .6,
1398
+ isBlind: true,
1399
+ select: degreeDiversePairs
1400
+ }
1401
+ ];
1402
+ if (config.includeGrasp) components.push({
1403
+ id: "grasp",
1404
+ weight: .8,
1405
+ isBlind: true,
1406
+ select: (g, nPairs, rngSeed) => {
1407
+ const k = Math.min(24, Math.max(8, Math.round(Math.sqrt(g.nodeCount))));
1408
+ return grasp(g, {
1409
+ rngSeed,
1410
+ nClusters: k,
1411
+ pairsPerCluster: Math.max(2, Math.ceil(nPairs / Math.max(1, k))),
1412
+ sampleSize: Math.min(2e4, Math.max(2e3, g.nodeCount * 20))
1413
+ }).pairs;
1414
+ }
1415
+ });
1416
+ return { pairs: runEnsemble(graph, components, {
1417
+ nPairs: config.nPairs,
1418
+ rngSeed: config.rngSeed,
1419
+ blindPriorityBias: config.blindPriorityBias
1420
+ }) };
1421
+ }
1422
+ //#endregion
1423
+ //#region src/seeds/omnia.ts
1424
+ var DEFAULTS$2 = {
1425
+ nPairs: 100,
1426
+ rngSeed: 42,
1427
+ blindPriorityBias: .05
1428
+ };
1429
+ function omnia(graph, options = {}) {
1430
+ const config = {
1431
+ ...DEFAULTS$2,
1432
+ ...options
1433
+ };
1434
+ return { pairs: runEnsemble(graph, [
1435
+ {
1436
+ id: "stride",
1437
+ weight: 1,
1438
+ isBlind: true,
1439
+ select: (g, nPairs, rngSeed) => stride(g, {
1440
+ nPairs,
1441
+ rngSeed
1442
+ }).pairs
1443
+ },
1444
+ {
1445
+ id: "crest",
1446
+ weight: 1,
1447
+ isBlind: true,
1448
+ select: (g, nPairs, rngSeed) => crest(g, {
1449
+ nPairs,
1450
+ rngSeed
1451
+ }).pairs
1452
+ },
1453
+ {
1454
+ id: "spine",
1455
+ weight: .9,
1456
+ isBlind: true,
1457
+ select: (g, nPairs, rngSeed) => spine(g, {
1458
+ nPairs,
1459
+ rngSeed
1460
+ }).pairs
1461
+ },
1462
+ {
1463
+ id: "grasp",
1464
+ weight: .8,
1465
+ isBlind: true,
1466
+ select: (g, nPairs, rngSeed) => {
1467
+ const k = Math.min(24, Math.max(8, Math.round(Math.sqrt(g.nodeCount))));
1468
+ return grasp(g, {
1469
+ rngSeed,
1470
+ nClusters: k,
1471
+ pairsPerCluster: Math.max(2, Math.ceil(nPairs / Math.max(1, k))),
1472
+ sampleSize: Math.min(2e4, Math.max(2e3, g.nodeCount * 20))
1473
+ }).pairs;
1474
+ }
1475
+ },
1476
+ {
1477
+ id: "degree_diverse",
1478
+ weight: .7,
1479
+ isBlind: true,
1480
+ select: degreeDiversePairs
1481
+ },
1482
+ {
1483
+ id: "crisp",
1484
+ weight: .85,
1485
+ isBlind: false,
1486
+ select: (g, nPairs, rngSeed) => crisp(g, {
1487
+ nPairs,
1488
+ rngSeed
1489
+ }).pairs
1490
+ },
1491
+ {
1492
+ id: "max_distance",
1493
+ weight: .9,
1494
+ isBlind: false,
1495
+ select: maxDistancePairs
1496
+ },
1497
+ {
1498
+ id: "community_bridge",
1499
+ weight: .9,
1500
+ isBlind: false,
1501
+ select: communityBridgePairs
1502
+ }
1503
+ ], {
1504
+ nPairs: config.nPairs,
1505
+ rngSeed: config.rngSeed,
1506
+ blindPriorityBias: config.blindPriorityBias
1507
+ }) };
1508
+ }
1509
+ //#endregion
1510
+ //#region src/seeds/prism.ts
1511
+ var DEFAULTS$1 = {
1512
+ nPairs: 100,
1513
+ rngSeed: 42,
1514
+ blindPriorityBias: .1
1515
+ };
1516
+ function prism(graph, options = {}) {
1517
+ const config = {
1518
+ ...DEFAULTS$1,
1519
+ ...options
1520
+ };
1521
+ return { pairs: runEnsemble(graph, [
1522
+ {
1523
+ id: "stride",
1524
+ weight: 1,
1525
+ isBlind: true,
1526
+ select: (g, nPairs, rngSeed) => stride(g, {
1527
+ nPairs,
1528
+ rngSeed
1529
+ }).pairs
1530
+ },
1531
+ {
1532
+ id: "crest",
1533
+ weight: 1,
1534
+ isBlind: true,
1535
+ select: (g, nPairs, rngSeed) => crest(g, {
1536
+ nPairs,
1537
+ rngSeed
1538
+ }).pairs
1539
+ },
1540
+ {
1541
+ id: "spine",
1542
+ weight: .9,
1543
+ isBlind: true,
1544
+ select: (g, nPairs, rngSeed) => spine(g, {
1545
+ nPairs,
1546
+ rngSeed
1547
+ }).pairs
1548
+ },
1549
+ {
1550
+ id: "grasp",
1551
+ weight: .8,
1552
+ isBlind: true,
1553
+ select: (g, nPairs, rngSeed) => {
1554
+ const k = Math.min(24, Math.max(8, Math.round(Math.sqrt(g.nodeCount))));
1555
+ return grasp(g, {
1556
+ rngSeed,
1557
+ nClusters: k,
1558
+ pairsPerCluster: Math.max(2, Math.ceil(nPairs / Math.max(1, k))),
1559
+ sampleSize: Math.min(2e4, Math.max(2e3, g.nodeCount * 20))
1560
+ }).pairs;
1561
+ }
1562
+ },
1563
+ {
1564
+ id: "degree_diverse",
1565
+ weight: .7,
1566
+ isBlind: true,
1567
+ select: degreeDiversePairs
1568
+ },
1569
+ {
1570
+ id: "max_distance_proxy",
1571
+ weight: .7,
1572
+ isBlind: false,
1573
+ select: maxDistancePairs
1574
+ },
1575
+ {
1576
+ id: "crisp",
1577
+ weight: .8,
1578
+ isBlind: false,
1579
+ select: (g, nPairs, rngSeed) => crisp(g, {
1580
+ nPairs,
1581
+ rngSeed
1582
+ }).pairs
1583
+ }
1584
+ ], {
1585
+ nPairs: config.nPairs,
1586
+ rngSeed: config.rngSeed,
1587
+ blindPriorityBias: config.blindPriorityBias
1588
+ }) };
1589
+ }
1590
+ //#endregion
1591
+ //#region src/seeds/stratified.ts
1592
+ /** Default values */
1593
+ var DEFAULTS = {
1594
+ pairsPerStratum: 10,
1595
+ rngSeed: 42
1596
+ };
1597
+ /**
1598
+ * Simple seeded pseudo-random number generator using mulberry32.
1599
+ */
1600
+ function createRNG(seed) {
1601
+ let state = seed >>> 0;
1602
+ return () => {
1603
+ state = state + 1831565813 >>> 0;
1604
+ let t = Math.imul(state ^ state >>> 15, state | 1);
1605
+ t = (t ^ t >>> 7) * (t | 1640531527);
1606
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
1607
+ };
1608
+ }
1609
+ /**
1610
+ * Stratified seed selection algorithm.
1611
+ *
1612
+ * @param graph - The graph to sample seeds from
1613
+ * @param options - Configuration options including field classifier
1614
+ * @returns Stratified selection result
1615
+ *
1616
+ * @example
1617
+ * ```typescript
1618
+ * const graph = new AdjacencyMapGraph();
1619
+ * // ... populate graph ...
1620
+ *
1621
+ * const result = stratified(graph, {
1622
+ * fieldClassifier: (node) => node.type === 'paper' ? 'computer-science' : undefined,
1623
+ * pairsPerStratum: 20,
1624
+ * });
1625
+ *
1626
+ * for (const stratum of result.strata) {
1627
+ * console.log(`${stratum.name}: ${stratum.pairs.length} pairs`);
1628
+ * }
1629
+ * ```
1630
+ */
1631
+ function stratified(graph, options) {
1632
+ const { fieldClassifier, pairsPerStratum = DEFAULTS.pairsPerStratum, rngSeed = DEFAULTS.rngSeed, customStrata } = options;
1633
+ const rng = createRNG(rngSeed);
1634
+ const strataDefinitions = customStrata ?? [];
1635
+ const nodesWithFields = [];
1636
+ for (const nodeId of graph.nodeIds()) {
1637
+ const node = graph.getNode(nodeId);
1638
+ if (node === void 0) continue;
1639
+ const field = fieldClassifier(node.type !== void 0 ? {
1640
+ id: nodeId,
1641
+ type: node.type
1642
+ } : { id: nodeId });
1643
+ if (field === void 0) continue;
1644
+ const nodeWithField = node.type !== void 0 ? {
1645
+ id: nodeId,
1646
+ type: node.type,
1647
+ field
1648
+ } : {
1649
+ id: nodeId,
1650
+ field
1651
+ };
1652
+ nodesWithFields.push(nodeWithField);
1653
+ }
1654
+ const errors = [];
1655
+ const strataResults = [];
1656
+ for (const stratum of strataDefinitions) {
1657
+ const pairs = [];
1658
+ const eligiblePairs = [];
1659
+ for (let i = 0; i < nodesWithFields.length; i++) {
1660
+ const source = nodesWithFields[i];
1661
+ if (source === void 0) continue;
1662
+ for (let j = i + 1; j < nodesWithFields.length; j++) {
1663
+ if (j === i) continue;
1664
+ const target = nodesWithFields[j];
1665
+ if (target === void 0) continue;
1666
+ if (stratum.predicate(source, target)) eligiblePairs.push({
1667
+ source,
1668
+ target
1669
+ });
1670
+ }
1671
+ }
1672
+ const numToSample = Math.min(pairsPerStratum, eligiblePairs.length);
1673
+ for (let i = 0; i < numToSample; i++) {
1674
+ const pair = eligiblePairs[Math.floor(rng() * eligiblePairs.length)];
1675
+ if (pair === void 0) continue;
1676
+ const sourceField = fieldClassifier(pair.source);
1677
+ const targetField = fieldClassifier(pair.target);
1678
+ pairs.push({
1679
+ source: { id: pair.source.id },
1680
+ target: { id: pair.target.id },
1681
+ stratum: stratum.name,
1682
+ sameField: sourceField === targetField
1683
+ });
1684
+ }
1685
+ strataResults.push({
1686
+ name: stratum.name,
1687
+ pairs
1688
+ });
1689
+ }
1690
+ for (const stratum of strataDefinitions) {
1691
+ const result = strataResults.find((r) => r.name === stratum.name);
1692
+ if (result === void 0 || result.pairs.length === 0) errors.push(/* @__PURE__ */ new Error(`No pairs found for stratum: ${stratum.name}`));
1693
+ }
1694
+ return {
1695
+ strata: strataResults,
1696
+ totalPairs: strataResults.reduce((sum, r) => sum + r.pairs.length, 0),
1697
+ errors
1698
+ };
1699
+ }
1700
+ //#endregion
1701
+ Object.defineProperty(exports, "basil", {
1702
+ enumerable: true,
1703
+ get: function() {
1704
+ return basil;
1705
+ }
1706
+ });
1707
+ Object.defineProperty(exports, "brisk", {
1708
+ enumerable: true,
1709
+ get: function() {
1710
+ return brisk;
1711
+ }
1712
+ });
1713
+ Object.defineProperty(exports, "crest", {
1714
+ enumerable: true,
1715
+ get: function() {
1716
+ return crest;
1717
+ }
1718
+ });
1719
+ Object.defineProperty(exports, "crisp", {
1720
+ enumerable: true,
1721
+ get: function() {
1722
+ return crisp;
1723
+ }
1724
+ });
1725
+ Object.defineProperty(exports, "grasp", {
1726
+ enumerable: true,
1727
+ get: function() {
1728
+ return grasp;
1729
+ }
1730
+ });
1731
+ Object.defineProperty(exports, "omnia", {
1732
+ enumerable: true,
1733
+ get: function() {
1734
+ return omnia;
1735
+ }
1736
+ });
1737
+ Object.defineProperty(exports, "prism", {
1738
+ enumerable: true,
1739
+ get: function() {
1740
+ return prism;
1741
+ }
1742
+ });
1743
+ Object.defineProperty(exports, "spine", {
1744
+ enumerable: true,
1745
+ get: function() {
1746
+ return spine;
1747
+ }
1748
+ });
1749
+ Object.defineProperty(exports, "stratified", {
1750
+ enumerable: true,
1751
+ get: function() {
1752
+ return stratified;
1753
+ }
1754
+ });
1755
+ Object.defineProperty(exports, "stride", {
1756
+ enumerable: true,
1757
+ get: function() {
1758
+ return stride;
1759
+ }
1760
+ });
1761
+
1762
+ //# sourceMappingURL=seeds--fLhoBaG.cjs.map