graphwise 1.1.1 → 1.3.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 (147) hide show
  1. package/README.md +149 -2
  2. package/dist/expansion/frontier-balanced.d.ts +12 -0
  3. package/dist/expansion/frontier-balanced.d.ts.map +1 -0
  4. package/dist/expansion/frontier-balanced.unit.test.d.ts +2 -0
  5. package/dist/expansion/frontier-balanced.unit.test.d.ts.map +1 -0
  6. package/dist/expansion/index.d.ts +12 -13
  7. package/dist/expansion/index.d.ts.map +1 -1
  8. package/dist/expansion/random-priority.d.ts +20 -0
  9. package/dist/expansion/random-priority.d.ts.map +1 -0
  10. package/dist/expansion/random-priority.unit.test.d.ts +2 -0
  11. package/dist/expansion/random-priority.unit.test.d.ts.map +1 -0
  12. package/dist/expansion/standard-bfs.d.ts +12 -0
  13. package/dist/expansion/standard-bfs.d.ts.map +1 -0
  14. package/dist/expansion/standard-bfs.unit.test.d.ts +2 -0
  15. package/dist/expansion/standard-bfs.unit.test.d.ts.map +1 -0
  16. package/dist/extraction/index.d.ts +6 -6
  17. package/dist/extraction/index.d.ts.map +1 -1
  18. package/dist/extraction/motif.d.ts.map +1 -1
  19. package/dist/gpu/context.d.ts.map +1 -1
  20. package/dist/gpu/csr.d.ts.map +1 -1
  21. package/dist/gpu/index.cjs +410 -5
  22. package/dist/gpu/index.cjs.map +1 -0
  23. package/dist/gpu/index.d.ts +4 -5
  24. package/dist/gpu/index.d.ts.map +1 -1
  25. package/dist/gpu/index.js +400 -2
  26. package/dist/gpu/index.js.map +1 -0
  27. package/dist/graph/index.cjs +222 -2
  28. package/dist/graph/index.cjs.map +1 -0
  29. package/dist/graph/index.d.ts +3 -3
  30. package/dist/graph/index.d.ts.map +1 -1
  31. package/dist/graph/index.js +221 -1
  32. package/dist/graph/index.js.map +1 -0
  33. package/dist/index/index.cjs +966 -126
  34. package/dist/index/index.cjs.map +1 -1
  35. package/dist/index/index.js +939 -126
  36. package/dist/index/index.js.map +1 -1
  37. package/dist/{kmeans-B0HEOU6k.cjs → kmeans-87ExSUNZ.js} +27 -13
  38. package/dist/{kmeans-DgbsOznU.js.map → kmeans-87ExSUNZ.js.map} +1 -1
  39. package/dist/{kmeans-DgbsOznU.js → kmeans-BIgSyGKu.cjs} +44 -2
  40. package/dist/{kmeans-B0HEOU6k.cjs.map → kmeans-BIgSyGKu.cjs.map} +1 -1
  41. package/dist/ranking/baselines/betweenness.d.ts +13 -0
  42. package/dist/ranking/baselines/betweenness.d.ts.map +1 -0
  43. package/dist/ranking/baselines/betweenness.unit.test.d.ts +2 -0
  44. package/dist/ranking/baselines/betweenness.unit.test.d.ts.map +1 -0
  45. package/dist/ranking/baselines/communicability.d.ts +13 -0
  46. package/dist/ranking/baselines/communicability.d.ts.map +1 -0
  47. package/dist/ranking/baselines/communicability.unit.test.d.ts +2 -0
  48. package/dist/ranking/baselines/communicability.unit.test.d.ts.map +1 -0
  49. package/dist/ranking/baselines/degree-sum.d.ts +13 -0
  50. package/dist/ranking/baselines/degree-sum.d.ts.map +1 -0
  51. package/dist/ranking/baselines/degree-sum.unit.test.d.ts +2 -0
  52. package/dist/ranking/baselines/degree-sum.unit.test.d.ts.map +1 -0
  53. package/dist/ranking/baselines/index.d.ts +20 -0
  54. package/dist/ranking/baselines/index.d.ts.map +1 -0
  55. package/dist/ranking/baselines/jaccard-arithmetic.d.ts +13 -0
  56. package/dist/ranking/baselines/jaccard-arithmetic.d.ts.map +1 -0
  57. package/dist/ranking/baselines/jaccard-arithmetic.unit.test.d.ts +2 -0
  58. package/dist/ranking/baselines/jaccard-arithmetic.unit.test.d.ts.map +1 -0
  59. package/dist/ranking/baselines/katz.d.ts +13 -0
  60. package/dist/ranking/baselines/katz.d.ts.map +1 -0
  61. package/dist/ranking/baselines/katz.unit.test.d.ts +2 -0
  62. package/dist/ranking/baselines/katz.unit.test.d.ts.map +1 -0
  63. package/dist/ranking/baselines/pagerank.d.ts +13 -0
  64. package/dist/ranking/baselines/pagerank.d.ts.map +1 -0
  65. package/dist/ranking/baselines/pagerank.unit.test.d.ts +2 -0
  66. package/dist/ranking/baselines/pagerank.unit.test.d.ts.map +1 -0
  67. package/dist/ranking/baselines/random-ranking.d.ts +21 -0
  68. package/dist/ranking/baselines/random-ranking.d.ts.map +1 -0
  69. package/dist/ranking/baselines/random-ranking.unit.test.d.ts +2 -0
  70. package/dist/ranking/baselines/random-ranking.unit.test.d.ts.map +1 -0
  71. package/dist/ranking/baselines/resistance-distance.d.ts +13 -0
  72. package/dist/ranking/baselines/resistance-distance.d.ts.map +1 -0
  73. package/dist/ranking/baselines/resistance-distance.unit.test.d.ts +2 -0
  74. package/dist/ranking/baselines/resistance-distance.unit.test.d.ts.map +1 -0
  75. package/dist/ranking/baselines/widest-path.d.ts +13 -0
  76. package/dist/ranking/baselines/widest-path.d.ts.map +1 -0
  77. package/dist/ranking/baselines/widest-path.unit.test.d.ts +2 -0
  78. package/dist/ranking/baselines/widest-path.unit.test.d.ts.map +1 -0
  79. package/dist/ranking/index.d.ts +3 -6
  80. package/dist/ranking/index.d.ts.map +1 -1
  81. package/dist/ranking/mi/adamic-adar.d.ts.map +1 -1
  82. package/dist/ranking/mi/adaptive.d.ts +1 -1
  83. package/dist/ranking/mi/adaptive.d.ts.map +1 -1
  84. package/dist/ranking/mi/etch.d.ts.map +1 -1
  85. package/dist/ranking/mi/index.d.ts +9 -9
  86. package/dist/ranking/mi/index.d.ts.map +1 -1
  87. package/dist/ranking/mi/jaccard.d.ts.map +1 -1
  88. package/dist/ranking/mi/notch.d.ts.map +1 -1
  89. package/dist/ranking/mi/scale.d.ts.map +1 -1
  90. package/dist/ranking/mi/skew.d.ts.map +1 -1
  91. package/dist/ranking/mi/span.d.ts.map +1 -1
  92. package/dist/schemas/index.d.ts +2 -2
  93. package/dist/schemas/index.d.ts.map +1 -1
  94. package/dist/seeds/index.cjs +398 -3
  95. package/dist/seeds/index.cjs.map +1 -0
  96. package/dist/seeds/index.d.ts +2 -4
  97. package/dist/seeds/index.d.ts.map +1 -1
  98. package/dist/seeds/index.js +396 -1
  99. package/dist/seeds/index.js.map +1 -0
  100. package/dist/seeds/stratified.d.ts.map +1 -1
  101. package/dist/structures/index.cjs +133 -2
  102. package/dist/structures/index.cjs.map +1 -0
  103. package/dist/structures/index.d.ts +1 -2
  104. package/dist/structures/index.d.ts.map +1 -1
  105. package/dist/structures/index.js +132 -1
  106. package/dist/structures/index.js.map +1 -0
  107. package/dist/traversal/index.cjs +152 -5
  108. package/dist/traversal/index.cjs.map +1 -0
  109. package/dist/traversal/index.d.ts +2 -2
  110. package/dist/traversal/index.d.ts.map +1 -1
  111. package/dist/traversal/index.js +148 -1
  112. package/dist/traversal/index.js.map +1 -0
  113. package/dist/utils/index.cjs +254 -9
  114. package/dist/utils/index.cjs.map +1 -0
  115. package/dist/utils/index.d.ts +4 -3
  116. package/dist/utils/index.d.ts.map +1 -1
  117. package/dist/utils/index.js +242 -3
  118. package/dist/utils/index.js.map +1 -0
  119. package/dist/utils/neighbours.d.ts +54 -0
  120. package/dist/utils/neighbours.d.ts.map +1 -0
  121. package/dist/utils/neighbours.unit.test.d.ts +5 -0
  122. package/dist/utils/neighbours.unit.test.d.ts.map +1 -0
  123. package/package.json +1 -1
  124. package/dist/gpu-BJRVYBjx.cjs +0 -338
  125. package/dist/gpu-BJRVYBjx.cjs.map +0 -1
  126. package/dist/gpu-BveuXugy.js +0 -315
  127. package/dist/gpu-BveuXugy.js.map +0 -1
  128. package/dist/graph-DLWiziLB.js +0 -222
  129. package/dist/graph-DLWiziLB.js.map +0 -1
  130. package/dist/graph-az06J1YV.cjs +0 -227
  131. package/dist/graph-az06J1YV.cjs.map +0 -1
  132. package/dist/seeds-B6J9oJfU.cjs +0 -404
  133. package/dist/seeds-B6J9oJfU.cjs.map +0 -1
  134. package/dist/seeds-UNZxqm_U.js +0 -393
  135. package/dist/seeds-UNZxqm_U.js.map +0 -1
  136. package/dist/structures-BPfhfqNP.js +0 -133
  137. package/dist/structures-BPfhfqNP.js.map +0 -1
  138. package/dist/structures-CJ_S_7fs.cjs +0 -138
  139. package/dist/structures-CJ_S_7fs.cjs.map +0 -1
  140. package/dist/traversal-CQCjUwUJ.js +0 -149
  141. package/dist/traversal-CQCjUwUJ.js.map +0 -1
  142. package/dist/traversal-QeHaNUWn.cjs +0 -172
  143. package/dist/traversal-QeHaNUWn.cjs.map +0 -1
  144. package/dist/utils-Q_akvlMn.js +0 -164
  145. package/dist/utils-Q_akvlMn.js.map +0 -1
  146. package/dist/utils-spZa1ZvS.cjs +0 -205
  147. package/dist/utils-spZa1ZvS.cjs.map +0 -1
