graphwise 1.0.0 → 1.1.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 (224) hide show
  1. package/README.md +41 -0
  2. package/dist/expansion/base.d.ts +12 -0
  3. package/dist/expansion/base.d.ts.map +1 -0
  4. package/dist/expansion/base.unit.test.d.ts +2 -0
  5. package/dist/expansion/base.unit.test.d.ts.map +1 -0
  6. package/dist/expansion/dome.d.ts +16 -0
  7. package/dist/expansion/dome.d.ts.map +1 -0
  8. package/dist/expansion/dome.unit.test.d.ts +2 -0
  9. package/dist/expansion/dome.unit.test.d.ts.map +1 -0
  10. package/dist/expansion/edge.d.ts +15 -0
  11. package/dist/expansion/edge.d.ts.map +1 -0
  12. package/dist/expansion/edge.unit.test.d.ts +2 -0
  13. package/dist/expansion/edge.unit.test.d.ts.map +1 -0
  14. package/dist/expansion/hae.d.ts +22 -0
  15. package/dist/expansion/hae.d.ts.map +1 -0
  16. package/dist/expansion/hae.unit.test.d.ts +2 -0
  17. package/dist/expansion/hae.unit.test.d.ts.map +1 -0
  18. package/dist/expansion/index.d.ts +22 -0
  19. package/dist/expansion/index.d.ts.map +1 -0
  20. package/dist/expansion/maze.d.ts +25 -0
  21. package/dist/expansion/maze.d.ts.map +1 -0
  22. package/dist/expansion/maze.unit.test.d.ts +2 -0
  23. package/dist/expansion/maze.unit.test.d.ts.map +1 -0
  24. package/dist/expansion/pipe.d.ts +15 -0
  25. package/dist/expansion/pipe.d.ts.map +1 -0
  26. package/dist/expansion/pipe.unit.test.d.ts +2 -0
  27. package/dist/expansion/pipe.unit.test.d.ts.map +1 -0
  28. package/dist/expansion/reach.d.ts +26 -0
  29. package/dist/expansion/reach.d.ts.map +1 -0
  30. package/dist/expansion/reach.unit.test.d.ts +2 -0
  31. package/dist/expansion/reach.unit.test.d.ts.map +1 -0
  32. package/dist/expansion/sage.d.ts +24 -0
  33. package/dist/expansion/sage.d.ts.map +1 -0
  34. package/dist/expansion/sage.unit.test.d.ts +2 -0
  35. package/dist/expansion/sage.unit.test.d.ts.map +1 -0
  36. package/dist/expansion/types.d.ts +105 -0
  37. package/dist/expansion/types.d.ts.map +1 -0
  38. package/dist/extraction/ego-network.d.ts +32 -0
  39. package/dist/extraction/ego-network.d.ts.map +1 -0
  40. package/dist/extraction/ego-network.unit.test.d.ts +5 -0
  41. package/dist/extraction/ego-network.unit.test.d.ts.map +1 -0
  42. package/dist/extraction/index.d.ts +20 -0
  43. package/dist/extraction/index.d.ts.map +1 -0
  44. package/dist/extraction/induced-subgraph.d.ts +19 -0
  45. package/dist/extraction/induced-subgraph.d.ts.map +1 -0
  46. package/dist/extraction/induced-subgraph.unit.test.d.ts +5 -0
  47. package/dist/extraction/induced-subgraph.unit.test.d.ts.map +1 -0
  48. package/dist/extraction/k-core.d.ts +24 -0
  49. package/dist/extraction/k-core.d.ts.map +1 -0
  50. package/dist/extraction/k-core.unit.test.d.ts +5 -0
  51. package/dist/extraction/k-core.unit.test.d.ts.map +1 -0
  52. package/dist/extraction/motif.d.ts +50 -0
  53. package/dist/extraction/motif.d.ts.map +1 -0
  54. package/dist/extraction/motif.unit.test.d.ts +5 -0
  55. package/dist/extraction/motif.unit.test.d.ts.map +1 -0
  56. package/dist/extraction/node-filter.d.ts +35 -0
  57. package/dist/extraction/node-filter.d.ts.map +1 -0
  58. package/dist/extraction/node-filter.unit.test.d.ts +5 -0
  59. package/dist/extraction/node-filter.unit.test.d.ts.map +1 -0
  60. package/dist/extraction/truss.d.ts +41 -0
  61. package/dist/extraction/truss.d.ts.map +1 -0
  62. package/dist/extraction/truss.unit.test.d.ts +5 -0
  63. package/dist/extraction/truss.unit.test.d.ts.map +1 -0
  64. package/dist/gpu/context.d.ts +118 -0
  65. package/dist/gpu/context.d.ts.map +1 -0
  66. package/dist/gpu/context.unit.test.d.ts +2 -0
  67. package/dist/gpu/context.unit.test.d.ts.map +1 -0
  68. package/dist/gpu/csr.d.ts +97 -0
  69. package/dist/gpu/csr.d.ts.map +1 -0
  70. package/dist/gpu/csr.unit.test.d.ts +2 -0
  71. package/dist/gpu/csr.unit.test.d.ts.map +1 -0
  72. package/dist/gpu/detect.d.ts +25 -0
  73. package/dist/gpu/detect.d.ts.map +1 -0
  74. package/dist/gpu/detect.unit.test.d.ts +2 -0
  75. package/dist/gpu/detect.unit.test.d.ts.map +1 -0
  76. package/dist/gpu/index.cjs +6 -0
  77. package/dist/gpu/index.d.ts +11 -0
  78. package/dist/gpu/index.d.ts.map +1 -0
  79. package/dist/gpu/index.js +2 -0
  80. package/dist/gpu/types.d.ts +50 -0
  81. package/dist/gpu/types.d.ts.map +1 -0
  82. package/dist/gpu-BJRVYBjx.cjs +338 -0
  83. package/dist/gpu-BJRVYBjx.cjs.map +1 -0
  84. package/dist/gpu-BveuXugy.js +315 -0
  85. package/dist/gpu-BveuXugy.js.map +1 -0
  86. package/dist/graph/adjacency-map.d.ts +95 -0
  87. package/dist/graph/adjacency-map.d.ts.map +1 -0
  88. package/dist/graph/adjacency-map.unit.test.d.ts +2 -0
  89. package/dist/graph/adjacency-map.unit.test.d.ts.map +1 -0
  90. package/dist/graph/index.cjs +3 -0
  91. package/dist/graph/index.d.ts +9 -0
  92. package/dist/graph/index.d.ts.map +1 -0
  93. package/dist/graph/index.js +2 -0
  94. package/dist/graph/interfaces.d.ts +125 -0
  95. package/dist/graph/interfaces.d.ts.map +1 -0
  96. package/dist/graph/types.d.ts +72 -0
  97. package/dist/graph/types.d.ts.map +1 -0
  98. package/dist/graph-DLWiziLB.js +222 -0
  99. package/dist/graph-DLWiziLB.js.map +1 -0
  100. package/dist/graph-az06J1YV.cjs +227 -0
  101. package/dist/graph-az06J1YV.cjs.map +1 -0
  102. package/dist/index/index.cjs +1404 -0
  103. package/dist/index/index.cjs.map +1 -0
  104. package/dist/index/index.js +1356 -0
  105. package/dist/index/index.js.map +1 -0
  106. package/dist/index.d.ts +15 -0
  107. package/dist/index.d.ts.map +1 -0
  108. package/dist/kmeans-B0HEOU6k.cjs +234 -0
  109. package/dist/kmeans-B0HEOU6k.cjs.map +1 -0
  110. package/dist/kmeans-DgbsOznU.js +223 -0
  111. package/dist/kmeans-DgbsOznU.js.map +1 -0
  112. package/dist/ranking/baselines/shortest.d.ts +15 -0
  113. package/dist/ranking/baselines/shortest.d.ts.map +1 -0
  114. package/dist/ranking/baselines/shortest.unit.test.d.ts +2 -0
  115. package/dist/ranking/baselines/shortest.unit.test.d.ts.map +1 -0
  116. package/dist/ranking/baselines/types.d.ts +30 -0
  117. package/dist/ranking/baselines/types.d.ts.map +1 -0
  118. package/dist/ranking/index.d.ts +15 -0
  119. package/dist/ranking/index.d.ts.map +1 -0
  120. package/dist/ranking/mi/adamic-adar.d.ts +13 -0
  121. package/dist/ranking/mi/adamic-adar.d.ts.map +1 -0
  122. package/dist/ranking/mi/adaptive.d.ts +16 -0
  123. package/dist/ranking/mi/adaptive.d.ts.map +1 -0
  124. package/dist/ranking/mi/etch.d.ts +7 -0
  125. package/dist/ranking/mi/etch.d.ts.map +1 -0
  126. package/dist/ranking/mi/index.d.ts +18 -0
  127. package/dist/ranking/mi/index.d.ts.map +1 -0
  128. package/dist/ranking/mi/jaccard.d.ts +13 -0
  129. package/dist/ranking/mi/jaccard.d.ts.map +1 -0
  130. package/dist/ranking/mi/mi-variants.unit.test.d.ts +2 -0
  131. package/dist/ranking/mi/mi-variants.unit.test.d.ts.map +1 -0
  132. package/dist/ranking/mi/notch.d.ts +7 -0
  133. package/dist/ranking/mi/notch.d.ts.map +1 -0
  134. package/dist/ranking/mi/scale.d.ts +7 -0
  135. package/dist/ranking/mi/scale.d.ts.map +1 -0
  136. package/dist/ranking/mi/skew.d.ts +7 -0
  137. package/dist/ranking/mi/skew.d.ts.map +1 -0
  138. package/dist/ranking/mi/span.d.ts +7 -0
  139. package/dist/ranking/mi/span.d.ts.map +1 -0
  140. package/dist/ranking/mi/types.d.ts +35 -0
  141. package/dist/ranking/mi/types.d.ts.map +1 -0
  142. package/dist/ranking/parse.d.ts +56 -0
  143. package/dist/ranking/parse.d.ts.map +1 -0
  144. package/dist/ranking/parse.unit.test.d.ts +2 -0
  145. package/dist/ranking/parse.unit.test.d.ts.map +1 -0
  146. package/dist/schemas/define.d.ts +18 -0
  147. package/dist/schemas/define.d.ts.map +1 -0
  148. package/dist/schemas/define.unit.test.d.ts +2 -0
  149. package/dist/schemas/define.unit.test.d.ts.map +1 -0
  150. package/dist/schemas/graph.d.ts +85 -0
  151. package/dist/schemas/graph.d.ts.map +1 -0
  152. package/dist/schemas/graph.unit.test.d.ts +2 -0
  153. package/dist/schemas/graph.unit.test.d.ts.map +1 -0
  154. package/dist/schemas/index.cjs +3791 -0
  155. package/dist/schemas/index.cjs.map +1 -0
  156. package/dist/schemas/index.d.ts +3 -0
  157. package/dist/schemas/index.d.ts.map +1 -0
  158. package/dist/schemas/index.js +3782 -0
  159. package/dist/schemas/index.js.map +1 -0
  160. package/dist/seeds/grasp.d.ts +79 -0
  161. package/dist/seeds/grasp.d.ts.map +1 -0
  162. package/dist/seeds/grasp.unit.test.d.ts +2 -0
  163. package/dist/seeds/grasp.unit.test.d.ts.map +1 -0
  164. package/dist/seeds/index.cjs +4 -0
  165. package/dist/seeds/index.d.ts +10 -0
  166. package/dist/seeds/index.d.ts.map +1 -0
  167. package/dist/seeds/index.js +2 -0
  168. package/dist/seeds/stratified.d.ts +85 -0
  169. package/dist/seeds/stratified.d.ts.map +1 -0
  170. package/dist/seeds/stratified.unit.test.d.ts +2 -0
  171. package/dist/seeds/stratified.unit.test.d.ts.map +1 -0
  172. package/dist/seeds-B6J9oJfU.cjs +404 -0
  173. package/dist/seeds-B6J9oJfU.cjs.map +1 -0
  174. package/dist/seeds-UNZxqm_U.js +393 -0
  175. package/dist/seeds-UNZxqm_U.js.map +1 -0
  176. package/dist/structures/index.cjs +3 -0
  177. package/dist/structures/index.d.ts +3 -0
  178. package/dist/structures/index.d.ts.map +1 -0
  179. package/dist/structures/index.js +2 -0
  180. package/dist/structures/priority-queue.d.ts +59 -0
  181. package/dist/structures/priority-queue.d.ts.map +1 -0
  182. package/dist/structures/priority-queue.unit.test.d.ts +2 -0
  183. package/dist/structures/priority-queue.unit.test.d.ts.map +1 -0
  184. package/dist/structures-BPfhfqNP.js +133 -0
  185. package/dist/structures-BPfhfqNP.js.map +1 -0
  186. package/dist/structures-CJ_S_7fs.cjs +138 -0
  187. package/dist/structures-CJ_S_7fs.cjs.map +1 -0
  188. package/dist/traversal/bfs.d.ts +50 -0
  189. package/dist/traversal/bfs.d.ts.map +1 -0
  190. package/dist/traversal/bfs.unit.test.d.ts +2 -0
  191. package/dist/traversal/bfs.unit.test.d.ts.map +1 -0
  192. package/dist/traversal/dfs.d.ts +50 -0
  193. package/dist/traversal/dfs.d.ts.map +1 -0
  194. package/dist/traversal/dfs.unit.test.d.ts +2 -0
  195. package/dist/traversal/dfs.unit.test.d.ts.map +1 -0
  196. package/dist/traversal/index.cjs +6 -0
  197. package/dist/traversal/index.d.ts +11 -0
  198. package/dist/traversal/index.d.ts.map +1 -0
  199. package/dist/traversal/index.js +2 -0
  200. package/dist/traversal-CQCjUwUJ.js +149 -0
  201. package/dist/traversal-CQCjUwUJ.js.map +1 -0
  202. package/dist/traversal-QeHaNUWn.cjs +172 -0
  203. package/dist/traversal-QeHaNUWn.cjs.map +1 -0
  204. package/dist/utils/clustering-coefficient.d.ts +38 -0
  205. package/dist/utils/clustering-coefficient.d.ts.map +1 -0
  206. package/dist/utils/clustering-coefficient.unit.test.d.ts +2 -0
  207. package/dist/utils/clustering-coefficient.unit.test.d.ts.map +1 -0
  208. package/dist/utils/entropy.d.ts +58 -0
  209. package/dist/utils/entropy.d.ts.map +1 -0
  210. package/dist/utils/entropy.unit.test.d.ts +2 -0
  211. package/dist/utils/entropy.unit.test.d.ts.map +1 -0
  212. package/dist/utils/index.cjs +13 -0
  213. package/dist/utils/index.d.ts +9 -0
  214. package/dist/utils/index.d.ts.map +1 -0
  215. package/dist/utils/index.js +3 -0
  216. package/dist/utils/kmeans.d.ts +73 -0
  217. package/dist/utils/kmeans.d.ts.map +1 -0
  218. package/dist/utils/kmeans.unit.test.d.ts +2 -0
  219. package/dist/utils/kmeans.unit.test.d.ts.map +1 -0
  220. package/dist/utils-Q_akvlMn.js +164 -0
  221. package/dist/utils-Q_akvlMn.js.map +1 -0
  222. package/dist/utils-spZa1ZvS.cjs +205 -0
  223. package/dist/utils-spZa1ZvS.cjs.map +1 -0
  224. package/package.json +136 -8
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seeds-B6J9oJfU.cjs","names":[],"sources":["../src/seeds/grasp.ts","../src/seeds/stratified.ts"],"sourcesContent":["/**\n * GRASP — Graph-agnostic Representative Seed pAir Sampling.\n *\n * Novel blind structural seed selection algorithm that selects structurally\n * representative seed pairs without requiring domain knowledge or loading\n * the full graph into memory.\n *\n * Three phases:\n * 1. Reservoir sampling — stream edges, maintain reservoir of N nodes\n * 2. Structural feature computation — log-degree, clustering coeff, approx PageRank\n * 3. Mini-batch K-means clustering, then sample pairs within and across clusters\n *\n * @packageDocumentation\n */\n\nimport type { ReadableGraph, NodeId } from \"../graph\";\nimport type { Seed } from \"../schemas/index\";\nimport {\n\tminiBatchKMeans,\n\tzScoreNormalise,\n\ttype LabelledFeature,\n\ttype FeatureVector3D,\n} from \"../utils/kmeans\";\n\n/**\n * Configuration options for GRASP seed selection.\n */\nexport interface GraspOptions {\n\t/** Number of clusters for K-means (default: 100) */\n\treadonly nClusters?: number;\n\t/** Number of pairs to sample per cluster (default: 10) */\n\treadonly pairsPerCluster?: number;\n\t/** Ratio of within-cluster pairs vs cross-cluster pairs (default: 0.5) */\n\treadonly withinClusterRatio?: number;\n\t/** Reservoir sample size (default: 200000) */\n\treadonly sampleSize?: number;\n\t/** Random seed for reproducibility (default: 42) */\n\treadonly rngSeed?: number;\n\t/** Number of PageRank iterations for feature computation (default: 10) */\n\treadonly pagerankIterations?: number;\n}\n\n/**\n * A sampled seed pair with structural metadata.\n */\nexport interface GraspSeedPair {\n\t/** Source seed */\n\treadonly source: Seed;\n\t/** Target seed */\n\treadonly target: Seed;\n\t/** Euclidean distance in feature space */\n\treadonly featureDistance: number;\n\t/** Whether both seeds are from the same cluster */\n\treadonly sameCluster: boolean;\n\t/** Cluster index of source (or -1 if unclustered) */\n\treadonly sourceCluster: number;\n\t/** Cluster index of target (or -1 if unclustered) */\n\treadonly targetCluster: number;\n}\n\n/**\n * Result of GRASP seed selection.\n */\nexport interface GraspResult {\n\t/** Sampled seed pairs */\n\treadonly pairs: readonly GraspSeedPair[];\n\t/** Number of clusters used */\n\treadonly nClusters: number;\n\t/** Total nodes sampled */\n\treadonly sampledNodeCount: number;\n\t/** Features computed for sampled nodes */\n\treadonly features: readonly LabelledFeature[];\n\t/** Cluster assignments (nodeId -> cluster index) */\n\treadonly clusterAssignments: ReadonlyMap<string, number>;\n}\n\n/** Default configuration values */\nconst DEFAULTS = {\n\tnClusters: 100,\n\tpairsPerCluster: 10,\n\twithinClusterRatio: 0.5,\n\tsampleSize: 200000,\n\trngSeed: 42,\n\tpagerankIterations: 10,\n} as const;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Reservoir sampling (Vitter's Algorithm R) for streaming node selection.\n *\n * Maintains a uniform sample of nodes as edges are streamed, without\n * requiring the full graph in memory.\n */\nfunction reservoirSample(\n\tgraph: ReadableGraph,\n\tsampleSize: number,\n\trng: () => number,\n): { nodeIds: Set<NodeId>; neighbourMap: Map<NodeId, Set<NodeId>> } {\n\tconst reservoir: NodeId[] = [];\n\tconst neighbourMap = new Map<NodeId, Set<NodeId>>();\n\tconst inReservoir = new Set<NodeId>();\n\n\t// Track total nodes seen for reservoir probability\n\tlet nodesSeen = 0;\n\n\t// Stream all edges and sample nodes\n\tfor (const edge of graph.edges()) {\n\t\t// Process source node\n\t\tconst source = edge.source;\n\t\tif (!inReservoir.has(source)) {\n\t\t\tnodesSeen++;\n\t\t\tif (reservoir.length < sampleSize) {\n\t\t\t\treservoir.push(source);\n\t\t\t\tinReservoir.add(source);\n\t\t\t\tneighbourMap.set(source, new Set<NodeId>());\n\t\t\t} else {\n\t\t\t\t// Reservoir sampling: replace with probability sampleSize/nodesSeen\n\t\t\t\tconst j = Math.floor(rng() * nodesSeen);\n\t\t\t\tif (j < sampleSize) {\n\t\t\t\t\tconst oldNode = reservoir[j];\n\t\t\t\t\tif (oldNode !== undefined) {\n\t\t\t\t\t\tinReservoir.delete(oldNode);\n\t\t\t\t\t\tneighbourMap.delete(oldNode);\n\t\t\t\t\t}\n\t\t\t\t\treservoir[j] = source;\n\t\t\t\t\tinReservoir.add(source);\n\t\t\t\t\tneighbourMap.set(source, new Set<NodeId>());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Process target node\n\t\tconst target = edge.target;\n\t\tif (!inReservoir.has(target)) {\n\t\t\tnodesSeen++;\n\t\t\tif (reservoir.length < sampleSize) {\n\t\t\t\treservoir.push(target);\n\t\t\t\tinReservoir.add(target);\n\t\t\t\tneighbourMap.set(target, new Set<NodeId>());\n\t\t\t} else {\n\t\t\t\tconst j = Math.floor(rng() * nodesSeen);\n\t\t\t\tif (j < sampleSize) {\n\t\t\t\t\tconst oldNode = reservoir[j];\n\t\t\t\t\tif (oldNode !== undefined) {\n\t\t\t\t\t\tinReservoir.delete(oldNode);\n\t\t\t\t\t\tneighbourMap.delete(oldNode);\n\t\t\t\t\t}\n\t\t\t\t\treservoir[j] = target;\n\t\t\t\t\tinReservoir.add(target);\n\t\t\t\t\tneighbourMap.set(target, new Set<NodeId>());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Store neighbour relationships for sampled nodes\n\t\tif (inReservoir.has(source) && inReservoir.has(target)) {\n\t\t\tconst sourceNeighbours = neighbourMap.get(source);\n\t\t\tconst targetNeighbours = neighbourMap.get(target);\n\t\t\tsourceNeighbours?.add(target);\n\t\t\ttargetNeighbours?.add(source);\n\t\t}\n\t}\n\n\treturn { nodeIds: inReservoir, neighbourMap };\n}\n\n/**\n * Compute approximate PageRank scores using power iteration on the reservoir subgraph.\n *\n * This is an approximation since it only considers the sampled nodes and their\n * connections within the reservoir, not the full graph.\n */\nfunction approximatePageRank(\n\tnodeIds: Set<NodeId>,\n\tneighbourMap: Map<NodeId, Set<NodeId>>,\n\titerations: number,\n\tdampingFactor = 0.85,\n): Map<NodeId, number> {\n\tconst n = nodeIds.size;\n\tif (n === 0) return new Map();\n\n\tconst nodeIdList = [...nodeIds];\n\tconst nodeIndex = new Map(nodeIdList.map((id, i) => [id, i] as const));\n\n\t// Initialise PageRank scores uniformly\n\tconst scores = new Float64Array(n).fill(1 / n);\n\tconst newScores = new Float64Array(n);\n\n\t// Power iteration\n\tfor (let iter = 0; iter < iterations; iter++) {\n\t\tnewScores.fill((1 - dampingFactor) / n);\n\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst nodeId = nodeIdList[i];\n\t\t\tif (nodeId === undefined) continue;\n\n\t\t\tconst neighbours = neighbourMap.get(nodeId);\n\t\t\tif (neighbours === undefined) continue;\n\n\t\t\tconst outDegree = neighbours.size;\n\t\t\tif (outDegree === 0) continue;\n\n\t\t\tconst contribution = (dampingFactor * (scores[i] ?? 0)) / outDegree;\n\n\t\t\tfor (const neighbour of neighbours) {\n\t\t\t\tconst neighbourIdx = nodeIndex.get(neighbour);\n\t\t\t\tif (neighbourIdx !== undefined) {\n\t\t\t\t\tnewScores[neighbourIdx] =\n\t\t\t\t\t\t(newScores[neighbourIdx] ?? 0) + contribution;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Swap buffers\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tscores[i] = newScores[i] ?? 0;\n\t\t}\n\t}\n\n\t// Build result map\n\tconst result = new Map<NodeId, number>();\n\tfor (let i = 0; i < n; i++) {\n\t\tconst nodeId = nodeIdList[i];\n\t\tconst score = scores[i];\n\t\tif (nodeId !== undefined && score !== undefined) {\n\t\t\tresult.set(nodeId, score);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Compute structural features for sampled nodes.\n *\n * Features:\n * - f1: log(deg(v) + 1) — scale-normalised connectivity\n * - f2: clustering_coefficient(v) — local density\n * - f3: approx_pagerank(v) — positional importance\n */\nfunction computeFeatures(\n\tgraph: ReadableGraph,\n\tnodeIds: Set<NodeId>,\n\tneighbourMap: Map<NodeId, Set<NodeId>>,\n\tpagerankScores: Map<NodeId, number>,\n): LabelledFeature[] {\n\tconst features: LabelledFeature[] = [];\n\n\tfor (const nodeId of nodeIds) {\n\t\tconst degree = graph.degree(nodeId, \"both\");\n\t\tconst neighbours = neighbourMap.get(nodeId);\n\n\t\t// Compute local clustering coefficient using neighbour map\n\t\tlet clusteringCoef = 0;\n\t\tif (neighbours !== undefined && neighbours.size >= 2) {\n\t\t\tlet triangleCount = 0;\n\t\t\tconst neighbourList = [...neighbours];\n\n\t\t\tfor (let i = 0; i < neighbourList.length; i++) {\n\t\t\t\tfor (let j = i + 1; j < neighbourList.length; j++) {\n\t\t\t\t\tconst u = neighbourList[i];\n\t\t\t\t\tconst w = neighbourList[j];\n\t\t\t\t\tif (u !== undefined && w !== undefined) {\n\t\t\t\t\t\t// Check if u and w are connected\n\t\t\t\t\t\tconst uNeighbours = neighbourMap.get(u);\n\t\t\t\t\t\tif (uNeighbours?.has(w) === true) {\n\t\t\t\t\t\t\ttriangleCount++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst possibleTriangles = (degree * (degree - 1)) / 2;\n\t\t\tclusteringCoef = triangleCount / possibleTriangles;\n\t\t}\n\n\t\tconst pagerank = pagerankScores.get(nodeId) ?? 0;\n\n\t\tfeatures.push({\n\t\t\tnodeId,\n\t\t\tf1: Math.log(degree + 1),\n\t\t\tf2: clusteringCoef,\n\t\t\tf3: pagerank,\n\t\t});\n\t}\n\n\treturn features;\n}\n\n/**\n * Sample seed pairs from clusters.\n *\n * For each cluster, samples a mix of within-cluster and cross-cluster pairs.\n */\nfunction samplePairs(\n\tfeatures: readonly LabelledFeature[],\n\tclusterAssignments: ReadonlyMap<string, number>,\n\tnClusters: number,\n\tpairsPerCluster: number,\n\twithinClusterRatio: number,\n\trng: () => number,\n): GraspSeedPair[] {\n\tconst pairs: GraspSeedPair[] = [];\n\n\t// Group nodes by cluster\n\tconst clusterNodes = new Map<number, LabelledFeature[]>();\n\tfor (const feature of features) {\n\t\tconst cluster = clusterAssignments.get(feature.nodeId);\n\t\tif (cluster === undefined) continue;\n\n\t\tlet nodes = clusterNodes.get(cluster);\n\t\tif (nodes === undefined) {\n\t\t\tnodes = [];\n\t\t\tclusterNodes.set(cluster, nodes);\n\t\t}\n\t\tnodes.push(feature);\n\t}\n\n\tconst withinCount = Math.floor(pairsPerCluster * withinClusterRatio);\n\tconst crossCount = pairsPerCluster - withinCount;\n\n\t// Sample pairs for each cluster\n\tfor (let clusterIdx = 0; clusterIdx < nClusters; clusterIdx++) {\n\t\tconst nodes = clusterNodes.get(clusterIdx);\n\t\tif (nodes === undefined || nodes.length < 2) continue;\n\n\t\t// Within-cluster pairs\n\t\tfor (let i = 0; i < withinCount; i++) {\n\t\t\t// Sample two distinct nodes\n\t\t\tconst idx1 = Math.floor(rng() * nodes.length);\n\t\t\tlet idx2 = Math.floor(rng() * nodes.length);\n\t\t\twhile (idx1 === idx2) {\n\t\t\t\tidx2 = Math.floor(rng() * nodes.length);\n\t\t\t}\n\n\t\t\tconst source = nodes[idx1];\n\t\t\tconst target = nodes[idx2];\n\t\t\tif (source === undefined || target === undefined) continue;\n\n\t\t\tconst distance = computeFeatureDistance(source, target);\n\n\t\t\tpairs.push({\n\t\t\t\tsource: { id: source.nodeId },\n\t\t\t\ttarget: { id: target.nodeId },\n\t\t\t\tfeatureDistance: distance,\n\t\t\t\tsameCluster: true,\n\t\t\t\tsourceCluster: clusterIdx,\n\t\t\t\ttargetCluster: clusterIdx,\n\t\t\t});\n\t\t}\n\n\t\t// Cross-cluster pairs\n\t\tfor (let i = 0; i < crossCount; i++) {\n\t\t\tconst source = nodes[Math.floor(rng() * nodes.length)];\n\t\t\tif (source === undefined) continue;\n\n\t\t\t// Pick a random other cluster\n\t\t\tconst otherClusterIdx = Math.floor(rng() * nClusters);\n\t\t\tif (otherClusterIdx === clusterIdx) continue;\n\n\t\t\tconst otherNodes = clusterNodes.get(otherClusterIdx);\n\t\t\tif (otherNodes === undefined || otherNodes.length === 0) continue;\n\n\t\t\tconst target = otherNodes[Math.floor(rng() * otherNodes.length)];\n\t\t\tif (target === undefined) continue;\n\n\t\t\tconst distance = computeFeatureDistance(source, target);\n\n\t\t\tpairs.push({\n\t\t\t\tsource: { id: source.nodeId },\n\t\t\t\ttarget: { id: target.nodeId },\n\t\t\t\tfeatureDistance: distance,\n\t\t\t\tsameCluster: false,\n\t\t\t\tsourceCluster: clusterIdx,\n\t\t\t\ttargetCluster: otherClusterIdx,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn pairs;\n}\n\n/**\n * Compute Euclidean distance between two feature vectors.\n */\nfunction computeFeatureDistance(\n\ta: FeatureVector3D,\n\tb: FeatureVector3D,\n): number {\n\tconst d1 = a.f1 - b.f1;\n\tconst d2 = a.f2 - b.f2;\n\tconst d3 = a.f3 - b.f3;\n\treturn Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);\n}\n\n/**\n * GRASP — Graph-agnostic Representative Seed pAir Sampling.\n *\n * Selects structurally representative seed pairs without domain knowledge.\n * The algorithm streams edges, samples nodes via reservoir sampling, computes\n * structural features, clusters nodes, and samples pairs within/across clusters.\n *\n * @param graph - The graph to sample seeds from\n * @param options - Configuration options\n * @returns Sampled seed pairs with structural metadata\n *\n * @example\n * ```typescript\n * const graph = new AdjacencyMapGraph();\n * // ... populate graph ...\n *\n * const result = grasp(graph, {\n * nClusters: 50,\n * pairsPerCluster: 20,\n * sampleSize: 100000,\n * });\n *\n * console.log(`Sampled ${result.pairs.length} pairs from ${result.sampledNodeCount} nodes`);\n * ```\n */\nexport function grasp(\n\tgraph: ReadableGraph,\n\toptions: GraspOptions = {},\n): GraspResult {\n\tconst config = {\n\t\t...DEFAULTS,\n\t\t...options,\n\t};\n\n\tconst rng = createRNG(config.rngSeed);\n\n\t// Phase 1: Reservoir sampling\n\tconst { nodeIds, neighbourMap } = reservoirSample(\n\t\tgraph,\n\t\tconfig.sampleSize,\n\t\trng,\n\t);\n\n\t// Phase 2: Approximate PageRank on reservoir subgraph\n\tconst pagerankScores = approximatePageRank(\n\t\tnodeIds,\n\t\tneighbourMap,\n\t\tconfig.pagerankIterations,\n\t);\n\n\t// Phase 2: Compute structural features\n\tlet features = computeFeatures(graph, nodeIds, neighbourMap, pagerankScores);\n\n\t// Normalise features\n\tif (features.length > 0) {\n\t\tfeatures = zScoreNormalise(features);\n\t}\n\n\t// Phase 3: K-means clustering\n\tconst k = Math.min(config.nClusters, features.length);\n\tconst kmeansResult = miniBatchKMeans(features, {\n\t\tk,\n\t\tseed: config.rngSeed,\n\t\tmaxIterations: 100,\n\t});\n\n\t// Phase 3: Sample pairs\n\tconst pairs = samplePairs(\n\t\tfeatures,\n\t\tkmeansResult.assignments,\n\t\tkmeansResult.k,\n\t\tconfig.pairsPerCluster,\n\t\tconfig.withinClusterRatio,\n\t\trng,\n\t);\n\n\treturn {\n\t\tpairs,\n\t\tnClusters: kmeansResult.k,\n\t\tsampledNodeCount: nodeIds.size,\n\t\tfeatures,\n\t\tclusterAssignments: kmeansResult.assignments,\n\t};\n}\n","/**\n * Stratified seed selection — legacy human-defined strata.\n *\n * Requires user-provided field/type classifications. This is included for comparison\n * and for users who have domain metadata.\n *\n * @packageDocumentation\n */\n\nimport type { ReadableGraph, NodeId } from \"../graph/index\";\nimport type { Seed } from \"../schemas/index\";\n\n/**\n * Field classification function type.\n * User provides a function that returns the field name for a node.\n */\nexport type FieldClassifier = (node: {\n\tid: NodeId;\n\ttype?: string;\n}) => string | undefined;\n\n/**\n * Stratum definition for seed pair selection.\n */\nexport interface StratumDefinition {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly predicate: (\n\t\tsource: { id: NodeId; type?: string },\n\t\ttarget: { id: NodeId; type?: string },\n\t) => boolean;\n}\n\n/**\n * Stratum with sampled seed pairs.\n */\nexport interface StratumResult {\n\treadonly name: string;\n\treadonly pairs: readonly SeedPair[];\n}\n\n/**\n * A seed pair with stratum metadata.\n */\nexport interface SeedPair {\n\treadonly source: Seed;\n\treadonly target: Seed;\n\treadonly stratum: string;\n\treadonly sameField: boolean;\n}\n\n/**\n * Result of stratified seed selection.\n */\nexport interface StratifiedResult {\n\treadonly strata: readonly StratumResult[];\n\treadonly totalPairs: number;\n\treadonly errors: readonly Error[];\n}\n\n/**\n * Configuration for stratified seed selection.\n */\nexport interface StratifiedOptions {\n\t/** Function to classify nodes by field */\n\treadonly fieldClassifier: FieldClassifier;\n\t/** Number of pairs to sample per stratum */\n\treadonly pairsPerStratum?: number;\n\t/** Random seed for reproducibility */\n\treadonly rngSeed?: number;\n\t/** Custom stratum definitions */\n\treadonly customStrata?: readonly StratumDefinition[];\n}\n\n/** Default values */\nconst DEFAULTS = {\n\tpairsPerStratum: 10,\n\trngSeed: 42,\n} as const;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Stratified seed selection algorithm.\n *\n * @param graph - The graph to sample seeds from\n * @param options - Configuration options including field classifier\n * @returns Stratified selection result\n *\n * @example\n * ```typescript\n * const graph = new AdjacencyMapGraph();\n * // ... populate graph ...\n *\n * const result = stratified(graph, {\n * fieldClassifier: (node) => node.type === 'paper' ? 'computer-science' : undefined,\n * pairsPerStratum: 20,\n * });\n *\n * for (const stratum of result.strata) {\n * console.log(`${stratum.name}: ${stratum.pairs.length} pairs`);\n * }\n * ```\n */\nexport function stratified(\n\tgraph: ReadableGraph,\n\toptions: StratifiedOptions,\n): StratifiedResult {\n\tconst {\n\t\tfieldClassifier,\n\t\tpairsPerStratum = DEFAULTS.pairsPerStratum,\n\t\trngSeed = DEFAULTS.rngSeed,\n\t\tcustomStrata,\n\t} = options;\n\n\tconst rng = createRNG(rngSeed);\n\tconst strataDefinitions = customStrata ?? [];\n\n\t// Collect all nodes with their field classifications\n\tconst nodesWithFields: { id: NodeId; type?: string; field?: string }[] = [];\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst node = graph.getNode(nodeId);\n\t\tif (node === undefined) continue;\n\n\t\tconst field = fieldClassifier({ id: nodeId, type: node.type });\n\t\tif (field === undefined) continue;\n\n\t\tnodesWithFields.push({ id: nodeId, type: node.type, field });\n\t}\n\n\tconst errors: Error[] = [];\n\tconst strataResults: StratumResult[] = [];\n\n\t// Process each stratum\n\tfor (const stratum of strataDefinitions) {\n\t\tconst pairs: SeedPair[] = [];\n\t\tconst eligiblePairs: {\n\t\t\tsource: { id: NodeId; type?: string };\n\t\t\ttarget: { id: NodeId; type?: string };\n\t\t}[] = [];\n\n\t\t// Find all node pairs that match this stratum\n\t\tfor (let i = 0; i < nodesWithFields.length; i++) {\n\t\t\tconst source = nodesWithFields[i];\n\t\t\tif (source === undefined) continue;\n\n\t\t\tfor (let j = i + 1; j < nodesWithFields.length; j++) {\n\t\t\t\tif (j === i) continue;\n\t\t\t\tconst target = nodesWithFields[j];\n\t\t\t\tif (target === undefined) continue;\n\n\t\t\t\tif (stratum.predicate(source, target)) {\n\t\t\t\t\teligiblePairs.push({ source, target });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Sample pairs from eligible pairs\n\t\tconst numToSample = Math.min(pairsPerStratum, eligiblePairs.length);\n\t\tfor (let i = 0; i < numToSample; i++) {\n\t\t\tconst idx = Math.floor(rng() * eligiblePairs.length);\n\t\t\tconst pair = eligiblePairs[idx];\n\t\t\tif (pair === undefined) continue;\n\n\t\t\tconst sourceField = fieldClassifier(pair.source);\n\t\t\tconst targetField = fieldClassifier(pair.target);\n\n\t\t\tpairs.push({\n\t\t\t\tsource: { id: pair.source.id },\n\t\t\t\ttarget: { id: pair.target.id },\n\t\t\t\tstratum: stratum.name,\n\t\t\t\tsameField: sourceField === targetField,\n\t\t\t});\n\t\t}\n\n\t\tstrataResults.push({\n\t\t\tname: stratum.name,\n\t\t\tpairs,\n\t\t});\n\t}\n\n\t// Collect errors for empty strata\n\tfor (const stratum of strataDefinitions) {\n\t\tconst result = strataResults.find((r) => r.name === stratum.name);\n\t\tif (result === undefined || result.pairs.length === 0) {\n\t\t\terrors.push(new Error(`No pairs found for stratum: ${stratum.name}`));\n\t\t}\n\t}\n\n\tconst totalPairs = strataResults.reduce((sum, r) => sum + r.pairs.length, 0);\n\n\treturn {\n\t\tstrata: strataResults,\n\t\ttotalPairs,\n\t\terrors,\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,393 @@
1
+ import { n as normaliseFeatures, t as miniBatchKMeans } from "./kmeans-DgbsOznU.js";
2
+ //#region src/seeds/grasp.ts
3
+ /** Default configuration values */
4
+ var DEFAULTS$1 = {
5
+ nClusters: 100,
6
+ pairsPerCluster: 10,
7
+ withinClusterRatio: .5,
8
+ sampleSize: 2e5,
9
+ rngSeed: 42,
10
+ pagerankIterations: 10
11
+ };
12
+ /**
13
+ * Simple seeded pseudo-random number generator using mulberry32.
14
+ */
15
+ function createRNG$1(seed) {
16
+ let state = seed >>> 0;
17
+ return () => {
18
+ state = state + 1831565813 >>> 0;
19
+ let t = Math.imul(state ^ state >>> 15, state | 1);
20
+ t = (t ^ t >>> 7) * (t | 1640531527);
21
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
22
+ };
23
+ }
24
+ /**
25
+ * Reservoir sampling (Vitter's Algorithm R) for streaming node selection.
26
+ *
27
+ * Maintains a uniform sample of nodes as edges are streamed, without
28
+ * requiring the full graph in memory.
29
+ */
30
+ function reservoirSample(graph, sampleSize, rng) {
31
+ const reservoir = [];
32
+ const neighbourMap = /* @__PURE__ */ new Map();
33
+ const inReservoir = /* @__PURE__ */ new Set();
34
+ let nodesSeen = 0;
35
+ for (const edge of graph.edges()) {
36
+ const source = edge.source;
37
+ if (!inReservoir.has(source)) {
38
+ nodesSeen++;
39
+ if (reservoir.length < sampleSize) {
40
+ reservoir.push(source);
41
+ inReservoir.add(source);
42
+ neighbourMap.set(source, /* @__PURE__ */ new Set());
43
+ } else {
44
+ const j = Math.floor(rng() * nodesSeen);
45
+ if (j < sampleSize) {
46
+ const oldNode = reservoir[j];
47
+ if (oldNode !== void 0) {
48
+ inReservoir.delete(oldNode);
49
+ neighbourMap.delete(oldNode);
50
+ }
51
+ reservoir[j] = source;
52
+ inReservoir.add(source);
53
+ neighbourMap.set(source, /* @__PURE__ */ new Set());
54
+ }
55
+ }
56
+ }
57
+ const target = edge.target;
58
+ if (!inReservoir.has(target)) {
59
+ nodesSeen++;
60
+ if (reservoir.length < sampleSize) {
61
+ reservoir.push(target);
62
+ inReservoir.add(target);
63
+ neighbourMap.set(target, /* @__PURE__ */ new Set());
64
+ } else {
65
+ const j = Math.floor(rng() * nodesSeen);
66
+ if (j < sampleSize) {
67
+ const oldNode = reservoir[j];
68
+ if (oldNode !== void 0) {
69
+ inReservoir.delete(oldNode);
70
+ neighbourMap.delete(oldNode);
71
+ }
72
+ reservoir[j] = target;
73
+ inReservoir.add(target);
74
+ neighbourMap.set(target, /* @__PURE__ */ new Set());
75
+ }
76
+ }
77
+ }
78
+ if (inReservoir.has(source) && inReservoir.has(target)) {
79
+ const sourceNeighbours = neighbourMap.get(source);
80
+ const targetNeighbours = neighbourMap.get(target);
81
+ sourceNeighbours?.add(target);
82
+ targetNeighbours?.add(source);
83
+ }
84
+ }
85
+ return {
86
+ nodeIds: inReservoir,
87
+ neighbourMap
88
+ };
89
+ }
90
+ /**
91
+ * Compute approximate PageRank scores using power iteration on the reservoir subgraph.
92
+ *
93
+ * This is an approximation since it only considers the sampled nodes and their
94
+ * connections within the reservoir, not the full graph.
95
+ */
96
+ function approximatePageRank(nodeIds, neighbourMap, iterations, dampingFactor = .85) {
97
+ const n = nodeIds.size;
98
+ if (n === 0) return /* @__PURE__ */ new Map();
99
+ const nodeIdList = [...nodeIds];
100
+ const nodeIndex = new Map(nodeIdList.map((id, i) => [id, i]));
101
+ const scores = new Float64Array(n).fill(1 / n);
102
+ const newScores = new Float64Array(n);
103
+ for (let iter = 0; iter < iterations; iter++) {
104
+ newScores.fill((1 - dampingFactor) / n);
105
+ for (let i = 0; i < n; i++) {
106
+ const nodeId = nodeIdList[i];
107
+ if (nodeId === void 0) continue;
108
+ const neighbours = neighbourMap.get(nodeId);
109
+ if (neighbours === void 0) continue;
110
+ const outDegree = neighbours.size;
111
+ if (outDegree === 0) continue;
112
+ const contribution = dampingFactor * (scores[i] ?? 0) / outDegree;
113
+ for (const neighbour of neighbours) {
114
+ const neighbourIdx = nodeIndex.get(neighbour);
115
+ if (neighbourIdx !== void 0) newScores[neighbourIdx] = (newScores[neighbourIdx] ?? 0) + contribution;
116
+ }
117
+ }
118
+ for (let i = 0; i < n; i++) scores[i] = newScores[i] ?? 0;
119
+ }
120
+ const result = /* @__PURE__ */ new Map();
121
+ for (let i = 0; i < n; i++) {
122
+ const nodeId = nodeIdList[i];
123
+ const score = scores[i];
124
+ if (nodeId !== void 0 && score !== void 0) result.set(nodeId, score);
125
+ }
126
+ return result;
127
+ }
128
+ /**
129
+ * Compute structural features for sampled nodes.
130
+ *
131
+ * Features:
132
+ * - f1: log(deg(v) + 1) — scale-normalised connectivity
133
+ * - f2: clustering_coefficient(v) — local density
134
+ * - f3: approx_pagerank(v) — positional importance
135
+ */
136
+ function computeFeatures(graph, nodeIds, neighbourMap, pagerankScores) {
137
+ const features = [];
138
+ for (const nodeId of nodeIds) {
139
+ const degree = graph.degree(nodeId, "both");
140
+ const neighbours = neighbourMap.get(nodeId);
141
+ let clusteringCoef = 0;
142
+ if (neighbours !== void 0 && neighbours.size >= 2) {
143
+ let triangleCount = 0;
144
+ const neighbourList = [...neighbours];
145
+ for (let i = 0; i < neighbourList.length; i++) for (let j = i + 1; j < neighbourList.length; j++) {
146
+ const u = neighbourList[i];
147
+ const w = neighbourList[j];
148
+ if (u !== void 0 && w !== void 0) {
149
+ if (neighbourMap.get(u)?.has(w) === true) triangleCount++;
150
+ }
151
+ }
152
+ const possibleTriangles = degree * (degree - 1) / 2;
153
+ clusteringCoef = triangleCount / possibleTriangles;
154
+ }
155
+ const pagerank = pagerankScores.get(nodeId) ?? 0;
156
+ features.push({
157
+ nodeId,
158
+ f1: Math.log(degree + 1),
159
+ f2: clusteringCoef,
160
+ f3: pagerank
161
+ });
162
+ }
163
+ return features;
164
+ }
165
+ /**
166
+ * Sample seed pairs from clusters.
167
+ *
168
+ * For each cluster, samples a mix of within-cluster and cross-cluster pairs.
169
+ */
170
+ function samplePairs(features, clusterAssignments, nClusters, pairsPerCluster, withinClusterRatio, rng) {
171
+ const pairs = [];
172
+ const clusterNodes = /* @__PURE__ */ new Map();
173
+ for (const feature of features) {
174
+ const cluster = clusterAssignments.get(feature.nodeId);
175
+ if (cluster === void 0) continue;
176
+ let nodes = clusterNodes.get(cluster);
177
+ if (nodes === void 0) {
178
+ nodes = [];
179
+ clusterNodes.set(cluster, nodes);
180
+ }
181
+ nodes.push(feature);
182
+ }
183
+ const withinCount = Math.floor(pairsPerCluster * withinClusterRatio);
184
+ const crossCount = pairsPerCluster - withinCount;
185
+ for (let clusterIdx = 0; clusterIdx < nClusters; clusterIdx++) {
186
+ const nodes = clusterNodes.get(clusterIdx);
187
+ if (nodes === void 0 || nodes.length < 2) continue;
188
+ for (let i = 0; i < withinCount; i++) {
189
+ const idx1 = Math.floor(rng() * nodes.length);
190
+ let idx2 = Math.floor(rng() * nodes.length);
191
+ while (idx1 === idx2) idx2 = Math.floor(rng() * nodes.length);
192
+ const source = nodes[idx1];
193
+ const target = nodes[idx2];
194
+ if (source === void 0 || target === void 0) continue;
195
+ const distance = computeFeatureDistance(source, target);
196
+ pairs.push({
197
+ source: { id: source.nodeId },
198
+ target: { id: target.nodeId },
199
+ featureDistance: distance,
200
+ sameCluster: true,
201
+ sourceCluster: clusterIdx,
202
+ targetCluster: clusterIdx
203
+ });
204
+ }
205
+ for (let i = 0; i < crossCount; i++) {
206
+ const source = nodes[Math.floor(rng() * nodes.length)];
207
+ if (source === void 0) continue;
208
+ const otherClusterIdx = Math.floor(rng() * nClusters);
209
+ if (otherClusterIdx === clusterIdx) continue;
210
+ const otherNodes = clusterNodes.get(otherClusterIdx);
211
+ if (otherNodes === void 0 || otherNodes.length === 0) continue;
212
+ const target = otherNodes[Math.floor(rng() * otherNodes.length)];
213
+ if (target === void 0) continue;
214
+ const distance = computeFeatureDistance(source, target);
215
+ pairs.push({
216
+ source: { id: source.nodeId },
217
+ target: { id: target.nodeId },
218
+ featureDistance: distance,
219
+ sameCluster: false,
220
+ sourceCluster: clusterIdx,
221
+ targetCluster: otherClusterIdx
222
+ });
223
+ }
224
+ }
225
+ return pairs;
226
+ }
227
+ /**
228
+ * Compute Euclidean distance between two feature vectors.
229
+ */
230
+ function computeFeatureDistance(a, b) {
231
+ const d1 = a.f1 - b.f1;
232
+ const d2 = a.f2 - b.f2;
233
+ const d3 = a.f3 - b.f3;
234
+ return Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);
235
+ }
236
+ /**
237
+ * GRASP — Graph-agnostic Representative Seed pAir Sampling.
238
+ *
239
+ * Selects structurally representative seed pairs without domain knowledge.
240
+ * The algorithm streams edges, samples nodes via reservoir sampling, computes
241
+ * structural features, clusters nodes, and samples pairs within/across clusters.
242
+ *
243
+ * @param graph - The graph to sample seeds from
244
+ * @param options - Configuration options
245
+ * @returns Sampled seed pairs with structural metadata
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * const graph = new AdjacencyMapGraph();
250
+ * // ... populate graph ...
251
+ *
252
+ * const result = grasp(graph, {
253
+ * nClusters: 50,
254
+ * pairsPerCluster: 20,
255
+ * sampleSize: 100000,
256
+ * });
257
+ *
258
+ * console.log(`Sampled ${result.pairs.length} pairs from ${result.sampledNodeCount} nodes`);
259
+ * ```
260
+ */
261
+ function grasp(graph, options = {}) {
262
+ const config = {
263
+ ...DEFAULTS$1,
264
+ ...options
265
+ };
266
+ const rng = createRNG$1(config.rngSeed);
267
+ const { nodeIds, neighbourMap } = reservoirSample(graph, config.sampleSize, rng);
268
+ let features = computeFeatures(graph, nodeIds, neighbourMap, approximatePageRank(nodeIds, neighbourMap, config.pagerankIterations));
269
+ if (features.length > 0) features = normaliseFeatures(features);
270
+ const k = Math.min(config.nClusters, features.length);
271
+ const kmeansResult = miniBatchKMeans(features, {
272
+ k,
273
+ seed: config.rngSeed,
274
+ maxIterations: 100
275
+ });
276
+ return {
277
+ pairs: samplePairs(features, kmeansResult.assignments, kmeansResult.k, config.pairsPerCluster, config.withinClusterRatio, rng),
278
+ nClusters: kmeansResult.k,
279
+ sampledNodeCount: nodeIds.size,
280
+ features,
281
+ clusterAssignments: kmeansResult.assignments
282
+ };
283
+ }
284
+ //#endregion
285
+ //#region src/seeds/stratified.ts
286
+ /** Default values */
287
+ var DEFAULTS = {
288
+ pairsPerStratum: 10,
289
+ rngSeed: 42
290
+ };
291
+ /**
292
+ * Simple seeded pseudo-random number generator using mulberry32.
293
+ */
294
+ function createRNG(seed) {
295
+ let state = seed >>> 0;
296
+ return () => {
297
+ state = state + 1831565813 >>> 0;
298
+ let t = Math.imul(state ^ state >>> 15, state | 1);
299
+ t = (t ^ t >>> 7) * (t | 1640531527);
300
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
301
+ };
302
+ }
303
+ /**
304
+ * Stratified seed selection algorithm.
305
+ *
306
+ * @param graph - The graph to sample seeds from
307
+ * @param options - Configuration options including field classifier
308
+ * @returns Stratified selection result
309
+ *
310
+ * @example
311
+ * ```typescript
312
+ * const graph = new AdjacencyMapGraph();
313
+ * // ... populate graph ...
314
+ *
315
+ * const result = stratified(graph, {
316
+ * fieldClassifier: (node) => node.type === 'paper' ? 'computer-science' : undefined,
317
+ * pairsPerStratum: 20,
318
+ * });
319
+ *
320
+ * for (const stratum of result.strata) {
321
+ * console.log(`${stratum.name}: ${stratum.pairs.length} pairs`);
322
+ * }
323
+ * ```
324
+ */
325
+ function stratified(graph, options) {
326
+ const { fieldClassifier, pairsPerStratum = DEFAULTS.pairsPerStratum, rngSeed = DEFAULTS.rngSeed, customStrata } = options;
327
+ const rng = createRNG(rngSeed);
328
+ const strataDefinitions = customStrata ?? [];
329
+ const nodesWithFields = [];
330
+ for (const nodeId of graph.nodeIds()) {
331
+ const node = graph.getNode(nodeId);
332
+ if (node === void 0) continue;
333
+ const field = fieldClassifier({
334
+ id: nodeId,
335
+ type: node.type
336
+ });
337
+ if (field === void 0) continue;
338
+ nodesWithFields.push({
339
+ id: nodeId,
340
+ type: node.type,
341
+ field
342
+ });
343
+ }
344
+ const errors = [];
345
+ const strataResults = [];
346
+ for (const stratum of strataDefinitions) {
347
+ const pairs = [];
348
+ const eligiblePairs = [];
349
+ for (let i = 0; i < nodesWithFields.length; i++) {
350
+ const source = nodesWithFields[i];
351
+ if (source === void 0) continue;
352
+ for (let j = i + 1; j < nodesWithFields.length; j++) {
353
+ if (j === i) continue;
354
+ const target = nodesWithFields[j];
355
+ if (target === void 0) continue;
356
+ if (stratum.predicate(source, target)) eligiblePairs.push({
357
+ source,
358
+ target
359
+ });
360
+ }
361
+ }
362
+ const numToSample = Math.min(pairsPerStratum, eligiblePairs.length);
363
+ for (let i = 0; i < numToSample; i++) {
364
+ const pair = eligiblePairs[Math.floor(rng() * eligiblePairs.length)];
365
+ if (pair === void 0) continue;
366
+ const sourceField = fieldClassifier(pair.source);
367
+ const targetField = fieldClassifier(pair.target);
368
+ pairs.push({
369
+ source: { id: pair.source.id },
370
+ target: { id: pair.target.id },
371
+ stratum: stratum.name,
372
+ sameField: sourceField === targetField
373
+ });
374
+ }
375
+ strataResults.push({
376
+ name: stratum.name,
377
+ pairs
378
+ });
379
+ }
380
+ for (const stratum of strataDefinitions) {
381
+ const result = strataResults.find((r) => r.name === stratum.name);
382
+ if (result === void 0 || result.pairs.length === 0) errors.push(/* @__PURE__ */ new Error(`No pairs found for stratum: ${stratum.name}`));
383
+ }
384
+ return {
385
+ strata: strataResults,
386
+ totalPairs: strataResults.reduce((sum, r) => sum + r.pairs.length, 0),
387
+ errors
388
+ };
389
+ }
390
+ //#endregion
391
+ export { grasp as n, stratified as t };
392
+
393
+ //# sourceMappingURL=seeds-UNZxqm_U.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seeds-UNZxqm_U.js","names":[],"sources":["../src/seeds/grasp.ts","../src/seeds/stratified.ts"],"sourcesContent":["/**\n * GRASP — Graph-agnostic Representative Seed pAir Sampling.\n *\n * Novel blind structural seed selection algorithm that selects structurally\n * representative seed pairs without requiring domain knowledge or loading\n * the full graph into memory.\n *\n * Three phases:\n * 1. Reservoir sampling — stream edges, maintain reservoir of N nodes\n * 2. Structural feature computation — log-degree, clustering coeff, approx PageRank\n * 3. Mini-batch K-means clustering, then sample pairs within and across clusters\n *\n * @packageDocumentation\n */\n\nimport type { ReadableGraph, NodeId } from \"../graph\";\nimport type { Seed } from \"../schemas/index\";\nimport {\n\tminiBatchKMeans,\n\tzScoreNormalise,\n\ttype LabelledFeature,\n\ttype FeatureVector3D,\n} from \"../utils/kmeans\";\n\n/**\n * Configuration options for GRASP seed selection.\n */\nexport interface GraspOptions {\n\t/** Number of clusters for K-means (default: 100) */\n\treadonly nClusters?: number;\n\t/** Number of pairs to sample per cluster (default: 10) */\n\treadonly pairsPerCluster?: number;\n\t/** Ratio of within-cluster pairs vs cross-cluster pairs (default: 0.5) */\n\treadonly withinClusterRatio?: number;\n\t/** Reservoir sample size (default: 200000) */\n\treadonly sampleSize?: number;\n\t/** Random seed for reproducibility (default: 42) */\n\treadonly rngSeed?: number;\n\t/** Number of PageRank iterations for feature computation (default: 10) */\n\treadonly pagerankIterations?: number;\n}\n\n/**\n * A sampled seed pair with structural metadata.\n */\nexport interface GraspSeedPair {\n\t/** Source seed */\n\treadonly source: Seed;\n\t/** Target seed */\n\treadonly target: Seed;\n\t/** Euclidean distance in feature space */\n\treadonly featureDistance: number;\n\t/** Whether both seeds are from the same cluster */\n\treadonly sameCluster: boolean;\n\t/** Cluster index of source (or -1 if unclustered) */\n\treadonly sourceCluster: number;\n\t/** Cluster index of target (or -1 if unclustered) */\n\treadonly targetCluster: number;\n}\n\n/**\n * Result of GRASP seed selection.\n */\nexport interface GraspResult {\n\t/** Sampled seed pairs */\n\treadonly pairs: readonly GraspSeedPair[];\n\t/** Number of clusters used */\n\treadonly nClusters: number;\n\t/** Total nodes sampled */\n\treadonly sampledNodeCount: number;\n\t/** Features computed for sampled nodes */\n\treadonly features: readonly LabelledFeature[];\n\t/** Cluster assignments (nodeId -> cluster index) */\n\treadonly clusterAssignments: ReadonlyMap<string, number>;\n}\n\n/** Default configuration values */\nconst DEFAULTS = {\n\tnClusters: 100,\n\tpairsPerCluster: 10,\n\twithinClusterRatio: 0.5,\n\tsampleSize: 200000,\n\trngSeed: 42,\n\tpagerankIterations: 10,\n} as const;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Reservoir sampling (Vitter's Algorithm R) for streaming node selection.\n *\n * Maintains a uniform sample of nodes as edges are streamed, without\n * requiring the full graph in memory.\n */\nfunction reservoirSample(\n\tgraph: ReadableGraph,\n\tsampleSize: number,\n\trng: () => number,\n): { nodeIds: Set<NodeId>; neighbourMap: Map<NodeId, Set<NodeId>> } {\n\tconst reservoir: NodeId[] = [];\n\tconst neighbourMap = new Map<NodeId, Set<NodeId>>();\n\tconst inReservoir = new Set<NodeId>();\n\n\t// Track total nodes seen for reservoir probability\n\tlet nodesSeen = 0;\n\n\t// Stream all edges and sample nodes\n\tfor (const edge of graph.edges()) {\n\t\t// Process source node\n\t\tconst source = edge.source;\n\t\tif (!inReservoir.has(source)) {\n\t\t\tnodesSeen++;\n\t\t\tif (reservoir.length < sampleSize) {\n\t\t\t\treservoir.push(source);\n\t\t\t\tinReservoir.add(source);\n\t\t\t\tneighbourMap.set(source, new Set<NodeId>());\n\t\t\t} else {\n\t\t\t\t// Reservoir sampling: replace with probability sampleSize/nodesSeen\n\t\t\t\tconst j = Math.floor(rng() * nodesSeen);\n\t\t\t\tif (j < sampleSize) {\n\t\t\t\t\tconst oldNode = reservoir[j];\n\t\t\t\t\tif (oldNode !== undefined) {\n\t\t\t\t\t\tinReservoir.delete(oldNode);\n\t\t\t\t\t\tneighbourMap.delete(oldNode);\n\t\t\t\t\t}\n\t\t\t\t\treservoir[j] = source;\n\t\t\t\t\tinReservoir.add(source);\n\t\t\t\t\tneighbourMap.set(source, new Set<NodeId>());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Process target node\n\t\tconst target = edge.target;\n\t\tif (!inReservoir.has(target)) {\n\t\t\tnodesSeen++;\n\t\t\tif (reservoir.length < sampleSize) {\n\t\t\t\treservoir.push(target);\n\t\t\t\tinReservoir.add(target);\n\t\t\t\tneighbourMap.set(target, new Set<NodeId>());\n\t\t\t} else {\n\t\t\t\tconst j = Math.floor(rng() * nodesSeen);\n\t\t\t\tif (j < sampleSize) {\n\t\t\t\t\tconst oldNode = reservoir[j];\n\t\t\t\t\tif (oldNode !== undefined) {\n\t\t\t\t\t\tinReservoir.delete(oldNode);\n\t\t\t\t\t\tneighbourMap.delete(oldNode);\n\t\t\t\t\t}\n\t\t\t\t\treservoir[j] = target;\n\t\t\t\t\tinReservoir.add(target);\n\t\t\t\t\tneighbourMap.set(target, new Set<NodeId>());\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Store neighbour relationships for sampled nodes\n\t\tif (inReservoir.has(source) && inReservoir.has(target)) {\n\t\t\tconst sourceNeighbours = neighbourMap.get(source);\n\t\t\tconst targetNeighbours = neighbourMap.get(target);\n\t\t\tsourceNeighbours?.add(target);\n\t\t\ttargetNeighbours?.add(source);\n\t\t}\n\t}\n\n\treturn { nodeIds: inReservoir, neighbourMap };\n}\n\n/**\n * Compute approximate PageRank scores using power iteration on the reservoir subgraph.\n *\n * This is an approximation since it only considers the sampled nodes and their\n * connections within the reservoir, not the full graph.\n */\nfunction approximatePageRank(\n\tnodeIds: Set<NodeId>,\n\tneighbourMap: Map<NodeId, Set<NodeId>>,\n\titerations: number,\n\tdampingFactor = 0.85,\n): Map<NodeId, number> {\n\tconst n = nodeIds.size;\n\tif (n === 0) return new Map();\n\n\tconst nodeIdList = [...nodeIds];\n\tconst nodeIndex = new Map(nodeIdList.map((id, i) => [id, i] as const));\n\n\t// Initialise PageRank scores uniformly\n\tconst scores = new Float64Array(n).fill(1 / n);\n\tconst newScores = new Float64Array(n);\n\n\t// Power iteration\n\tfor (let iter = 0; iter < iterations; iter++) {\n\t\tnewScores.fill((1 - dampingFactor) / n);\n\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tconst nodeId = nodeIdList[i];\n\t\t\tif (nodeId === undefined) continue;\n\n\t\t\tconst neighbours = neighbourMap.get(nodeId);\n\t\t\tif (neighbours === undefined) continue;\n\n\t\t\tconst outDegree = neighbours.size;\n\t\t\tif (outDegree === 0) continue;\n\n\t\t\tconst contribution = (dampingFactor * (scores[i] ?? 0)) / outDegree;\n\n\t\t\tfor (const neighbour of neighbours) {\n\t\t\t\tconst neighbourIdx = nodeIndex.get(neighbour);\n\t\t\t\tif (neighbourIdx !== undefined) {\n\t\t\t\t\tnewScores[neighbourIdx] =\n\t\t\t\t\t\t(newScores[neighbourIdx] ?? 0) + contribution;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Swap buffers\n\t\tfor (let i = 0; i < n; i++) {\n\t\t\tscores[i] = newScores[i] ?? 0;\n\t\t}\n\t}\n\n\t// Build result map\n\tconst result = new Map<NodeId, number>();\n\tfor (let i = 0; i < n; i++) {\n\t\tconst nodeId = nodeIdList[i];\n\t\tconst score = scores[i];\n\t\tif (nodeId !== undefined && score !== undefined) {\n\t\t\tresult.set(nodeId, score);\n\t\t}\n\t}\n\n\treturn result;\n}\n\n/**\n * Compute structural features for sampled nodes.\n *\n * Features:\n * - f1: log(deg(v) + 1) — scale-normalised connectivity\n * - f2: clustering_coefficient(v) — local density\n * - f3: approx_pagerank(v) — positional importance\n */\nfunction computeFeatures(\n\tgraph: ReadableGraph,\n\tnodeIds: Set<NodeId>,\n\tneighbourMap: Map<NodeId, Set<NodeId>>,\n\tpagerankScores: Map<NodeId, number>,\n): LabelledFeature[] {\n\tconst features: LabelledFeature[] = [];\n\n\tfor (const nodeId of nodeIds) {\n\t\tconst degree = graph.degree(nodeId, \"both\");\n\t\tconst neighbours = neighbourMap.get(nodeId);\n\n\t\t// Compute local clustering coefficient using neighbour map\n\t\tlet clusteringCoef = 0;\n\t\tif (neighbours !== undefined && neighbours.size >= 2) {\n\t\t\tlet triangleCount = 0;\n\t\t\tconst neighbourList = [...neighbours];\n\n\t\t\tfor (let i = 0; i < neighbourList.length; i++) {\n\t\t\t\tfor (let j = i + 1; j < neighbourList.length; j++) {\n\t\t\t\t\tconst u = neighbourList[i];\n\t\t\t\t\tconst w = neighbourList[j];\n\t\t\t\t\tif (u !== undefined && w !== undefined) {\n\t\t\t\t\t\t// Check if u and w are connected\n\t\t\t\t\t\tconst uNeighbours = neighbourMap.get(u);\n\t\t\t\t\t\tif (uNeighbours?.has(w) === true) {\n\t\t\t\t\t\t\ttriangleCount++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst possibleTriangles = (degree * (degree - 1)) / 2;\n\t\t\tclusteringCoef = triangleCount / possibleTriangles;\n\t\t}\n\n\t\tconst pagerank = pagerankScores.get(nodeId) ?? 0;\n\n\t\tfeatures.push({\n\t\t\tnodeId,\n\t\t\tf1: Math.log(degree + 1),\n\t\t\tf2: clusteringCoef,\n\t\t\tf3: pagerank,\n\t\t});\n\t}\n\n\treturn features;\n}\n\n/**\n * Sample seed pairs from clusters.\n *\n * For each cluster, samples a mix of within-cluster and cross-cluster pairs.\n */\nfunction samplePairs(\n\tfeatures: readonly LabelledFeature[],\n\tclusterAssignments: ReadonlyMap<string, number>,\n\tnClusters: number,\n\tpairsPerCluster: number,\n\twithinClusterRatio: number,\n\trng: () => number,\n): GraspSeedPair[] {\n\tconst pairs: GraspSeedPair[] = [];\n\n\t// Group nodes by cluster\n\tconst clusterNodes = new Map<number, LabelledFeature[]>();\n\tfor (const feature of features) {\n\t\tconst cluster = clusterAssignments.get(feature.nodeId);\n\t\tif (cluster === undefined) continue;\n\n\t\tlet nodes = clusterNodes.get(cluster);\n\t\tif (nodes === undefined) {\n\t\t\tnodes = [];\n\t\t\tclusterNodes.set(cluster, nodes);\n\t\t}\n\t\tnodes.push(feature);\n\t}\n\n\tconst withinCount = Math.floor(pairsPerCluster * withinClusterRatio);\n\tconst crossCount = pairsPerCluster - withinCount;\n\n\t// Sample pairs for each cluster\n\tfor (let clusterIdx = 0; clusterIdx < nClusters; clusterIdx++) {\n\t\tconst nodes = clusterNodes.get(clusterIdx);\n\t\tif (nodes === undefined || nodes.length < 2) continue;\n\n\t\t// Within-cluster pairs\n\t\tfor (let i = 0; i < withinCount; i++) {\n\t\t\t// Sample two distinct nodes\n\t\t\tconst idx1 = Math.floor(rng() * nodes.length);\n\t\t\tlet idx2 = Math.floor(rng() * nodes.length);\n\t\t\twhile (idx1 === idx2) {\n\t\t\t\tidx2 = Math.floor(rng() * nodes.length);\n\t\t\t}\n\n\t\t\tconst source = nodes[idx1];\n\t\t\tconst target = nodes[idx2];\n\t\t\tif (source === undefined || target === undefined) continue;\n\n\t\t\tconst distance = computeFeatureDistance(source, target);\n\n\t\t\tpairs.push({\n\t\t\t\tsource: { id: source.nodeId },\n\t\t\t\ttarget: { id: target.nodeId },\n\t\t\t\tfeatureDistance: distance,\n\t\t\t\tsameCluster: true,\n\t\t\t\tsourceCluster: clusterIdx,\n\t\t\t\ttargetCluster: clusterIdx,\n\t\t\t});\n\t\t}\n\n\t\t// Cross-cluster pairs\n\t\tfor (let i = 0; i < crossCount; i++) {\n\t\t\tconst source = nodes[Math.floor(rng() * nodes.length)];\n\t\t\tif (source === undefined) continue;\n\n\t\t\t// Pick a random other cluster\n\t\t\tconst otherClusterIdx = Math.floor(rng() * nClusters);\n\t\t\tif (otherClusterIdx === clusterIdx) continue;\n\n\t\t\tconst otherNodes = clusterNodes.get(otherClusterIdx);\n\t\t\tif (otherNodes === undefined || otherNodes.length === 0) continue;\n\n\t\t\tconst target = otherNodes[Math.floor(rng() * otherNodes.length)];\n\t\t\tif (target === undefined) continue;\n\n\t\t\tconst distance = computeFeatureDistance(source, target);\n\n\t\t\tpairs.push({\n\t\t\t\tsource: { id: source.nodeId },\n\t\t\t\ttarget: { id: target.nodeId },\n\t\t\t\tfeatureDistance: distance,\n\t\t\t\tsameCluster: false,\n\t\t\t\tsourceCluster: clusterIdx,\n\t\t\t\ttargetCluster: otherClusterIdx,\n\t\t\t});\n\t\t}\n\t}\n\n\treturn pairs;\n}\n\n/**\n * Compute Euclidean distance between two feature vectors.\n */\nfunction computeFeatureDistance(\n\ta: FeatureVector3D,\n\tb: FeatureVector3D,\n): number {\n\tconst d1 = a.f1 - b.f1;\n\tconst d2 = a.f2 - b.f2;\n\tconst d3 = a.f3 - b.f3;\n\treturn Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);\n}\n\n/**\n * GRASP — Graph-agnostic Representative Seed pAir Sampling.\n *\n * Selects structurally representative seed pairs without domain knowledge.\n * The algorithm streams edges, samples nodes via reservoir sampling, computes\n * structural features, clusters nodes, and samples pairs within/across clusters.\n *\n * @param graph - The graph to sample seeds from\n * @param options - Configuration options\n * @returns Sampled seed pairs with structural metadata\n *\n * @example\n * ```typescript\n * const graph = new AdjacencyMapGraph();\n * // ... populate graph ...\n *\n * const result = grasp(graph, {\n * nClusters: 50,\n * pairsPerCluster: 20,\n * sampleSize: 100000,\n * });\n *\n * console.log(`Sampled ${result.pairs.length} pairs from ${result.sampledNodeCount} nodes`);\n * ```\n */\nexport function grasp(\n\tgraph: ReadableGraph,\n\toptions: GraspOptions = {},\n): GraspResult {\n\tconst config = {\n\t\t...DEFAULTS,\n\t\t...options,\n\t};\n\n\tconst rng = createRNG(config.rngSeed);\n\n\t// Phase 1: Reservoir sampling\n\tconst { nodeIds, neighbourMap } = reservoirSample(\n\t\tgraph,\n\t\tconfig.sampleSize,\n\t\trng,\n\t);\n\n\t// Phase 2: Approximate PageRank on reservoir subgraph\n\tconst pagerankScores = approximatePageRank(\n\t\tnodeIds,\n\t\tneighbourMap,\n\t\tconfig.pagerankIterations,\n\t);\n\n\t// Phase 2: Compute structural features\n\tlet features = computeFeatures(graph, nodeIds, neighbourMap, pagerankScores);\n\n\t// Normalise features\n\tif (features.length > 0) {\n\t\tfeatures = zScoreNormalise(features);\n\t}\n\n\t// Phase 3: K-means clustering\n\tconst k = Math.min(config.nClusters, features.length);\n\tconst kmeansResult = miniBatchKMeans(features, {\n\t\tk,\n\t\tseed: config.rngSeed,\n\t\tmaxIterations: 100,\n\t});\n\n\t// Phase 3: Sample pairs\n\tconst pairs = samplePairs(\n\t\tfeatures,\n\t\tkmeansResult.assignments,\n\t\tkmeansResult.k,\n\t\tconfig.pairsPerCluster,\n\t\tconfig.withinClusterRatio,\n\t\trng,\n\t);\n\n\treturn {\n\t\tpairs,\n\t\tnClusters: kmeansResult.k,\n\t\tsampledNodeCount: nodeIds.size,\n\t\tfeatures,\n\t\tclusterAssignments: kmeansResult.assignments,\n\t};\n}\n","/**\n * Stratified seed selection — legacy human-defined strata.\n *\n * Requires user-provided field/type classifications. This is included for comparison\n * and for users who have domain metadata.\n *\n * @packageDocumentation\n */\n\nimport type { ReadableGraph, NodeId } from \"../graph/index\";\nimport type { Seed } from \"../schemas/index\";\n\n/**\n * Field classification function type.\n * User provides a function that returns the field name for a node.\n */\nexport type FieldClassifier = (node: {\n\tid: NodeId;\n\ttype?: string;\n}) => string | undefined;\n\n/**\n * Stratum definition for seed pair selection.\n */\nexport interface StratumDefinition {\n\treadonly name: string;\n\treadonly description: string;\n\treadonly predicate: (\n\t\tsource: { id: NodeId; type?: string },\n\t\ttarget: { id: NodeId; type?: string },\n\t) => boolean;\n}\n\n/**\n * Stratum with sampled seed pairs.\n */\nexport interface StratumResult {\n\treadonly name: string;\n\treadonly pairs: readonly SeedPair[];\n}\n\n/**\n * A seed pair with stratum metadata.\n */\nexport interface SeedPair {\n\treadonly source: Seed;\n\treadonly target: Seed;\n\treadonly stratum: string;\n\treadonly sameField: boolean;\n}\n\n/**\n * Result of stratified seed selection.\n */\nexport interface StratifiedResult {\n\treadonly strata: readonly StratumResult[];\n\treadonly totalPairs: number;\n\treadonly errors: readonly Error[];\n}\n\n/**\n * Configuration for stratified seed selection.\n */\nexport interface StratifiedOptions {\n\t/** Function to classify nodes by field */\n\treadonly fieldClassifier: FieldClassifier;\n\t/** Number of pairs to sample per stratum */\n\treadonly pairsPerStratum?: number;\n\t/** Random seed for reproducibility */\n\treadonly rngSeed?: number;\n\t/** Custom stratum definitions */\n\treadonly customStrata?: readonly StratumDefinition[];\n}\n\n/** Default values */\nconst DEFAULTS = {\n\tpairsPerStratum: 10,\n\trngSeed: 42,\n} as const;\n\n/**\n * Simple seeded pseudo-random number generator using mulberry32.\n */\nfunction createRNG(seed: number): () => number {\n\tlet state = seed >>> 0;\n\treturn (): number => {\n\t\tstate = (state + 0x6d2b79f5) >>> 0;\n\t\tlet t = Math.imul(state ^ (state >>> 15), state | 1);\n\t\tt = (t ^ (t >>> 7)) * (t | 0x61c88647);\n\t\treturn ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n\t};\n}\n\n/**\n * Stratified seed selection algorithm.\n *\n * @param graph - The graph to sample seeds from\n * @param options - Configuration options including field classifier\n * @returns Stratified selection result\n *\n * @example\n * ```typescript\n * const graph = new AdjacencyMapGraph();\n * // ... populate graph ...\n *\n * const result = stratified(graph, {\n * fieldClassifier: (node) => node.type === 'paper' ? 'computer-science' : undefined,\n * pairsPerStratum: 20,\n * });\n *\n * for (const stratum of result.strata) {\n * console.log(`${stratum.name}: ${stratum.pairs.length} pairs`);\n * }\n * ```\n */\nexport function stratified(\n\tgraph: ReadableGraph,\n\toptions: StratifiedOptions,\n): StratifiedResult {\n\tconst {\n\t\tfieldClassifier,\n\t\tpairsPerStratum = DEFAULTS.pairsPerStratum,\n\t\trngSeed = DEFAULTS.rngSeed,\n\t\tcustomStrata,\n\t} = options;\n\n\tconst rng = createRNG(rngSeed);\n\tconst strataDefinitions = customStrata ?? [];\n\n\t// Collect all nodes with their field classifications\n\tconst nodesWithFields: { id: NodeId; type?: string; field?: string }[] = [];\n\n\tfor (const nodeId of graph.nodeIds()) {\n\t\tconst node = graph.getNode(nodeId);\n\t\tif (node === undefined) continue;\n\n\t\tconst field = fieldClassifier({ id: nodeId, type: node.type });\n\t\tif (field === undefined) continue;\n\n\t\tnodesWithFields.push({ id: nodeId, type: node.type, field });\n\t}\n\n\tconst errors: Error[] = [];\n\tconst strataResults: StratumResult[] = [];\n\n\t// Process each stratum\n\tfor (const stratum of strataDefinitions) {\n\t\tconst pairs: SeedPair[] = [];\n\t\tconst eligiblePairs: {\n\t\t\tsource: { id: NodeId; type?: string };\n\t\t\ttarget: { id: NodeId; type?: string };\n\t\t}[] = [];\n\n\t\t// Find all node pairs that match this stratum\n\t\tfor (let i = 0; i < nodesWithFields.length; i++) {\n\t\t\tconst source = nodesWithFields[i];\n\t\t\tif (source === undefined) continue;\n\n\t\t\tfor (let j = i + 1; j < nodesWithFields.length; j++) {\n\t\t\t\tif (j === i) continue;\n\t\t\t\tconst target = nodesWithFields[j];\n\t\t\t\tif (target === undefined) continue;\n\n\t\t\t\tif (stratum.predicate(source, target)) {\n\t\t\t\t\teligiblePairs.push({ source, target });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Sample pairs from eligible pairs\n\t\tconst numToSample = Math.min(pairsPerStratum, eligiblePairs.length);\n\t\tfor (let i = 0; i < numToSample; i++) {\n\t\t\tconst idx = Math.floor(rng() * eligiblePairs.length);\n\t\t\tconst pair = eligiblePairs[idx];\n\t\t\tif (pair === undefined) continue;\n\n\t\t\tconst sourceField = fieldClassifier(pair.source);\n\t\t\tconst targetField = fieldClassifier(pair.target);\n\n\t\t\tpairs.push({\n\t\t\t\tsource: { id: pair.source.id },\n\t\t\t\ttarget: { id: pair.target.id },\n\t\t\t\tstratum: stratum.name,\n\t\t\t\tsameField: sourceField === targetField,\n\t\t\t});\n\t\t}\n\n\t\tstrataResults.push({\n\t\t\tname: stratum.name,\n\t\t\tpairs,\n\t\t});\n\t}\n\n\t// Collect errors for empty strata\n\tfor (const stratum of strataDefinitions) {\n\t\tconst result = strataResults.find((r) => r.name === stratum.name);\n\t\tif (result === undefined || result.pairs.length === 0) {\n\t\t\terrors.push(new Error(`No pairs found for stratum: ${stratum.name}`));\n\t\t}\n\t}\n\n\tconst totalPairs = strataResults.reduce((sum, r) => sum + r.pairs.length, 0);\n\n\treturn {\n\t\tstrata: strataResults,\n\t\ttotalPairs,\n\t\terrors,\n\t};\n}\n"],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_structures = require("../structures-CJ_S_7fs.cjs");
3
+ exports.PriorityQueue = require_structures.PriorityQueue;
@@ -0,0 +1,3 @@
1
+ export { PriorityQueue } from './priority-queue';
2
+ export type { PriorityEntry } from './priority-queue';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/structures/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,YAAY,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import { t as PriorityQueue } from "../structures-BPfhfqNP.js";
2
+ export { PriorityQueue };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Priority queue entry containing an item and its priority.
3
+ */
4
+ export interface PriorityEntry<T> {
5
+ item: T;
6
+ priority: number;
7
+ }
8
+ /**
9
+ * Min-heap priority queue implementation using an array-based binary heap.
10
+ * Lower priority values have higher precedence (extracted first).
11
+ */
12
+ export declare class PriorityQueue<T> {
13
+ private heap;
14
+ /**
15
+ * Returns the number of items in the queue.
16
+ */
17
+ size(): number;
18
+ /**
19
+ * Returns true if the queue is empty.
20
+ */
21
+ isEmpty(): boolean;
22
+ /**
23
+ * Adds an item with the given priority to the queue.
24
+ */
25
+ push(item: T, priority: number): void;
26
+ /**
27
+ * Removes and returns the highest precedence item (lowest priority value).
28
+ * Returns undefined if the queue is empty.
29
+ */
30
+ pop(): PriorityEntry<T> | undefined;
31
+ /**
32
+ * Returns the highest precedence item without removing it.
33
+ * Returns undefined if the queue is empty.
34
+ */
35
+ peek(): PriorityEntry<T> | undefined;
36
+ /**
37
+ * Rebuilds the heap from the current array state.
38
+ * Useful when priorities have been modified externally (e.g., phase transitions).
39
+ */
40
+ rebuild(): void;
41
+ /**
42
+ * Decreases the priority of an existing item in the queue.
43
+ * Returns true if the item was found and updated, false otherwise.
44
+ *
45
+ * @param item - The item to find
46
+ * @param newPriority - The new (lower) priority value
47
+ * @param equals - Function to compare items for equality
48
+ */
49
+ decreaseKey(item: T, newPriority: number, equals: (a: T, b: T) => boolean): boolean;
50
+ /**
51
+ * Restores heap property by moving element up from given index.
52
+ */
53
+ private heapifyUp;
54
+ /**
55
+ * Restores heap property by moving element down from given index.
56
+ */
57
+ private heapifyDown;
58
+ }
59
+ //# sourceMappingURL=priority-queue.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"priority-queue.d.ts","sourceRoot":"","sources":["../../src/structures/priority-queue.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC;IAC/B,IAAI,EAAE,CAAC,CAAC;IACR,QAAQ,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,qBAAa,aAAa,CAAC,CAAC;IAC3B,OAAO,CAAC,IAAI,CAA0B;IAEtC;;OAEG;IACI,IAAI,IAAI,MAAM;IAIrB;;OAEG;IACI,OAAO,IAAI,OAAO;IAIzB;;OAEG;IACI,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IAM5C;;;OAGG;IACI,GAAG,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS;IAuB1C;;;OAGG;IACI,IAAI,IAAI,aAAa,CAAC,CAAC,CAAC,GAAG,SAAS;IAI3C;;;OAGG;IACI,OAAO,IAAI,IAAI;IAQtB;;;;;;;OAOG;IACI,WAAW,CACjB,IAAI,EAAE,CAAC,EACP,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,OAAO,GAC7B,OAAO;IA8BV;;OAEG;IACH,OAAO,CAAC,SAAS;IAuBjB;;OAEG;IACH,OAAO,CAAC,WAAW;CAmDnB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=priority-queue.unit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"priority-queue.unit.test.d.ts","sourceRoot":"","sources":["../../src/structures/priority-queue.unit.test.ts"],"names":[],"mappings":""}