graphwise 1.5.2 → 1.7.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.
- package/README.md +36 -11
- package/dist/__test__/fixtures/index.d.ts +1 -0
- package/dist/__test__/fixtures/index.d.ts.map +1 -1
- package/dist/__test__/fixtures/wrap-async.d.ts +10 -0
- package/dist/__test__/fixtures/wrap-async.d.ts.map +1 -0
- package/dist/async/index.d.ts +11 -0
- package/dist/async/index.d.ts.map +1 -0
- package/dist/async/ops.d.ts +10 -0
- package/dist/async/ops.d.ts.map +1 -0
- package/dist/async/protocol.d.ts +62 -0
- package/dist/async/protocol.d.ts.map +1 -0
- package/dist/async/runners.d.ts +55 -0
- package/dist/async/runners.d.ts.map +1 -0
- package/dist/async/runners.unit.test.d.ts +8 -0
- package/dist/async/runners.unit.test.d.ts.map +1 -0
- package/dist/async/types.d.ts +15 -0
- package/dist/async/types.d.ts.map +1 -0
- package/dist/async/utils.d.ts +10 -0
- package/dist/async/utils.d.ts.map +1 -0
- package/dist/expansion/base-core.d.ts +24 -0
- package/dist/expansion/base-core.d.ts.map +1 -0
- package/dist/expansion/base-core.unit.test.d.ts +10 -0
- package/dist/expansion/base-core.unit.test.d.ts.map +1 -0
- package/dist/expansion/base-helpers.d.ts +57 -0
- package/dist/expansion/base-helpers.d.ts.map +1 -0
- package/dist/expansion/base.d.ts +32 -1
- package/dist/expansion/base.d.ts.map +1 -1
- package/dist/graph/async-interfaces.d.ts +31 -0
- package/dist/graph/async-interfaces.d.ts.map +1 -0
- package/dist/graph/async-interfaces.unit.test.d.ts +2 -0
- package/dist/graph/async-interfaces.unit.test.d.ts.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/graph/index.d.ts.map +1 -1
- package/dist/index/index.cjs +416 -82
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +416 -83
- package/dist/index/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index/index.js
CHANGED
|
@@ -5,22 +5,326 @@ import { n as miniBatchKMeans, r as normaliseFeatures, t as _computeMean } from
|
|
|
5
5
|
import { approximateClusteringCoefficient, batchClusteringCoefficients, computeJaccard, countEdgesOfType, countNodesOfType, entropyFromCounts, localClusteringCoefficient, localTypeEntropy, neighbourIntersection, neighbourOverlap, neighbourSet, normalisedEntropy, shannonEntropy } from "../utils/index.js";
|
|
6
6
|
import { grasp, stratified } from "../seeds/index.js";
|
|
7
7
|
import { GPUContext, GPUNotAvailableError, assertWebGPUAvailable, createGPUContext, createResultBuffer, csrToGPUBuffers, detectWebGPU, getGPUContext, graphToCSR, isWebGPUAvailable, readBufferToCPU } from "../gpu/index.js";
|
|
8
|
-
//#region src/
|
|
8
|
+
//#region src/async/utils.ts
|
|
9
|
+
/**
|
|
10
|
+
* Async utility functions.
|
|
11
|
+
*
|
|
12
|
+
* @module async/utils
|
|
13
|
+
*/
|
|
14
|
+
/** Collect an AsyncIterable into a readonly array. */
|
|
15
|
+
async function collectAsyncIterable(iter) {
|
|
16
|
+
const result = [];
|
|
17
|
+
for await (const item of iter) result.push(item);
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
/** Default yield strategy: setTimeout(0) to yield to the event loop. */
|
|
21
|
+
function defaultYieldStrategy() {
|
|
22
|
+
return new Promise((r) => {
|
|
23
|
+
setTimeout(r, 0);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
//#endregion
|
|
27
|
+
//#region src/async/runners.ts
|
|
28
|
+
/**
|
|
29
|
+
* Resolve a single GraphOp against a synchronous ReadableGraph.
|
|
30
|
+
*
|
|
31
|
+
* Returns a tagged GraphOpResponse so the receiving generator can narrow
|
|
32
|
+
* the result type without type assertions.
|
|
33
|
+
*
|
|
34
|
+
* @param graph - The synchronous graph to query
|
|
35
|
+
* @param op - The operation to resolve
|
|
36
|
+
* @returns The tagged response
|
|
37
|
+
*/
|
|
38
|
+
function resolveSyncOp(graph, op) {
|
|
39
|
+
switch (op.tag) {
|
|
40
|
+
case "neighbours": return {
|
|
41
|
+
tag: "neighbours",
|
|
42
|
+
value: Array.from(graph.neighbours(op.id, op.direction))
|
|
43
|
+
};
|
|
44
|
+
case "degree": return {
|
|
45
|
+
tag: "degree",
|
|
46
|
+
value: graph.degree(op.id, op.direction)
|
|
47
|
+
};
|
|
48
|
+
case "getNode": return {
|
|
49
|
+
tag: "getNode",
|
|
50
|
+
value: graph.getNode(op.id)
|
|
51
|
+
};
|
|
52
|
+
case "getEdge": return {
|
|
53
|
+
tag: "getEdge",
|
|
54
|
+
value: graph.getEdge(op.source, op.target)
|
|
55
|
+
};
|
|
56
|
+
case "hasNode": return {
|
|
57
|
+
tag: "hasNode",
|
|
58
|
+
value: graph.hasNode(op.id)
|
|
59
|
+
};
|
|
60
|
+
case "yield": return { tag: "yield" };
|
|
61
|
+
case "progress": return { tag: "progress" };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
9
64
|
/**
|
|
10
|
-
*
|
|
65
|
+
* Drive a generator to completion using a synchronous graph.
|
|
66
|
+
*
|
|
67
|
+
* The generator yields GraphOp requests; each is resolved immediately
|
|
68
|
+
* against the graph and the tagged response is fed back via gen.next().
|
|
69
|
+
*
|
|
70
|
+
* @param gen - The generator to drive
|
|
71
|
+
* @param graph - The graph to resolve ops against
|
|
72
|
+
* @returns The generator's return value
|
|
73
|
+
*/
|
|
74
|
+
function runSync(gen, graph) {
|
|
75
|
+
let step = gen.next();
|
|
76
|
+
while (step.done !== true) {
|
|
77
|
+
const response = resolveSyncOp(graph, step.value);
|
|
78
|
+
step = gen.next(response);
|
|
79
|
+
}
|
|
80
|
+
return step.value;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Resolve a single GraphOp against an async ReadableGraph.
|
|
84
|
+
*
|
|
85
|
+
* AsyncIterables (neighbours) are collected into readonly arrays so the
|
|
86
|
+
* generator receives the same value type as in sync mode. Returns a tagged
|
|
87
|
+
* GraphOpResponse for type-safe narrowing without assertions.
|
|
88
|
+
*
|
|
89
|
+
* @param graph - The async graph to query
|
|
90
|
+
* @param op - The operation to resolve
|
|
91
|
+
* @returns A promise resolving to the tagged response
|
|
92
|
+
*/
|
|
93
|
+
async function resolveAsyncOp(graph, op) {
|
|
94
|
+
switch (op.tag) {
|
|
95
|
+
case "neighbours": return {
|
|
96
|
+
tag: "neighbours",
|
|
97
|
+
value: await collectAsyncIterable(graph.neighbours(op.id, op.direction))
|
|
98
|
+
};
|
|
99
|
+
case "degree": return {
|
|
100
|
+
tag: "degree",
|
|
101
|
+
value: await graph.degree(op.id, op.direction)
|
|
102
|
+
};
|
|
103
|
+
case "getNode": return {
|
|
104
|
+
tag: "getNode",
|
|
105
|
+
value: await graph.getNode(op.id)
|
|
106
|
+
};
|
|
107
|
+
case "getEdge": return {
|
|
108
|
+
tag: "getEdge",
|
|
109
|
+
value: await graph.getEdge(op.source, op.target)
|
|
110
|
+
};
|
|
111
|
+
case "hasNode": return {
|
|
112
|
+
tag: "hasNode",
|
|
113
|
+
value: await graph.hasNode(op.id)
|
|
114
|
+
};
|
|
115
|
+
case "yield": return { tag: "yield" };
|
|
116
|
+
case "progress": return { tag: "progress" };
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Drive a generator to completion using an async graph.
|
|
121
|
+
*
|
|
122
|
+
* Extends sync semantics with:
|
|
123
|
+
* - Cancellation via AbortSignal (throws DOMException "AbortError")
|
|
124
|
+
* - Cooperative yielding at `yield` ops (calls yieldStrategy)
|
|
125
|
+
* - Progress callbacks at `progress` ops (may be async for backpressure)
|
|
126
|
+
* - Error propagation: graph errors are forwarded via gen.throw(); if the
|
|
127
|
+
* generator does not handle them, they propagate to the caller
|
|
128
|
+
*
|
|
129
|
+
* @param gen - The generator to drive
|
|
130
|
+
* @param graph - The async graph to resolve ops against
|
|
131
|
+
* @param options - Runner configuration
|
|
132
|
+
* @returns A promise resolving to the generator's return value
|
|
133
|
+
*/
|
|
134
|
+
async function runAsync(gen, graph, options) {
|
|
135
|
+
const signal = options?.signal;
|
|
136
|
+
const onProgress = options?.onProgress;
|
|
137
|
+
const yieldStrategy = options?.yieldStrategy ?? defaultYieldStrategy;
|
|
138
|
+
let step = gen.next();
|
|
139
|
+
while (step.done !== true) {
|
|
140
|
+
if (signal?.aborted === true) {
|
|
141
|
+
const abortError = new DOMException("Aborted", "AbortError");
|
|
142
|
+
try {
|
|
143
|
+
gen.throw(abortError);
|
|
144
|
+
} catch {
|
|
145
|
+
throw abortError;
|
|
146
|
+
}
|
|
147
|
+
throw abortError;
|
|
148
|
+
}
|
|
149
|
+
const op = step.value;
|
|
150
|
+
if (op.tag === "yield") {
|
|
151
|
+
await yieldStrategy();
|
|
152
|
+
step = gen.next({ tag: "yield" });
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (op.tag === "progress") {
|
|
156
|
+
if (onProgress !== void 0) {
|
|
157
|
+
const maybePromise = onProgress(op.stats);
|
|
158
|
+
if (maybePromise instanceof Promise) await maybePromise;
|
|
159
|
+
}
|
|
160
|
+
step = gen.next({ tag: "progress" });
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
let response;
|
|
164
|
+
try {
|
|
165
|
+
response = await resolveAsyncOp(graph, op);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
step = gen.throw(error);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
step = gen.next(response);
|
|
171
|
+
}
|
|
172
|
+
return step.value;
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/async/ops.ts
|
|
176
|
+
function* opNeighbours(id, direction) {
|
|
177
|
+
const response = yield direction !== void 0 ? {
|
|
178
|
+
tag: "neighbours",
|
|
179
|
+
id,
|
|
180
|
+
direction
|
|
181
|
+
} : {
|
|
182
|
+
tag: "neighbours",
|
|
183
|
+
id
|
|
184
|
+
};
|
|
185
|
+
if (response.tag !== "neighbours") throw new TypeError(`Expected neighbours response, got ${response.tag}`);
|
|
186
|
+
return response.value;
|
|
187
|
+
}
|
|
188
|
+
function* opDegree(id, direction) {
|
|
189
|
+
const response = yield direction !== void 0 ? {
|
|
190
|
+
tag: "degree",
|
|
191
|
+
id,
|
|
192
|
+
direction
|
|
193
|
+
} : {
|
|
194
|
+
tag: "degree",
|
|
195
|
+
id
|
|
196
|
+
};
|
|
197
|
+
if (response.tag !== "degree") throw new TypeError(`Expected degree response, got ${response.tag}`);
|
|
198
|
+
return response.value;
|
|
199
|
+
}
|
|
200
|
+
//#endregion
|
|
201
|
+
//#region src/expansion/base-helpers.ts
|
|
202
|
+
/**
|
|
203
|
+
* Check whether expansion should continue given current progress.
|
|
204
|
+
*
|
|
205
|
+
* Returns shouldContinue=false as soon as any configured limit is reached,
|
|
206
|
+
* along with the appropriate termination reason.
|
|
207
|
+
*
|
|
208
|
+
* @param iterations - Number of iterations completed so far
|
|
209
|
+
* @param nodesVisited - Number of distinct nodes visited so far
|
|
210
|
+
* @param pathsFound - Number of paths discovered so far
|
|
211
|
+
* @param limits - Configured expansion limits (0 = unlimited)
|
|
212
|
+
* @returns Whether to continue and the termination reason if stopping
|
|
213
|
+
*/
|
|
214
|
+
function continueExpansion(iterations, nodesVisited, pathsFound, limits) {
|
|
215
|
+
if (limits.maxIterations > 0 && iterations >= limits.maxIterations) return {
|
|
216
|
+
shouldContinue: false,
|
|
217
|
+
termination: "limit"
|
|
218
|
+
};
|
|
219
|
+
if (limits.maxNodes > 0 && nodesVisited >= limits.maxNodes) return {
|
|
220
|
+
shouldContinue: false,
|
|
221
|
+
termination: "limit"
|
|
222
|
+
};
|
|
223
|
+
if (limits.maxPaths > 0 && pathsFound >= limits.maxPaths) return {
|
|
224
|
+
shouldContinue: false,
|
|
225
|
+
termination: "limit"
|
|
226
|
+
};
|
|
227
|
+
return {
|
|
228
|
+
shouldContinue: true,
|
|
229
|
+
termination: "exhausted"
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Reconstruct path from collision point.
|
|
234
|
+
*
|
|
235
|
+
* Traces backwards through the predecessor maps of both frontiers from the
|
|
236
|
+
* collision node, then concatenates the two halves to form the full path.
|
|
237
|
+
*
|
|
238
|
+
* @param collisionNode - The node where the two frontiers met
|
|
239
|
+
* @param frontierA - Index of the first frontier
|
|
240
|
+
* @param frontierB - Index of the second frontier
|
|
241
|
+
* @param predecessors - Predecessor maps, one per frontier
|
|
242
|
+
* @param seeds - Seed nodes, one per frontier
|
|
243
|
+
* @returns The reconstructed path, or null if seeds are missing
|
|
244
|
+
*/
|
|
245
|
+
function reconstructPath$1(collisionNode, frontierA, frontierB, predecessors, seeds) {
|
|
246
|
+
const pathA = [collisionNode];
|
|
247
|
+
const predA = predecessors[frontierA];
|
|
248
|
+
if (predA !== void 0) {
|
|
249
|
+
let node = collisionNode;
|
|
250
|
+
let next = predA.get(node);
|
|
251
|
+
while (next !== null && next !== void 0) {
|
|
252
|
+
node = next;
|
|
253
|
+
pathA.unshift(node);
|
|
254
|
+
next = predA.get(node);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const pathB = [];
|
|
258
|
+
const predB = predecessors[frontierB];
|
|
259
|
+
if (predB !== void 0) {
|
|
260
|
+
let node = collisionNode;
|
|
261
|
+
let next = predB.get(node);
|
|
262
|
+
while (next !== null && next !== void 0) {
|
|
263
|
+
node = next;
|
|
264
|
+
pathB.push(node);
|
|
265
|
+
next = predB.get(node);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
const fullPath = [...pathA, ...pathB];
|
|
269
|
+
const seedA = seeds[frontierA];
|
|
270
|
+
const seedB = seeds[frontierB];
|
|
271
|
+
if (seedA === void 0 || seedB === void 0) return null;
|
|
272
|
+
return {
|
|
273
|
+
fromSeed: seedA,
|
|
274
|
+
toSeed: seedB,
|
|
275
|
+
nodes: fullPath
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Create an empty expansion result for early termination (e.g. no seeds given).
|
|
280
|
+
*
|
|
281
|
+
* @param algorithm - Name of the algorithm producing this result
|
|
282
|
+
* @param startTime - performance.now() timestamp taken before the algorithm began
|
|
283
|
+
* @returns An ExpansionResult with zero paths and zero stats
|
|
284
|
+
*/
|
|
285
|
+
function emptyResult$2(algorithm, startTime) {
|
|
286
|
+
return {
|
|
287
|
+
paths: [],
|
|
288
|
+
sampledNodes: /* @__PURE__ */ new Set(),
|
|
289
|
+
sampledEdges: /* @__PURE__ */ new Set(),
|
|
290
|
+
visitedPerFrontier: [],
|
|
291
|
+
stats: {
|
|
292
|
+
iterations: 0,
|
|
293
|
+
nodesVisited: 0,
|
|
294
|
+
edgesTraversed: 0,
|
|
295
|
+
pathsFound: 0,
|
|
296
|
+
durationMs: performance.now() - startTime,
|
|
297
|
+
algorithm,
|
|
298
|
+
termination: "exhausted"
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/expansion/base-core.ts
|
|
304
|
+
/**
|
|
305
|
+
* Default priority function — degree-ordered (DOME).
|
|
306
|
+
*
|
|
307
|
+
* Lower degree = higher priority, so sparse nodes are explored before hubs.
|
|
11
308
|
*/
|
|
12
309
|
function degreePriority(_nodeId, context) {
|
|
13
310
|
return context.degree;
|
|
14
311
|
}
|
|
15
312
|
/**
|
|
16
|
-
*
|
|
313
|
+
* Generator core of the BASE expansion algorithm.
|
|
17
314
|
*
|
|
18
|
-
*
|
|
315
|
+
* Yields GraphOp objects to request graph data, allowing the caller to
|
|
316
|
+
* provide a sync or async runner. The optional `graphRef` parameter is
|
|
317
|
+
* required when the priority function accesses `context.graph` — it is
|
|
318
|
+
* populated in sync mode by `base()`. In async mode (Phase 4+), a proxy
|
|
319
|
+
* graph may be supplied instead.
|
|
320
|
+
*
|
|
321
|
+
* @param graphMeta - Immutable graph metadata (directed, nodeCount, edgeCount)
|
|
19
322
|
* @param seeds - Seed nodes for expansion
|
|
20
|
-
* @param config - Expansion configuration
|
|
21
|
-
* @
|
|
323
|
+
* @param config - Expansion configuration (priority, limits, debug)
|
|
324
|
+
* @param graphRef - Optional real graph reference for context.graph in priority functions
|
|
325
|
+
* @returns An ExpansionResult with all discovered paths and statistics
|
|
22
326
|
*/
|
|
23
|
-
function
|
|
327
|
+
function* baseCore(graphMeta, seeds, config, graphRef) {
|
|
24
328
|
const startTime = performance.now();
|
|
25
329
|
const { maxNodes = 0, maxIterations = 0, maxPaths = 0, priority = degreePriority, debug = false } = config ?? {};
|
|
26
330
|
if (seeds.length === 0) return emptyResult$2("base", startTime);
|
|
@@ -40,7 +344,8 @@ function base(graph, seeds, config) {
|
|
|
40
344
|
predecessors[i]?.set(seedNode, null);
|
|
41
345
|
combinedVisited.set(seedNode, i);
|
|
42
346
|
allVisited.add(seedNode);
|
|
43
|
-
const
|
|
347
|
+
const seedDegree = yield* opDegree(seedNode);
|
|
348
|
+
const seedPriority = priority(seedNode, buildPriorityContext(seedNode, i, combinedVisited, allVisited, [], 0, seedDegree, graphRef));
|
|
44
349
|
queues[i]?.push({
|
|
45
350
|
nodeId: seedNode,
|
|
46
351
|
frontierIndex: i,
|
|
@@ -52,22 +357,17 @@ function base(graph, seeds, config) {
|
|
|
52
357
|
let iterations = 0;
|
|
53
358
|
let edgesTraversed = 0;
|
|
54
359
|
let termination = "exhausted";
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
if (maxNodes > 0 && allVisited.size >= maxNodes) {
|
|
61
|
-
termination = "limit";
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
if (maxPaths > 0 && discoveredPaths.length >= maxPaths) {
|
|
65
|
-
termination = "limit";
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
return true;
|
|
360
|
+
const limits = {
|
|
361
|
+
maxIterations,
|
|
362
|
+
maxNodes,
|
|
363
|
+
maxPaths
|
|
69
364
|
};
|
|
70
|
-
|
|
365
|
+
for (;;) {
|
|
366
|
+
const check = continueExpansion(iterations, allVisited.size, discoveredPaths.length, limits);
|
|
367
|
+
if (!check.shouldContinue) {
|
|
368
|
+
termination = check.termination;
|
|
369
|
+
break;
|
|
370
|
+
}
|
|
71
371
|
let lowestPriority = Number.POSITIVE_INFINITY;
|
|
72
372
|
let activeFrontier = -1;
|
|
73
373
|
for (let i = 0; i < numFrontiers; i++) {
|
|
@@ -111,7 +411,7 @@ function base(graph, seeds, config) {
|
|
|
111
411
|
}
|
|
112
412
|
}
|
|
113
413
|
}
|
|
114
|
-
const neighbours =
|
|
414
|
+
const neighbours = yield* opNeighbours(nodeId);
|
|
115
415
|
for (const neighbour of neighbours) {
|
|
116
416
|
edgesTraversed++;
|
|
117
417
|
const [s, t] = nodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];
|
|
@@ -121,9 +421,10 @@ function base(graph, seeds, config) {
|
|
|
121
421
|
sampledEdgeMap.set(s, targets);
|
|
122
422
|
}
|
|
123
423
|
targets.add(t);
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
const
|
|
424
|
+
const fv = visitedByFrontier[activeFrontier];
|
|
425
|
+
if (fv === void 0 || fv.has(neighbour)) continue;
|
|
426
|
+
const neighbourDegree = yield* opDegree(neighbour);
|
|
427
|
+
const neighbourPriority = priority(neighbour, buildPriorityContext(neighbour, activeFrontier, combinedVisited, allVisited, discoveredPaths, iterations + 1, neighbourDegree, graphRef));
|
|
127
428
|
queue.push({
|
|
128
429
|
nodeId: neighbour,
|
|
129
430
|
frontierIndex: activeFrontier,
|
|
@@ -153,12 +454,50 @@ function base(graph, seeds, config) {
|
|
|
153
454
|
};
|
|
154
455
|
}
|
|
155
456
|
/**
|
|
156
|
-
* Create
|
|
457
|
+
* Create a sentinel ReadableGraph that throws if any member is accessed.
|
|
458
|
+
*
|
|
459
|
+
* Used in async mode when no graphRef is provided. Gives a clear error
|
|
460
|
+
* message rather than silently returning incorrect results if a priority
|
|
461
|
+
* function attempts to access `context.graph` before Phase 4b introduces
|
|
462
|
+
* a real async proxy.
|
|
157
463
|
*/
|
|
158
|
-
function
|
|
464
|
+
function makeNoGraphSentinel() {
|
|
465
|
+
const msg = "Priority function accessed context.graph in async mode without a graph proxy. Pass a graphRef or use a priority function that does not access context.graph.";
|
|
466
|
+
const fail = () => {
|
|
467
|
+
throw new Error(msg);
|
|
468
|
+
};
|
|
159
469
|
return {
|
|
160
|
-
|
|
161
|
-
|
|
470
|
+
get directed() {
|
|
471
|
+
return fail();
|
|
472
|
+
},
|
|
473
|
+
get nodeCount() {
|
|
474
|
+
return fail();
|
|
475
|
+
},
|
|
476
|
+
get edgeCount() {
|
|
477
|
+
return fail();
|
|
478
|
+
},
|
|
479
|
+
hasNode: fail,
|
|
480
|
+
getNode: fail,
|
|
481
|
+
nodeIds: fail,
|
|
482
|
+
neighbours: fail,
|
|
483
|
+
degree: fail,
|
|
484
|
+
getEdge: fail,
|
|
485
|
+
edges: fail
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Build a PriorityContext for a node using a pre-fetched degree.
|
|
490
|
+
*
|
|
491
|
+
* When `graphRef` is provided (sync mode), it is used as `context.graph` so
|
|
492
|
+
* priority functions can access the graph directly. When it is absent (async
|
|
493
|
+
* mode), a Proxy is used in its place that throws a clear error if any
|
|
494
|
+
* property is accessed — this prevents silent failures until Phase 4b
|
|
495
|
+
* introduces a real async proxy graph.
|
|
496
|
+
*/
|
|
497
|
+
function buildPriorityContext(_nodeId, frontierIndex, combinedVisited, allVisited, discoveredPaths, iteration, degree, graphRef) {
|
|
498
|
+
return {
|
|
499
|
+
graph: graphRef ?? makeNoGraphSentinel(),
|
|
500
|
+
degree,
|
|
162
501
|
frontierIndex,
|
|
163
502
|
visitedByFrontier: combinedVisited,
|
|
164
503
|
allVisited,
|
|
@@ -166,61 +505,55 @@ function createPriorityContext(graph, nodeId, frontierIndex, combinedVisited, al
|
|
|
166
505
|
iteration
|
|
167
506
|
};
|
|
168
507
|
}
|
|
508
|
+
//#endregion
|
|
509
|
+
//#region src/expansion/base.ts
|
|
169
510
|
/**
|
|
170
|
-
*
|
|
511
|
+
* Run BASE expansion synchronously.
|
|
512
|
+
*
|
|
513
|
+
* Delegates to baseCore + runSync. Behaviour is identical to the previous
|
|
514
|
+
* direct implementation — all existing callers are unaffected.
|
|
515
|
+
*
|
|
516
|
+
* @param graph - Source graph
|
|
517
|
+
* @param seeds - Seed nodes for expansion
|
|
518
|
+
* @param config - Expansion configuration
|
|
519
|
+
* @returns Expansion result with discovered paths
|
|
171
520
|
*/
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
while (next !== null && next !== void 0) {
|
|
179
|
-
node = next;
|
|
180
|
-
pathA.unshift(node);
|
|
181
|
-
next = predA.get(node);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
const pathB = [];
|
|
185
|
-
const predB = predecessors[frontierB];
|
|
186
|
-
if (predB !== void 0) {
|
|
187
|
-
let node = collisionNode;
|
|
188
|
-
let next = predB.get(node);
|
|
189
|
-
while (next !== null && next !== void 0) {
|
|
190
|
-
node = next;
|
|
191
|
-
pathB.push(node);
|
|
192
|
-
next = predB.get(node);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
const fullPath = [...pathA, ...pathB];
|
|
196
|
-
const seedA = seeds[frontierA];
|
|
197
|
-
const seedB = seeds[frontierB];
|
|
198
|
-
if (seedA === void 0 || seedB === void 0) return null;
|
|
199
|
-
return {
|
|
200
|
-
fromSeed: seedA,
|
|
201
|
-
toSeed: seedB,
|
|
202
|
-
nodes: fullPath
|
|
203
|
-
};
|
|
521
|
+
function base(graph, seeds, config) {
|
|
522
|
+
return runSync(baseCore({
|
|
523
|
+
directed: graph.directed,
|
|
524
|
+
nodeCount: graph.nodeCount,
|
|
525
|
+
edgeCount: graph.edgeCount
|
|
526
|
+
}, seeds, config, graph), graph);
|
|
204
527
|
}
|
|
205
528
|
/**
|
|
206
|
-
*
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
529
|
+
* Run BASE expansion asynchronously.
|
|
530
|
+
*
|
|
531
|
+
* Delegates to baseCore + runAsync. Supports:
|
|
532
|
+
* - Cancellation via AbortSignal (config.signal)
|
|
533
|
+
* - Progress callbacks (config.onProgress)
|
|
534
|
+
* - Custom cooperative yield strategies (config.yieldStrategy)
|
|
535
|
+
*
|
|
536
|
+
* Note: priority functions that access `context.graph` are not supported in
|
|
537
|
+
* async mode without a graph proxy (Phase 4b). The default degree-based
|
|
538
|
+
* priority (DOME) does not access context.graph and works correctly.
|
|
539
|
+
*
|
|
540
|
+
* @param graph - Async source graph
|
|
541
|
+
* @param seeds - Seed nodes for expansion
|
|
542
|
+
* @param config - Expansion and async runner configuration
|
|
543
|
+
* @returns Promise resolving to the expansion result
|
|
544
|
+
*/
|
|
545
|
+
async function baseAsync(graph, seeds, config) {
|
|
546
|
+
const [nodeCount, edgeCount] = await Promise.all([graph.nodeCount, graph.edgeCount]);
|
|
547
|
+
const gen = baseCore({
|
|
548
|
+
directed: graph.directed,
|
|
549
|
+
nodeCount,
|
|
550
|
+
edgeCount
|
|
551
|
+
}, seeds, config);
|
|
552
|
+
const runnerOptions = {};
|
|
553
|
+
if (config?.signal !== void 0) runnerOptions.signal = config.signal;
|
|
554
|
+
if (config?.onProgress !== void 0) runnerOptions.onProgress = config.onProgress;
|
|
555
|
+
if (config?.yieldStrategy !== void 0) runnerOptions.yieldStrategy = config.yieldStrategy;
|
|
556
|
+
return runAsync(gen, graph, runnerOptions);
|
|
224
557
|
}
|
|
225
558
|
//#endregion
|
|
226
559
|
//#region src/expansion/dome.ts
|
|
@@ -3000,6 +3333,6 @@ function filterSubgraph(graph, options) {
|
|
|
3000
3333
|
return result;
|
|
3001
3334
|
}
|
|
3002
3335
|
//#endregion
|
|
3003
|
-
export { AdjacencyMapGraph, GPUContext, GPUNotAvailableError, PriorityQueue, _computeMean, adamicAdar, adaptive, approximateClusteringCoefficient, assertWebGPUAvailable, base, batchClusteringCoefficients, betweenness, bfs, bfsWithPath, communicability, computeJaccard, computeTrussNumbers, cosine, countEdgesOfType, countNodesOfType, createGPUContext, createResultBuffer, csrToGPUBuffers, degreeSum, detectWebGPU, dfs, dfsPriority, dfsPriorityFn, dfsWithPath, dome, domeHighDegree, edge, entropyFromCounts, enumerateMotifs, enumerateMotifsWithInstances, etch, extractEgoNetwork, extractInducedSubgraph, extractKCore, extractKTruss, filterSubgraph, flux, frontierBalanced, fuse, getGPUContext, getMotifName, graphToCSR, grasp, hae, hittingTime, hubPromoted, isWebGPUAvailable, jaccard, jaccardArithmetic, kHop, katz, lace, localClusteringCoefficient, localTypeEntropy, maze, miniBatchKMeans, neighbourIntersection, neighbourOverlap, neighbourSet, normaliseFeatures, normaliseFeatures as zScoreNormalise, normalisedEntropy, notch, overlapCoefficient, pagerank, parse, pipe, randomPriority, randomRanking, randomWalk, reach, readBufferToCPU, resistanceDistance, resourceAllocation, sage, scale, shannonEntropy, shortest, sift, skew, sorensen, span, standardBfs, stratified, tide, warp, widestPath };
|
|
3336
|
+
export { AdjacencyMapGraph, GPUContext, GPUNotAvailableError, PriorityQueue, _computeMean, adamicAdar, adaptive, approximateClusteringCoefficient, assertWebGPUAvailable, base, baseAsync, batchClusteringCoefficients, betweenness, bfs, bfsWithPath, communicability, computeJaccard, computeTrussNumbers, cosine, countEdgesOfType, countNodesOfType, createGPUContext, createResultBuffer, csrToGPUBuffers, degreeSum, detectWebGPU, dfs, dfsPriority, dfsPriorityFn, dfsWithPath, dome, domeHighDegree, edge, entropyFromCounts, enumerateMotifs, enumerateMotifsWithInstances, etch, extractEgoNetwork, extractInducedSubgraph, extractKCore, extractKTruss, filterSubgraph, flux, frontierBalanced, fuse, getGPUContext, getMotifName, graphToCSR, grasp, hae, hittingTime, hubPromoted, isWebGPUAvailable, jaccard, jaccardArithmetic, kHop, katz, lace, localClusteringCoefficient, localTypeEntropy, maze, miniBatchKMeans, neighbourIntersection, neighbourOverlap, neighbourSet, normaliseFeatures, normaliseFeatures as zScoreNormalise, normalisedEntropy, notch, overlapCoefficient, pagerank, parse, pipe, randomPriority, randomRanking, randomWalk, reach, readBufferToCPU, resistanceDistance, resourceAllocation, sage, scale, shannonEntropy, shortest, sift, skew, sorensen, span, standardBfs, stratified, tide, warp, widestPath };
|
|
3004
3337
|
|
|
3005
3338
|
//# sourceMappingURL=index.js.map
|