@@ -1,227 +0,0 @@
1
- //#region src/graph/adjacency-map.ts
2
- /**
3
- * Graph implementation using adjacency map data structure.
4
- *
5
- * Uses Map<NodeId, N> for node storage and Map<NodeId, Map<NodeId, E>>
6
- * for adjacency representation. This provides O(1) average-case lookup
7
- * for nodes and edges, with memory proportional to V + E.
8
- *
9
- * @typeParam N - Node data type, must extend NodeData
10
- * @typeParam E - Edge data type, must extend EdgeData
11
- *
12
- * @example
13
- * ```typescript
14
- * // Create a directed citation graph
15
- * const graph = AdjacencyMapGraph.directed<AuthorNode, CitationEdge>()
16
- * .addNode({ id: 'A1', name: 'Alice' })
17
- * .addNode({ id: 'B1', name: 'Bob' })
18
- * .addEdge({ source: 'A1', target: 'B1', year: 2024 });
19
- * ```
20
- */
21
- var AdjacencyMapGraph = class AdjacencyMapGraph {
22
- directed;
23
- nodes;
24
- adjacency;
25
- reverseAdjacency;
26
- _edgeCount;
27
- constructor(directed) {
28
- this.directed = directed;
29
- this.nodes = /* @__PURE__ */ new Map();
30
- this.adjacency = /* @__PURE__ */ new Map();
31
- this.reverseAdjacency = directed ? /* @__PURE__ */ new Map() : null;
32
- this._edgeCount = 0;
33
- }
34
- /**
35
- * Create a new directed graph.
36
- *
37
- * In a directed graph, edges have direction from source to target.
38
- * The `neighbours` method with direction 'out' returns successors,
39
- * and direction 'in' returns predecessors.
40
- *
41
- * @typeParam N - Node data type
42
- * @typeParam E - Edge data type
43
- * @returns A new empty directed graph
44
- */
45
- static directed() {
46
- return new AdjacencyMapGraph(true);
47
- }
48
- /**
49
- * Create a new undirected graph.
50
- *
51
- * In an undirected graph, edges have no direction. Adding an edge
52
- * from A to B automatically creates the connection from B to A.
53
- *
54
- * @typeParam N - Node data type
55
- * @typeParam E - Edge data type
56
- * @returns A new empty undirected graph
57
- */
58
- static undirected() {
59
- return new AdjacencyMapGraph(false);
60
- }
61
- get nodeCount() {
62
- return this.nodes.size;
63
- }
64
- get edgeCount() {
65
- return this._edgeCount;
66
- }
67
- hasNode(id) {
68
- return this.nodes.has(id);
69
- }
70
- getNode(id) {
71
- return this.nodes.get(id);
72
- }
73
- /**
74
- * Iterate over all node identifiers in the graph.
75
- *
76
- * @returns An iterable of all node IDs
77
- */
78
- *nodeIds() {
79
- yield* this.nodes.keys();
80
- }
81
- neighbours(id, direction = "out") {
82
- if (!this.nodes.has(id)) return [];
83
- if (this.directed) {
84
- if (direction === "out") return this.adjacency.get(id)?.keys() ?? [];
85
- if (direction === "in") return this.reverseAdjacency?.get(id)?.keys() ?? [];
86
- return this.iterateBothDirections(id);
87
- }
88
- return this.adjacency.get(id)?.keys() ?? [];
89
- }
90
- *iterateBothDirections(id) {
91
- const seen = /* @__PURE__ */ new Set();
92
- const outNeighbours = this.adjacency.get(id);
93
- if (outNeighbours !== void 0) {
94
- for (const neighbour of outNeighbours.keys()) if (!seen.has(neighbour)) {
95
- seen.add(neighbour);
96
- yield neighbour;
97
- }
98
- }
99
- const inNeighbours = this.reverseAdjacency?.get(id);
100
- if (inNeighbours !== void 0) {
101
- for (const neighbour of inNeighbours.keys()) if (!seen.has(neighbour)) {
102
- seen.add(neighbour);
103
- yield neighbour;
104
- }
105
- }
106
- }
107
- degree(id, direction = "out") {
108
- if (!this.nodes.has(id)) return 0;
109
- if (this.directed) {
110
- if (direction === "out") return this.adjacency.get(id)?.size ?? 0;
111
- if (direction === "in") return this.reverseAdjacency?.get(id)?.size ?? 0;
112
- return (this.adjacency.get(id)?.size ?? 0) + (this.reverseAdjacency?.get(id)?.size ?? 0);
113
- }
114
- return this.adjacency.get(id)?.size ?? 0;
115
- }
116
- getEdge(source, target) {
117
- const forward = this.adjacency.get(source)?.get(target);
118
- if (forward !== void 0) return forward;
119
- if (!this.directed) return this.adjacency.get(target)?.get(source);
120
- }
121
- *edges() {
122
- const emitted = /* @__PURE__ */ new Set();
123
- for (const [, neighbours] of this.adjacency) for (const [, edge] of neighbours) if (this.directed) yield edge;
124
- else {
125
- const key = this.edgeKey(edge.source, edge.target);
126
- if (!emitted.has(key)) {
127
- emitted.add(key);
128
- yield edge;
129
- }
130
- }
131
- }
132
- edgeKey(source, target) {
133
- const [a, b] = source < target ? [source, target] : [target, source];
134
- return `${a}::${b}`;
135
- }
136
- /**
137
- * Add a node to the graph (builder pattern).
138
- *
139
- * If a node with the same ID already exists, it is not replaced.
140
- *
141
- * @param node - The node data to add
142
- * @returns this (for method chaining)
143
- */
144
- addNode(node) {
145
- if (!this.nodes.has(node.id)) this.nodes.set(node.id, node);
146
- return this;
147
- }
148
- /**
149
- * Add an edge to the graph (builder pattern).
150
- *
151
- * @param edge - The edge data to add
152
- * @returns this (for method chaining)
153
- * @throws Error if either endpoint node does not exist
154
- */
155
- addEdge(edge) {
156
- if (!this.nodes.has(edge.source) || !this.nodes.has(edge.target)) throw new Error(`Cannot add edge: nodes ${edge.source} and/or ${edge.target} do not exist`);
157
- let forwardMap = this.adjacency.get(edge.source);
158
- if (forwardMap === void 0) {
159
- forwardMap = /* @__PURE__ */ new Map();
160
- this.adjacency.set(edge.source, forwardMap);
161
- }
162
- const isNewEdge = !forwardMap.has(edge.target);
163
- forwardMap.set(edge.target, edge);
164
- if (this.directed) {
165
- let reverseMap = this.reverseAdjacency?.get(edge.target);
166
- if (reverseMap === void 0) {
167
- reverseMap = /* @__PURE__ */ new Map();
168
- this.reverseAdjacency?.set(edge.target, reverseMap);
169
- }
170
- reverseMap.set(edge.source, edge);
171
- } else {
172
- let reverseMap = this.adjacency.get(edge.target);
173
- if (reverseMap === void 0) {
174
- reverseMap = /* @__PURE__ */ new Map();
175
- this.adjacency.set(edge.target, reverseMap);
176
- }
177
- reverseMap.set(edge.source, edge);
178
- }
179
- if (isNewEdge) this._edgeCount++;
180
- return this;
181
- }
182
- removeNode(id) {
183
- if (!this.nodes.has(id)) return false;
184
- const outNeighbours = [...this.adjacency.get(id)?.keys() ?? []];
185
- for (const neighbour of outNeighbours) this.removeEdgeInternal(id, neighbour);
186
- if (this.directed && this.reverseAdjacency !== null) {
187
- const inNeighbours = [...this.reverseAdjacency.get(id)?.keys() ?? []];
188
- for (const neighbour of inNeighbours) this.removeEdgeFromDirected(neighbour, id);
189
- }
190
- this.nodes.delete(id);
191
- this.adjacency.delete(id);
192
- this.reverseAdjacency?.delete(id);
193
- return true;
194
- }
195
- /**
196
- * Remove an edge from a directed graph, updating both adjacency maps.
197
- * This handles the case where we're removing an edge that points TO the removed node.
198
- */
199
- removeEdgeFromDirected(source, target) {
200
- if (this.adjacency.get(source)?.delete(target) === true) this._edgeCount--;
201
- this.reverseAdjacency?.get(target)?.delete(source);
202
- }
203
- removeEdgeInternal(source, target) {
204
- if (this.adjacency.get(source)?.delete(target) === true) this._edgeCount--;
205
- if (this.directed) this.reverseAdjacency?.get(target)?.delete(source);
206
- else this.adjacency.get(target)?.delete(source);
207
- }
208
- removeEdge(source, target) {
209
- if (!this.hasEdgeInternal(source, target)) return false;
210
- this.removeEdgeInternal(source, target);
211
- return true;
212
- }
213
- hasEdgeInternal(source, target) {
214
- if (this.adjacency.get(source)?.has(target) === true) return true;
215
- if (!this.directed) return this.adjacency.get(target)?.has(source) === true;
216
- return false;
217
- }
218
- };
219
- //#endregion
220
- Object.defineProperty(exports, "AdjacencyMapGraph", {
221
- enumerable: true,
222
- get: function() {
223
- return AdjacencyMapGraph;
224
- }
225
- });
226
-
227
- //# sourceMappingURL=graph-az06J1YV.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"graph-az06J1YV.cjs","names":[],"sources":["../src/graph/adjacency-map.ts"],"sourcesContent":["/**\n * Adjacency map graph implementation.\n *\n * This module provides a flexible graph implementation using nested Maps\n * for efficient adjacency list representation. It supports both directed\n * and undirected graphs with builder pattern for convenient construction.\n */\n\nimport type { NodeId, NodeData, EdgeData, Direction } from \"./types\";\nimport type { MutableGraph } from \"./interfaces\";\n\n/**\n * Graph implementation using adjacency map data structure.\n *\n * Uses Map<NodeId, N> for node storage and Map<NodeId, Map<NodeId, E>>\n * for adjacency representation. This provides O(1) average-case lookup\n * for nodes and edges, with memory proportional to V + E.\n *\n * @typeParam N - Node data type, must extend NodeData\n * @typeParam E - Edge data type, must extend EdgeData\n *\n * @example\n * ```typescript\n * // Create a directed citation graph\n * const graph = AdjacencyMapGraph.directed<AuthorNode, CitationEdge>()\n * .addNode({ id: 'A1', name: 'Alice' })\n * .addNode({ id: 'B1', name: 'Bob' })\n * .addEdge({ source: 'A1', target: 'B1', year: 2024 });\n * ```\n */\nexport class AdjacencyMapGraph<\n\tN extends NodeData = NodeData,\n\tE extends EdgeData = EdgeData,\n> implements MutableGraph<N, E> {\n\treadonly directed: boolean;\n\n\tprivate readonly nodes: Map<NodeId, N>;\n\tprivate readonly adjacency: Map<NodeId, Map<NodeId, E>>;\n\tprivate readonly reverseAdjacency: Map<NodeId, Map<NodeId, E>> | null;\n\tprivate _edgeCount: number;\n\n\tprivate constructor(directed: boolean) {\n\t\tthis.directed = directed;\n\t\tthis.nodes = new Map();\n\t\tthis.adjacency = new Map();\n\t\tthis.reverseAdjacency = directed ? new Map() : null;\n\t\tthis._edgeCount = 0;\n\t}\n\n\t/**\n\t * Create a new directed graph.\n\t *\n\t * In a directed graph, edges have direction from source to target.\n\t * The `neighbours` method with direction 'out' returns successors,\n\t * and direction 'in' returns predecessors.\n\t *\n\t * @typeParam N - Node data type\n\t * @typeParam E - Edge data type\n\t * @returns A new empty directed graph\n\t */\n\tstatic directed<\n\t\tN extends NodeData = NodeData,\n\t\tE extends EdgeData = EdgeData,\n\t>(): AdjacencyMapGraph<N, E> {\n\t\treturn new AdjacencyMapGraph<N, E>(true);\n\t}\n\n\t/**\n\t * Create a new undirected graph.\n\t *\n\t * In an undirected graph, edges have no direction. Adding an edge\n\t * from A to B automatically creates the connection from B to A.\n\t *\n\t * @typeParam N - Node data type\n\t * @typeParam E - Edge data type\n\t * @returns A new empty undirected graph\n\t */\n\tstatic undirected<\n\t\tN extends NodeData = NodeData,\n\t\tE extends EdgeData = EdgeData,\n\t>(): AdjacencyMapGraph<N, E> {\n\t\treturn new AdjacencyMapGraph<N, E>(false);\n\t}\n\n\tget nodeCount(): number {\n\t\treturn this.nodes.size;\n\t}\n\n\tget edgeCount(): number {\n\t\treturn this._edgeCount;\n\t}\n\n\thasNode(id: NodeId): boolean {\n\t\treturn this.nodes.has(id);\n\t}\n\n\tgetNode(id: NodeId): N | undefined {\n\t\treturn this.nodes.get(id);\n\t}\n\n\t/**\n\t * Iterate over all node identifiers in the graph.\n\t *\n\t * @returns An iterable of all node IDs\n\t */\n\t*nodeIds(): Iterable<NodeId> {\n\t\tyield* this.nodes.keys();\n\t}\n\n\tneighbours(id: NodeId, direction: Direction = \"out\"): Iterable<NodeId> {\n\t\tif (!this.nodes.has(id)) {\n\t\t\treturn [];\n\t\t}\n\n\t\tif (this.directed) {\n\t\t\tif (direction === \"out\") {\n\t\t\t\treturn this.adjacency.get(id)?.keys() ?? [];\n\t\t\t}\n\t\t\tif (direction === \"in\") {\n\t\t\t\treturn this.reverseAdjacency?.get(id)?.keys() ?? [];\n\t\t\t}\n\t\t\t// direction === 'both'\n\t\t\treturn this.iterateBothDirections(id);\n\t\t}\n\n\t\t// Undirected: all neighbours are in adjacency\n\t\treturn this.adjacency.get(id)?.keys() ?? [];\n\t}\n\n\tprivate *iterateBothDirections(id: NodeId): Iterable<NodeId> {\n\t\tconst seen = new Set<NodeId>();\n\n\t\t// Yield outgoing neighbours\n\t\tconst outNeighbours = this.adjacency.get(id);\n\t\tif (outNeighbours !== undefined) {\n\t\t\tfor (const neighbour of outNeighbours.keys()) {\n\t\t\t\tif (!seen.has(neighbour)) {\n\t\t\t\t\tseen.add(neighbour);\n\t\t\t\t\tyield neighbour;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Yield incoming neighbours\n\t\tconst inNeighbours = this.reverseAdjacency?.get(id);\n\t\tif (inNeighbours !== undefined) {\n\t\t\tfor (const neighbour of inNeighbours.keys()) {\n\t\t\t\tif (!seen.has(neighbour)) {\n\t\t\t\t\tseen.add(neighbour);\n\t\t\t\t\tyield neighbour;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tdegree(id: NodeId, direction: Direction = \"out\"): number {\n\t\tif (!this.nodes.has(id)) {\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (this.directed) {\n\t\t\tif (direction === \"out\") {\n\t\t\t\treturn this.adjacency.get(id)?.size ?? 0;\n\t\t\t}\n\t\t\tif (direction === \"in\") {\n\t\t\t\treturn this.reverseAdjacency?.get(id)?.size ?? 0;\n\t\t\t}\n\t\t\t// direction === 'both': count unique neighbours\n\t\t\tconst outSize = this.adjacency.get(id)?.size ?? 0;\n\t\t\tconst inSize = this.reverseAdjacency?.get(id)?.size ?? 0;\n\t\t\t// Simple sum is sufficient as edges are stored separately\n\t\t\treturn outSize + inSize;\n\t\t}\n\n\t\t// Undirected\n\t\treturn this.adjacency.get(id)?.size ?? 0;\n\t}\n\n\tgetEdge(source: NodeId, target: NodeId): E | undefined {\n\t\t// For undirected, try both orders\n\t\tconst forward = this.adjacency.get(source)?.get(target);\n\t\tif (forward !== undefined) {\n\t\t\treturn forward;\n\t\t}\n\n\t\tif (!this.directed) {\n\t\t\treturn this.adjacency.get(target)?.get(source);\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\t*edges(): Iterable<E> {\n\t\tconst emitted = new Set<string>();\n\n\t\tfor (const [, neighbours] of this.adjacency) {\n\t\t\tfor (const [, edge] of neighbours) {\n\t\t\t\tif (this.directed) {\n\t\t\t\t\tyield edge;\n\t\t\t\t} else {\n\t\t\t\t\t// For undirected, avoid emitting duplicate edges\n\t\t\t\t\tconst key = this.edgeKey(edge.source, edge.target);\n\t\t\t\t\tif (!emitted.has(key)) {\n\t\t\t\t\t\temitted.add(key);\n\t\t\t\t\t\tyield edge;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate edgeKey(source: NodeId, target: NodeId): string {\n\t\t// Create a canonical key for undirected edges\n\t\tconst [a, b] = source < target ? [source, target] : [target, source];\n\t\treturn `${a}::${b}`;\n\t}\n\n\t/**\n\t * Add a node to the graph (builder pattern).\n\t *\n\t * If a node with the same ID already exists, it is not replaced.\n\t *\n\t * @param node - The node data to add\n\t * @returns this (for method chaining)\n\t */\n\taddNode(node: N): this {\n\t\tif (!this.nodes.has(node.id)) {\n\t\t\tthis.nodes.set(node.id, node);\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Add an edge to the graph (builder pattern).\n\t *\n\t * @param edge - The edge data to add\n\t * @returns this (for method chaining)\n\t * @throws Error if either endpoint node does not exist\n\t */\n\taddEdge(edge: E): this {\n\t\t// Ensure both nodes exist\n\t\tif (!this.nodes.has(edge.source) || !this.nodes.has(edge.target)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Cannot add edge: nodes ${edge.source} and/or ${edge.target} do not exist`,\n\t\t\t);\n\t\t}\n\n\t\t// Store in forward adjacency\n\t\tlet forwardMap = this.adjacency.get(edge.source);\n\t\tif (forwardMap === undefined) {\n\t\t\tforwardMap = new Map();\n\t\t\tthis.adjacency.set(edge.source, forwardMap);\n\t\t}\n\n\t\tconst isNewEdge = !forwardMap.has(edge.target);\n\t\tforwardMap.set(edge.target, edge);\n\n\t\tif (this.directed) {\n\t\t\t// Store reverse reference for efficient predecessor lookup\n\t\t\tlet reverseMap = this.reverseAdjacency?.get(edge.target);\n\t\t\tif (reverseMap === undefined) {\n\t\t\t\treverseMap = new Map();\n\t\t\t\tthis.reverseAdjacency?.set(edge.target, reverseMap);\n\t\t\t}\n\t\t\treverseMap.set(edge.source, edge);\n\t\t} else {\n\t\t\t// For undirected, also store in reverse direction\n\t\t\tlet reverseMap = this.adjacency.get(edge.target);\n\t\t\tif (reverseMap === undefined) {\n\t\t\t\treverseMap = new Map();\n\t\t\t\tthis.adjacency.set(edge.target, reverseMap);\n\t\t\t}\n\t\t\treverseMap.set(edge.source, edge);\n\t\t}\n\n\t\tif (isNewEdge) {\n\t\t\tthis._edgeCount++;\n\t\t}\n\t\treturn this;\n\t}\n\n\tremoveNode(id: NodeId): boolean {\n\t\tif (!this.nodes.has(id)) {\n\t\t\treturn false;\n\t\t}\n\n\t\t// Remove all outgoing edges from this node\n\t\tconst outNeighbours = [...(this.adjacency.get(id)?.keys() ?? [])];\n\t\tfor (const neighbour of outNeighbours) {\n\t\t\tthis.removeEdgeInternal(id, neighbour);\n\t\t}\n\n\t\t// For directed graphs, also remove incoming edges to this node\n\t\tif (this.directed && this.reverseAdjacency !== null) {\n\t\t\tconst inNeighbours = [...(this.reverseAdjacency.get(id)?.keys() ?? [])];\n\t\t\tfor (const neighbour of inNeighbours) {\n\t\t\t\t// Remove the edge from neighbour -> id\n\t\t\t\tthis.removeEdgeFromDirected(neighbour, id);\n\t\t\t}\n\t\t}\n\n\t\t// Remove the node itself\n\t\tthis.nodes.delete(id);\n\t\tthis.adjacency.delete(id);\n\t\tthis.reverseAdjacency?.delete(id);\n\n\t\treturn true;\n\t}\n\n\t/**\n\t * Remove an edge from a directed graph, updating both adjacency maps.\n\t * This handles the case where we're removing an edge that points TO the removed node.\n\t */\n\tprivate removeEdgeFromDirected(source: NodeId, target: NodeId): void {\n\t\t// Remove from forward adjacency (source -> target)\n\t\tconst forwardMap = this.adjacency.get(source);\n\t\tif (forwardMap?.delete(target) === true) {\n\t\t\tthis._edgeCount--;\n\t\t}\n\n\t\t// Remove from reverse adjacency\n\t\tthis.reverseAdjacency?.get(target)?.delete(source);\n\t}\n\n\tprivate removeEdgeInternal(source: NodeId, target: NodeId): void {\n\t\t// Remove from forward adjacency\n\t\tconst forwardMap = this.adjacency.get(source);\n\t\tif (forwardMap?.delete(target) === true) {\n\t\t\tthis._edgeCount--;\n\t\t}\n\n\t\tif (this.directed) {\n\t\t\t// Remove from reverse adjacency\n\t\t\tthis.reverseAdjacency?.get(target)?.delete(source);\n\t\t} else {\n\t\t\t// For undirected, remove both directions\n\t\t\tthis.adjacency.get(target)?.delete(source);\n\t\t}\n\t}\n\n\tremoveEdge(source: NodeId, target: NodeId): boolean {\n\t\t// Check if edge exists\n\t\tif (!this.hasEdgeInternal(source, target)) {\n\t\t\treturn false;\n\t\t}\n\n\t\tthis.removeEdgeInternal(source, target);\n\t\treturn true;\n\t}\n\n\tprivate hasEdgeInternal(source: NodeId, target: NodeId): boolean {\n\t\tconst forward = this.adjacency.get(source)?.has(target) === true;\n\t\tif (forward) {\n\t\t\treturn true;\n\t\t}\n\n\t\tif (!this.directed) {\n\t\t\treturn this.adjacency.get(target)?.has(source) === true;\n\t\t}\n\n\t\treturn false;\n\t}\n}\n"],"mappings":"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"}
@@ -1,404 +0,0 @@
1
- const require_kmeans = require("./kmeans-B0HEOU6k.cjs");
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 = require_kmeans.normaliseFeatures(features);
270
- const k = Math.min(config.nClusters, features.length);
271
- const kmeansResult = require_kmeans.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
- Object.defineProperty(exports, "grasp", {
392
- enumerable: true,
393
- get: function() {
394
- return grasp;
395
- }
396
- });
397
- Object.defineProperty(exports, "stratified", {
398
- enumerable: true,
399
- get: function() {
400
- return stratified;
401
- }
402
- });
403
-
404
- //# sourceMappingURL=seeds-B6J9oJfU.cjs.map