graphwise 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/expansion/frontier-balanced.d.ts +12 -0
  2. package/dist/expansion/frontier-balanced.d.ts.map +1 -0
  3. package/dist/expansion/frontier-balanced.unit.test.d.ts +2 -0
  4. package/dist/expansion/frontier-balanced.unit.test.d.ts.map +1 -0
  5. package/dist/expansion/index.d.ts +12 -13
  6. package/dist/expansion/index.d.ts.map +1 -1
  7. package/dist/expansion/random-priority.d.ts +20 -0
  8. package/dist/expansion/random-priority.d.ts.map +1 -0
  9. package/dist/expansion/random-priority.unit.test.d.ts +2 -0
  10. package/dist/expansion/random-priority.unit.test.d.ts.map +1 -0
  11. package/dist/expansion/standard-bfs.d.ts +12 -0
  12. package/dist/expansion/standard-bfs.d.ts.map +1 -0
  13. package/dist/expansion/standard-bfs.unit.test.d.ts +2 -0
  14. package/dist/expansion/standard-bfs.unit.test.d.ts.map +1 -0
  15. package/dist/extraction/index.d.ts +6 -6
  16. package/dist/extraction/index.d.ts.map +1 -1
  17. package/dist/extraction/motif.d.ts.map +1 -1
  18. package/dist/gpu/context.d.ts.map +1 -1
  19. package/dist/gpu/csr.d.ts.map +1 -1
  20. package/dist/gpu/index.cjs +410 -5
  21. package/dist/gpu/index.cjs.map +1 -0
  22. package/dist/gpu/index.d.ts +4 -5
  23. package/dist/gpu/index.d.ts.map +1 -1
  24. package/dist/gpu/index.js +400 -2
  25. package/dist/gpu/index.js.map +1 -0
  26. package/dist/graph/index.cjs +222 -2
  27. package/dist/graph/index.cjs.map +1 -0
  28. package/dist/graph/index.d.ts +3 -3
  29. package/dist/graph/index.d.ts.map +1 -1
  30. package/dist/graph/index.js +221 -1
  31. package/dist/graph/index.js.map +1 -0
  32. package/dist/index/index.cjs +902 -10
  33. package/dist/index/index.cjs.map +1 -1
  34. package/dist/index/index.js +880 -10
  35. package/dist/index/index.js.map +1 -1
  36. package/dist/{kmeans-B0HEOU6k.cjs → kmeans-87ExSUNZ.js} +27 -13
  37. package/dist/{kmeans-DgbsOznU.js.map → kmeans-87ExSUNZ.js.map} +1 -1
  38. package/dist/{kmeans-DgbsOznU.js → kmeans-BIgSyGKu.cjs} +44 -2
  39. package/dist/{kmeans-B0HEOU6k.cjs.map → kmeans-BIgSyGKu.cjs.map} +1 -1
  40. package/dist/ranking/baselines/betweenness.d.ts +13 -0
  41. package/dist/ranking/baselines/betweenness.d.ts.map +1 -0
  42. package/dist/ranking/baselines/betweenness.unit.test.d.ts +2 -0
  43. package/dist/ranking/baselines/betweenness.unit.test.d.ts.map +1 -0
  44. package/dist/ranking/baselines/communicability.d.ts +13 -0
  45. package/dist/ranking/baselines/communicability.d.ts.map +1 -0
  46. package/dist/ranking/baselines/communicability.unit.test.d.ts +2 -0
  47. package/dist/ranking/baselines/communicability.unit.test.d.ts.map +1 -0
  48. package/dist/ranking/baselines/degree-sum.d.ts +13 -0
  49. package/dist/ranking/baselines/degree-sum.d.ts.map +1 -0
  50. package/dist/ranking/baselines/degree-sum.unit.test.d.ts +2 -0
  51. package/dist/ranking/baselines/degree-sum.unit.test.d.ts.map +1 -0
  52. package/dist/ranking/baselines/index.d.ts +20 -0
  53. package/dist/ranking/baselines/index.d.ts.map +1 -0
  54. package/dist/ranking/baselines/jaccard-arithmetic.d.ts +13 -0
  55. package/dist/ranking/baselines/jaccard-arithmetic.d.ts.map +1 -0
  56. package/dist/ranking/baselines/jaccard-arithmetic.unit.test.d.ts +2 -0
  57. package/dist/ranking/baselines/jaccard-arithmetic.unit.test.d.ts.map +1 -0
  58. package/dist/ranking/baselines/katz.d.ts +13 -0
  59. package/dist/ranking/baselines/katz.d.ts.map +1 -0
  60. package/dist/ranking/baselines/katz.unit.test.d.ts +2 -0
  61. package/dist/ranking/baselines/katz.unit.test.d.ts.map +1 -0
  62. package/dist/ranking/baselines/pagerank.d.ts +13 -0
  63. package/dist/ranking/baselines/pagerank.d.ts.map +1 -0
  64. package/dist/ranking/baselines/pagerank.unit.test.d.ts +2 -0
  65. package/dist/ranking/baselines/pagerank.unit.test.d.ts.map +1 -0
  66. package/dist/ranking/baselines/random-ranking.d.ts +21 -0
  67. package/dist/ranking/baselines/random-ranking.d.ts.map +1 -0
  68. package/dist/ranking/baselines/random-ranking.unit.test.d.ts +2 -0
  69. package/dist/ranking/baselines/random-ranking.unit.test.d.ts.map +1 -0
  70. package/dist/ranking/baselines/resistance-distance.d.ts +13 -0
  71. package/dist/ranking/baselines/resistance-distance.d.ts.map +1 -0
  72. package/dist/ranking/baselines/resistance-distance.unit.test.d.ts +2 -0
  73. package/dist/ranking/baselines/resistance-distance.unit.test.d.ts.map +1 -0
  74. package/dist/ranking/baselines/widest-path.d.ts +13 -0
  75. package/dist/ranking/baselines/widest-path.d.ts.map +1 -0
  76. package/dist/ranking/baselines/widest-path.unit.test.d.ts +2 -0
  77. package/dist/ranking/baselines/widest-path.unit.test.d.ts.map +1 -0
  78. package/dist/ranking/index.d.ts +3 -6
  79. package/dist/ranking/index.d.ts.map +1 -1
  80. package/dist/ranking/mi/index.d.ts +9 -9
  81. package/dist/ranking/mi/index.d.ts.map +1 -1
  82. package/dist/schemas/index.d.ts +2 -2
  83. package/dist/schemas/index.d.ts.map +1 -1
  84. package/dist/seeds/index.cjs +398 -3
  85. package/dist/seeds/index.cjs.map +1 -0
  86. package/dist/seeds/index.d.ts +2 -4
  87. package/dist/seeds/index.d.ts.map +1 -1
  88. package/dist/seeds/index.js +396 -1
  89. package/dist/seeds/index.js.map +1 -0
  90. package/dist/seeds/stratified.d.ts.map +1 -1
  91. package/dist/structures/index.cjs +133 -2
  92. package/dist/structures/index.cjs.map +1 -0
  93. package/dist/structures/index.d.ts +1 -2
  94. package/dist/structures/index.d.ts.map +1 -1
  95. package/dist/structures/index.js +132 -1
  96. package/dist/structures/index.js.map +1 -0
  97. package/dist/traversal/index.cjs +152 -5
  98. package/dist/traversal/index.cjs.map +1 -0
  99. package/dist/traversal/index.d.ts +2 -2
  100. package/dist/traversal/index.d.ts.map +1 -1
  101. package/dist/traversal/index.js +148 -1
  102. package/dist/traversal/index.js.map +1 -0
  103. package/dist/utils/index.cjs +172 -9
  104. package/dist/utils/index.cjs.map +1 -0
  105. package/dist/utils/index.d.ts +3 -3
  106. package/dist/utils/index.d.ts.map +1 -1
  107. package/dist/utils/index.js +165 -3
  108. package/dist/utils/index.js.map +1 -0
  109. package/package.json +1 -1
  110. package/dist/gpu-BJRVYBjx.cjs +0 -338
  111. package/dist/gpu-BJRVYBjx.cjs.map +0 -1
  112. package/dist/gpu-BveuXugy.js +0 -315
  113. package/dist/gpu-BveuXugy.js.map +0 -1
  114. package/dist/graph-DLWiziLB.js +0 -222
  115. package/dist/graph-DLWiziLB.js.map +0 -1
  116. package/dist/graph-az06J1YV.cjs +0 -227
  117. package/dist/graph-az06J1YV.cjs.map +0 -1
  118. package/dist/seeds-B6J9oJfU.cjs +0 -404
  119. package/dist/seeds-B6J9oJfU.cjs.map +0 -1
  120. package/dist/seeds-UNZxqm_U.js +0 -393
  121. package/dist/seeds-UNZxqm_U.js.map +0 -1
  122. package/dist/structures-BPfhfqNP.js +0 -133
  123. package/dist/structures-BPfhfqNP.js.map +0 -1
  124. package/dist/structures-CJ_S_7fs.cjs +0 -138
  125. package/dist/structures-CJ_S_7fs.cjs.map +0 -1
  126. package/dist/traversal-CQCjUwUJ.js +0 -149
  127. package/dist/traversal-CQCjUwUJ.js.map +0 -1
  128. package/dist/traversal-QeHaNUWn.cjs +0 -172
  129. package/dist/traversal-QeHaNUWn.cjs.map +0 -1
  130. package/dist/utils-Q_akvlMn.js +0 -164
  131. package/dist/utils-Q_akvlMn.js.map +0 -1
  132. package/dist/utils-spZa1ZvS.cjs +0 -205
  133. package/dist/utils-spZa1ZvS.cjs.map +0 -1
@@ -1,4 +1,399 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_seeds = require("../seeds-B6J9oJfU.cjs");
3
- exports.grasp = require_seeds.grasp;
4
- exports.stratified = require_seeds.stratified;
2
+ const require_kmeans = require("../kmeans-BIgSyGKu.cjs");
3
+ //#region src/seeds/grasp.ts
4
+ /** Default configuration values */
5
+ var DEFAULTS$1 = {
6
+ nClusters: 100,
7
+ pairsPerCluster: 10,
8
+ withinClusterRatio: .5,
9
+ sampleSize: 2e5,
10
+ rngSeed: 42,
11
+ pagerankIterations: 10
12
+ };
13
+ /**
14
+ * Simple seeded pseudo-random number generator using mulberry32.
15
+ */
16
+ function createRNG$1(seed) {
17
+ let state = seed >>> 0;
18
+ return () => {
19
+ state = state + 1831565813 >>> 0;
20
+ let t = Math.imul(state ^ state >>> 15, state | 1);
21
+ t = (t ^ t >>> 7) * (t | 1640531527);
22
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
23
+ };
24
+ }
25
+ /**
26
+ * Reservoir sampling (Vitter's Algorithm R) for streaming node selection.
27
+ *
28
+ * Maintains a uniform sample of nodes as edges are streamed, without
29
+ * requiring the full graph in memory.
30
+ */
31
+ function reservoirSample(graph, sampleSize, rng) {
32
+ const reservoir = [];
33
+ const neighbourMap = /* @__PURE__ */ new Map();
34
+ const inReservoir = /* @__PURE__ */ new Set();
35
+ let nodesSeen = 0;
36
+ for (const edge of graph.edges()) {
37
+ const source = edge.source;
38
+ if (!inReservoir.has(source)) {
39
+ nodesSeen++;
40
+ if (reservoir.length < sampleSize) {
41
+ reservoir.push(source);
42
+ inReservoir.add(source);
43
+ neighbourMap.set(source, /* @__PURE__ */ new Set());
44
+ } else {
45
+ const j = Math.floor(rng() * nodesSeen);
46
+ if (j < sampleSize) {
47
+ const oldNode = reservoir[j];
48
+ if (oldNode !== void 0) {
49
+ inReservoir.delete(oldNode);
50
+ neighbourMap.delete(oldNode);
51
+ }
52
+ reservoir[j] = source;
53
+ inReservoir.add(source);
54
+ neighbourMap.set(source, /* @__PURE__ */ new Set());
55
+ }
56
+ }
57
+ }
58
+ const target = edge.target;
59
+ if (!inReservoir.has(target)) {
60
+ nodesSeen++;
61
+ if (reservoir.length < sampleSize) {
62
+ reservoir.push(target);
63
+ inReservoir.add(target);
64
+ neighbourMap.set(target, /* @__PURE__ */ new Set());
65
+ } else {
66
+ const j = Math.floor(rng() * nodesSeen);
67
+ if (j < sampleSize) {
68
+ const oldNode = reservoir[j];
69
+ if (oldNode !== void 0) {
70
+ inReservoir.delete(oldNode);
71
+ neighbourMap.delete(oldNode);
72
+ }
73
+ reservoir[j] = target;
74
+ inReservoir.add(target);
75
+ neighbourMap.set(target, /* @__PURE__ */ new Set());
76
+ }
77
+ }
78
+ }
79
+ if (inReservoir.has(source) && inReservoir.has(target)) {
80
+ const sourceNeighbours = neighbourMap.get(source);
81
+ const targetNeighbours = neighbourMap.get(target);
82
+ sourceNeighbours?.add(target);
83
+ targetNeighbours?.add(source);
84
+ }
85
+ }
86
+ return {
87
+ nodeIds: inReservoir,
88
+ neighbourMap
89
+ };
90
+ }
91
+ /**
92
+ * Compute approximate PageRank scores using power iteration on the reservoir subgraph.
93
+ *
94
+ * This is an approximation since it only considers the sampled nodes and their
95
+ * connections within the reservoir, not the full graph.
96
+ */
97
+ function approximatePageRank(nodeIds, neighbourMap, iterations, dampingFactor = .85) {
98
+ const n = nodeIds.size;
99
+ if (n === 0) return /* @__PURE__ */ new Map();
100
+ const nodeIdList = [...nodeIds];
101
+ const nodeIndex = new Map(nodeIdList.map((id, i) => [id, i]));
102
+ const scores = new Float64Array(n).fill(1 / n);
103
+ const newScores = new Float64Array(n);
104
+ for (let iter = 0; iter < iterations; iter++) {
105
+ newScores.fill((1 - dampingFactor) / n);
106
+ for (let i = 0; i < n; i++) {
107
+ const nodeId = nodeIdList[i];
108
+ if (nodeId === void 0) continue;
109
+ const neighbours = neighbourMap.get(nodeId);
110
+ if (neighbours === void 0) continue;
111
+ const outDegree = neighbours.size;
112
+ if (outDegree === 0) continue;
113
+ const contribution = dampingFactor * (scores[i] ?? 0) / outDegree;
114
+ for (const neighbour of neighbours) {
115
+ const neighbourIdx = nodeIndex.get(neighbour);
116
+ if (neighbourIdx !== void 0) newScores[neighbourIdx] = (newScores[neighbourIdx] ?? 0) + contribution;
117
+ }
118
+ }
119
+ for (let i = 0; i < n; i++) scores[i] = newScores[i] ?? 0;
120
+ }
121
+ const result = /* @__PURE__ */ new Map();
122
+ for (let i = 0; i < n; i++) {
123
+ const nodeId = nodeIdList[i];
124
+ const score = scores[i];
125
+ if (nodeId !== void 0 && score !== void 0) result.set(nodeId, score);
126
+ }
127
+ return result;
128
+ }
129
+ /**
130
+ * Compute structural features for sampled nodes.
131
+ *
132
+ * Features:
133
+ * - f1: log(deg(v) + 1) — scale-normalised connectivity
134
+ * - f2: clustering_coefficient(v) — local density
135
+ * - f3: approx_pagerank(v) — positional importance
136
+ */
137
+ function computeFeatures(graph, nodeIds, neighbourMap, pagerankScores) {
138
+ const features = [];
139
+ for (const nodeId of nodeIds) {
140
+ const degree = graph.degree(nodeId, "both");
141
+ const neighbours = neighbourMap.get(nodeId);
142
+ let clusteringCoef = 0;
143
+ if (neighbours !== void 0 && neighbours.size >= 2) {
144
+ let triangleCount = 0;
145
+ const neighbourList = [...neighbours];
146
+ for (let i = 0; i < neighbourList.length; i++) for (let j = i + 1; j < neighbourList.length; j++) {
147
+ const u = neighbourList[i];
148
+ const w = neighbourList[j];
149
+ if (u !== void 0 && w !== void 0) {
150
+ if (neighbourMap.get(u)?.has(w) === true) triangleCount++;
151
+ }
152
+ }
153
+ const possibleTriangles = degree * (degree - 1) / 2;
154
+ clusteringCoef = triangleCount / possibleTriangles;
155
+ }
156
+ const pagerank = pagerankScores.get(nodeId) ?? 0;
157
+ features.push({
158
+ nodeId,
159
+ f1: Math.log(degree + 1),
160
+ f2: clusteringCoef,
161
+ f3: pagerank
162
+ });
163
+ }
164
+ return features;
165
+ }
166
+ /**
167
+ * Sample seed pairs from clusters.
168
+ *
169
+ * For each cluster, samples a mix of within-cluster and cross-cluster pairs.
170
+ */
171
+ function samplePairs(features, clusterAssignments, nClusters, pairsPerCluster, withinClusterRatio, rng) {
172
+ const pairs = [];
173
+ const clusterNodes = /* @__PURE__ */ new Map();
174
+ for (const feature of features) {
175
+ const cluster = clusterAssignments.get(feature.nodeId);
176
+ if (cluster === void 0) continue;
177
+ let nodes = clusterNodes.get(cluster);
178
+ if (nodes === void 0) {
179
+ nodes = [];
180
+ clusterNodes.set(cluster, nodes);
181
+ }
182
+ nodes.push(feature);
183
+ }
184
+ const withinCount = Math.floor(pairsPerCluster * withinClusterRatio);
185
+ const crossCount = pairsPerCluster - withinCount;
186
+ for (let clusterIdx = 0; clusterIdx < nClusters; clusterIdx++) {
187
+ const nodes = clusterNodes.get(clusterIdx);
188
+ if (nodes === void 0 || nodes.length < 2) continue;
189
+ for (let i = 0; i < withinCount; i++) {
190
+ const idx1 = Math.floor(rng() * nodes.length);
191
+ let idx2 = Math.floor(rng() * nodes.length);
192
+ while (idx1 === idx2) idx2 = Math.floor(rng() * nodes.length);
193
+ const source = nodes[idx1];
194
+ const target = nodes[idx2];
195
+ if (source === void 0 || target === void 0) continue;
196
+ const distance = computeFeatureDistance(source, target);
197
+ pairs.push({
198
+ source: { id: source.nodeId },
199
+ target: { id: target.nodeId },
200
+ featureDistance: distance,
201
+ sameCluster: true,
202
+ sourceCluster: clusterIdx,
203
+ targetCluster: clusterIdx
204
+ });
205
+ }
206
+ for (let i = 0; i < crossCount; i++) {
207
+ const source = nodes[Math.floor(rng() * nodes.length)];
208
+ if (source === void 0) continue;
209
+ const otherClusterIdx = Math.floor(rng() * nClusters);
210
+ if (otherClusterIdx === clusterIdx) continue;
211
+ const otherNodes = clusterNodes.get(otherClusterIdx);
212
+ if (otherNodes === void 0 || otherNodes.length === 0) continue;
213
+ const target = otherNodes[Math.floor(rng() * otherNodes.length)];
214
+ if (target === void 0) continue;
215
+ const distance = computeFeatureDistance(source, target);
216
+ pairs.push({
217
+ source: { id: source.nodeId },
218
+ target: { id: target.nodeId },
219
+ featureDistance: distance,
220
+ sameCluster: false,
221
+ sourceCluster: clusterIdx,
222
+ targetCluster: otherClusterIdx
223
+ });
224
+ }
225
+ }
226
+ return pairs;
227
+ }
228
+ /**
229
+ * Compute Euclidean distance between two feature vectors.
230
+ */
231
+ function computeFeatureDistance(a, b) {
232
+ const d1 = a.f1 - b.f1;
233
+ const d2 = a.f2 - b.f2;
234
+ const d3 = a.f3 - b.f3;
235
+ return Math.sqrt(d1 * d1 + d2 * d2 + d3 * d3);
236
+ }
237
+ /**
238
+ * GRASP — Graph-agnostic Representative Seed pAir Sampling.
239
+ *
240
+ * Selects structurally representative seed pairs without domain knowledge.
241
+ * The algorithm streams edges, samples nodes via reservoir sampling, computes
242
+ * structural features, clusters nodes, and samples pairs within/across clusters.
243
+ *
244
+ * @param graph - The graph to sample seeds from
245
+ * @param options - Configuration options
246
+ * @returns Sampled seed pairs with structural metadata
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * const graph = new AdjacencyMapGraph();
251
+ * // ... populate graph ...
252
+ *
253
+ * const result = grasp(graph, {
254
+ * nClusters: 50,
255
+ * pairsPerCluster: 20,
256
+ * sampleSize: 100000,
257
+ * });
258
+ *
259
+ * console.log(`Sampled ${result.pairs.length} pairs from ${result.sampledNodeCount} nodes`);
260
+ * ```
261
+ */
262
+ function grasp(graph, options = {}) {
263
+ const config = {
264
+ ...DEFAULTS$1,
265
+ ...options
266
+ };
267
+ const rng = createRNG$1(config.rngSeed);
268
+ const { nodeIds, neighbourMap } = reservoirSample(graph, config.sampleSize, rng);
269
+ let features = computeFeatures(graph, nodeIds, neighbourMap, approximatePageRank(nodeIds, neighbourMap, config.pagerankIterations));
270
+ if (features.length > 0) features = require_kmeans.normaliseFeatures(features);
271
+ const k = Math.min(config.nClusters, features.length);
272
+ const kmeansResult = require_kmeans.miniBatchKMeans(features, {
273
+ k,
274
+ seed: config.rngSeed,
275
+ maxIterations: 100
276
+ });
277
+ return {
278
+ pairs: samplePairs(features, kmeansResult.assignments, kmeansResult.k, config.pairsPerCluster, config.withinClusterRatio, rng),
279
+ nClusters: kmeansResult.k,
280
+ sampledNodeCount: nodeIds.size,
281
+ features,
282
+ clusterAssignments: kmeansResult.assignments
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region src/seeds/stratified.ts
287
+ /** Default values */
288
+ var DEFAULTS = {
289
+ pairsPerStratum: 10,
290
+ rngSeed: 42
291
+ };
292
+ /**
293
+ * Simple seeded pseudo-random number generator using mulberry32.
294
+ */
295
+ function createRNG(seed) {
296
+ let state = seed >>> 0;
297
+ return () => {
298
+ state = state + 1831565813 >>> 0;
299
+ let t = Math.imul(state ^ state >>> 15, state | 1);
300
+ t = (t ^ t >>> 7) * (t | 1640531527);
301
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
302
+ };
303
+ }
304
+ /**
305
+ * Stratified seed selection algorithm.
306
+ *
307
+ * @param graph - The graph to sample seeds from
308
+ * @param options - Configuration options including field classifier
309
+ * @returns Stratified selection result
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const graph = new AdjacencyMapGraph();
314
+ * // ... populate graph ...
315
+ *
316
+ * const result = stratified(graph, {
317
+ * fieldClassifier: (node) => node.type === 'paper' ? 'computer-science' : undefined,
318
+ * pairsPerStratum: 20,
319
+ * });
320
+ *
321
+ * for (const stratum of result.strata) {
322
+ * console.log(`${stratum.name}: ${stratum.pairs.length} pairs`);
323
+ * }
324
+ * ```
325
+ */
326
+ function stratified(graph, options) {
327
+ const { fieldClassifier, pairsPerStratum = DEFAULTS.pairsPerStratum, rngSeed = DEFAULTS.rngSeed, customStrata } = options;
328
+ const rng = createRNG(rngSeed);
329
+ const strataDefinitions = customStrata ?? [];
330
+ const nodesWithFields = [];
331
+ for (const nodeId of graph.nodeIds()) {
332
+ const node = graph.getNode(nodeId);
333
+ if (node === void 0) continue;
334
+ const field = fieldClassifier(node.type !== void 0 ? {
335
+ id: nodeId,
336
+ type: node.type
337
+ } : { id: nodeId });
338
+ if (field === void 0) continue;
339
+ const nodeWithField = node.type !== void 0 ? {
340
+ id: nodeId,
341
+ type: node.type,
342
+ field
343
+ } : {
344
+ id: nodeId,
345
+ field
346
+ };
347
+ nodesWithFields.push(nodeWithField);
348
+ }
349
+ const errors = [];
350
+ const strataResults = [];
351
+ for (const stratum of strataDefinitions) {
352
+ const pairs = [];
353
+ const eligiblePairs = [];
354
+ for (let i = 0; i < nodesWithFields.length; i++) {
355
+ const source = nodesWithFields[i];
356
+ if (source === void 0) continue;
357
+ for (let j = i + 1; j < nodesWithFields.length; j++) {
358
+ if (j === i) continue;
359
+ const target = nodesWithFields[j];
360
+ if (target === void 0) continue;
361
+ if (stratum.predicate(source, target)) eligiblePairs.push({
362
+ source,
363
+ target
364
+ });
365
+ }
366
+ }
367
+ const numToSample = Math.min(pairsPerStratum, eligiblePairs.length);
368
+ for (let i = 0; i < numToSample; i++) {
369
+ const pair = eligiblePairs[Math.floor(rng() * eligiblePairs.length)];
370
+ if (pair === void 0) continue;
371
+ const sourceField = fieldClassifier(pair.source);
372
+ const targetField = fieldClassifier(pair.target);
373
+ pairs.push({
374
+ source: { id: pair.source.id },
375
+ target: { id: pair.target.id },
376
+ stratum: stratum.name,
377
+ sameField: sourceField === targetField
378
+ });
379
+ }
380
+ strataResults.push({
381
+ name: stratum.name,
382
+ pairs
383
+ });
384
+ }
385
+ for (const stratum of strataDefinitions) {
386
+ const result = strataResults.find((r) => r.name === stratum.name);
387
+ if (result === void 0 || result.pairs.length === 0) errors.push(/* @__PURE__ */ new Error(`No pairs found for stratum: ${stratum.name}`));
388
+ }
389
+ return {
390
+ strata: strataResults,
391
+ totalPairs: strataResults.reduce((sum, r) => sum + r.pairs.length, 0),
392
+ errors
393
+ };
394
+ }
395
+ //#endregion
396
+ exports.grasp = grasp;
397
+ exports.stratified = stratified;
398
+
399
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.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 classifierInput: { id: NodeId; type?: string } =\n\t\t\tnode.type !== undefined\n\t\t\t\t? { id: nodeId, type: node.type }\n\t\t\t\t: { id: nodeId };\n\t\tconst field = fieldClassifier(classifierInput);\n\t\tif (field === undefined) continue;\n\n\t\tconst nodeWithField: { id: NodeId; type?: string; field: string } =\n\t\t\tnode.type !== undefined\n\t\t\t\t? { id: nodeId, type: node.type, field }\n\t\t\t\t: { id: nodeId, field };\n\t\tnodesWithFields.push(nodeWithField);\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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"}
@@ -3,8 +3,6 @@
3
3
  *
4
4
  * @module seeds
5
5
  */
6
- export { grasp } from './grasp';
7
- export type { GraspOptions, GraspResult, GraspSeedPair } from './grasp';
8
- export { stratified } from './stratified';
9
- export type { StratifiedOptions, StratifiedResult, StratumResult, StratumDefinition, SeedPair, FieldClassifier, } from './stratified';
6
+ export * from './grasp';
7
+ export * from './stratified';
10
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seeds/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAExE,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,YAAY,EACX,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,EACb,iBAAiB,EACjB,QAAQ,EACR,eAAe,GACf,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/seeds/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,cAAc,SAAS,CAAC;AACxB,cAAc,cAAc,CAAC"}