graphwise 1.7.0 → 1.8.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 +81 -30
- package/dist/async/index.cjs +243 -0
- package/dist/async/index.cjs.map +1 -0
- package/dist/async/index.js +230 -0
- package/dist/async/index.js.map +1 -0
- package/dist/expansion/dfs-priority.d.ts +11 -0
- package/dist/expansion/dfs-priority.d.ts.map +1 -1
- package/dist/expansion/dome.d.ts +20 -0
- package/dist/expansion/dome.d.ts.map +1 -1
- package/dist/expansion/edge.d.ts +18 -0
- package/dist/expansion/edge.d.ts.map +1 -1
- package/dist/expansion/flux.d.ts +16 -0
- package/dist/expansion/flux.d.ts.map +1 -1
- package/dist/expansion/frontier-balanced.d.ts +11 -0
- package/dist/expansion/frontier-balanced.d.ts.map +1 -1
- package/dist/expansion/fuse.d.ts +16 -0
- package/dist/expansion/fuse.d.ts.map +1 -1
- package/dist/expansion/hae.d.ts +16 -0
- package/dist/expansion/hae.d.ts.map +1 -1
- package/dist/expansion/lace.d.ts +16 -0
- package/dist/expansion/lace.d.ts.map +1 -1
- package/dist/expansion/maze.d.ts +17 -0
- package/dist/expansion/maze.d.ts.map +1 -1
- package/dist/expansion/pipe.d.ts +16 -0
- package/dist/expansion/pipe.d.ts.map +1 -1
- package/dist/expansion/random-priority.d.ts +18 -0
- package/dist/expansion/random-priority.d.ts.map +1 -1
- package/dist/expansion/reach.d.ts +17 -0
- package/dist/expansion/reach.d.ts.map +1 -1
- package/dist/expansion/sage.d.ts +15 -0
- package/dist/expansion/sage.d.ts.map +1 -1
- package/dist/expansion/sift.d.ts +16 -0
- package/dist/expansion/sift.d.ts.map +1 -1
- package/dist/expansion/standard-bfs.d.ts +11 -0
- package/dist/expansion/standard-bfs.d.ts.map +1 -1
- package/dist/expansion/tide.d.ts +16 -0
- package/dist/expansion/tide.d.ts.map +1 -1
- package/dist/expansion/warp.d.ts +16 -0
- package/dist/expansion/warp.d.ts.map +1 -1
- package/dist/index/index.cjs +842 -215
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +793 -211
- package/dist/index/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/ranking/mi/adamic-adar.d.ts +8 -0
- package/dist/ranking/mi/adamic-adar.d.ts.map +1 -1
- package/dist/ranking/mi/adaptive.d.ts +8 -0
- package/dist/ranking/mi/adaptive.d.ts.map +1 -1
- package/dist/ranking/mi/cosine.d.ts +7 -0
- package/dist/ranking/mi/cosine.d.ts.map +1 -1
- package/dist/ranking/mi/etch.d.ts +8 -0
- package/dist/ranking/mi/etch.d.ts.map +1 -1
- package/dist/ranking/mi/hub-promoted.d.ts +7 -0
- package/dist/ranking/mi/hub-promoted.d.ts.map +1 -1
- package/dist/ranking/mi/jaccard.d.ts +7 -0
- package/dist/ranking/mi/jaccard.d.ts.map +1 -1
- package/dist/ranking/mi/notch.d.ts +8 -0
- package/dist/ranking/mi/notch.d.ts.map +1 -1
- package/dist/ranking/mi/overlap-coefficient.d.ts +7 -0
- package/dist/ranking/mi/overlap-coefficient.d.ts.map +1 -1
- package/dist/ranking/mi/resource-allocation.d.ts +8 -0
- package/dist/ranking/mi/resource-allocation.d.ts.map +1 -1
- package/dist/ranking/mi/scale.d.ts +7 -0
- package/dist/ranking/mi/scale.d.ts.map +1 -1
- package/dist/ranking/mi/skew.d.ts +7 -0
- package/dist/ranking/mi/skew.d.ts.map +1 -1
- package/dist/ranking/mi/sorensen.d.ts +7 -0
- package/dist/ranking/mi/sorensen.d.ts.map +1 -1
- package/dist/ranking/mi/span.d.ts +8 -0
- package/dist/ranking/mi/span.d.ts.map +1 -1
- package/dist/ranking/mi/types.d.ts +12 -0
- package/dist/ranking/mi/types.d.ts.map +1 -1
- package/dist/ranking/parse.d.ts +24 -1
- package/dist/ranking/parse.d.ts.map +1 -1
- package/package.json +6 -1
package/dist/index/index.cjs
CHANGED
|
@@ -6,199 +6,7 @@ const require_kmeans = require("../kmeans-BIgSyGKu.cjs");
|
|
|
6
6
|
const require_utils = require("../utils/index.cjs");
|
|
7
7
|
const require_seeds = require("../seeds/index.cjs");
|
|
8
8
|
const require_gpu = require("../gpu/index.cjs");
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Async utility functions.
|
|
12
|
-
*
|
|
13
|
-
* @module async/utils
|
|
14
|
-
*/
|
|
15
|
-
/** Collect an AsyncIterable into a readonly array. */
|
|
16
|
-
async function collectAsyncIterable(iter) {
|
|
17
|
-
const result = [];
|
|
18
|
-
for await (const item of iter) result.push(item);
|
|
19
|
-
return result;
|
|
20
|
-
}
|
|
21
|
-
/** Default yield strategy: setTimeout(0) to yield to the event loop. */
|
|
22
|
-
function defaultYieldStrategy() {
|
|
23
|
-
return new Promise((r) => {
|
|
24
|
-
setTimeout(r, 0);
|
|
25
|
-
});
|
|
26
|
-
}
|
|
27
|
-
//#endregion
|
|
28
|
-
//#region src/async/runners.ts
|
|
29
|
-
/**
|
|
30
|
-
* Resolve a single GraphOp against a synchronous ReadableGraph.
|
|
31
|
-
*
|
|
32
|
-
* Returns a tagged GraphOpResponse so the receiving generator can narrow
|
|
33
|
-
* the result type without type assertions.
|
|
34
|
-
*
|
|
35
|
-
* @param graph - The synchronous graph to query
|
|
36
|
-
* @param op - The operation to resolve
|
|
37
|
-
* @returns The tagged response
|
|
38
|
-
*/
|
|
39
|
-
function resolveSyncOp(graph, op) {
|
|
40
|
-
switch (op.tag) {
|
|
41
|
-
case "neighbours": return {
|
|
42
|
-
tag: "neighbours",
|
|
43
|
-
value: Array.from(graph.neighbours(op.id, op.direction))
|
|
44
|
-
};
|
|
45
|
-
case "degree": return {
|
|
46
|
-
tag: "degree",
|
|
47
|
-
value: graph.degree(op.id, op.direction)
|
|
48
|
-
};
|
|
49
|
-
case "getNode": return {
|
|
50
|
-
tag: "getNode",
|
|
51
|
-
value: graph.getNode(op.id)
|
|
52
|
-
};
|
|
53
|
-
case "getEdge": return {
|
|
54
|
-
tag: "getEdge",
|
|
55
|
-
value: graph.getEdge(op.source, op.target)
|
|
56
|
-
};
|
|
57
|
-
case "hasNode": return {
|
|
58
|
-
tag: "hasNode",
|
|
59
|
-
value: graph.hasNode(op.id)
|
|
60
|
-
};
|
|
61
|
-
case "yield": return { tag: "yield" };
|
|
62
|
-
case "progress": return { tag: "progress" };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* Drive a generator to completion using a synchronous graph.
|
|
67
|
-
*
|
|
68
|
-
* The generator yields GraphOp requests; each is resolved immediately
|
|
69
|
-
* against the graph and the tagged response is fed back via gen.next().
|
|
70
|
-
*
|
|
71
|
-
* @param gen - The generator to drive
|
|
72
|
-
* @param graph - The graph to resolve ops against
|
|
73
|
-
* @returns The generator's return value
|
|
74
|
-
*/
|
|
75
|
-
function runSync(gen, graph) {
|
|
76
|
-
let step = gen.next();
|
|
77
|
-
while (step.done !== true) {
|
|
78
|
-
const response = resolveSyncOp(graph, step.value);
|
|
79
|
-
step = gen.next(response);
|
|
80
|
-
}
|
|
81
|
-
return step.value;
|
|
82
|
-
}
|
|
83
|
-
/**
|
|
84
|
-
* Resolve a single GraphOp against an async ReadableGraph.
|
|
85
|
-
*
|
|
86
|
-
* AsyncIterables (neighbours) are collected into readonly arrays so the
|
|
87
|
-
* generator receives the same value type as in sync mode. Returns a tagged
|
|
88
|
-
* GraphOpResponse for type-safe narrowing without assertions.
|
|
89
|
-
*
|
|
90
|
-
* @param graph - The async graph to query
|
|
91
|
-
* @param op - The operation to resolve
|
|
92
|
-
* @returns A promise resolving to the tagged response
|
|
93
|
-
*/
|
|
94
|
-
async function resolveAsyncOp(graph, op) {
|
|
95
|
-
switch (op.tag) {
|
|
96
|
-
case "neighbours": return {
|
|
97
|
-
tag: "neighbours",
|
|
98
|
-
value: await collectAsyncIterable(graph.neighbours(op.id, op.direction))
|
|
99
|
-
};
|
|
100
|
-
case "degree": return {
|
|
101
|
-
tag: "degree",
|
|
102
|
-
value: await graph.degree(op.id, op.direction)
|
|
103
|
-
};
|
|
104
|
-
case "getNode": return {
|
|
105
|
-
tag: "getNode",
|
|
106
|
-
value: await graph.getNode(op.id)
|
|
107
|
-
};
|
|
108
|
-
case "getEdge": return {
|
|
109
|
-
tag: "getEdge",
|
|
110
|
-
value: await graph.getEdge(op.source, op.target)
|
|
111
|
-
};
|
|
112
|
-
case "hasNode": return {
|
|
113
|
-
tag: "hasNode",
|
|
114
|
-
value: await graph.hasNode(op.id)
|
|
115
|
-
};
|
|
116
|
-
case "yield": return { tag: "yield" };
|
|
117
|
-
case "progress": return { tag: "progress" };
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
/**
|
|
121
|
-
* Drive a generator to completion using an async graph.
|
|
122
|
-
*
|
|
123
|
-
* Extends sync semantics with:
|
|
124
|
-
* - Cancellation via AbortSignal (throws DOMException "AbortError")
|
|
125
|
-
* - Cooperative yielding at `yield` ops (calls yieldStrategy)
|
|
126
|
-
* - Progress callbacks at `progress` ops (may be async for backpressure)
|
|
127
|
-
* - Error propagation: graph errors are forwarded via gen.throw(); if the
|
|
128
|
-
* generator does not handle them, they propagate to the caller
|
|
129
|
-
*
|
|
130
|
-
* @param gen - The generator to drive
|
|
131
|
-
* @param graph - The async graph to resolve ops against
|
|
132
|
-
* @param options - Runner configuration
|
|
133
|
-
* @returns A promise resolving to the generator's return value
|
|
134
|
-
*/
|
|
135
|
-
async function runAsync(gen, graph, options) {
|
|
136
|
-
const signal = options?.signal;
|
|
137
|
-
const onProgress = options?.onProgress;
|
|
138
|
-
const yieldStrategy = options?.yieldStrategy ?? defaultYieldStrategy;
|
|
139
|
-
let step = gen.next();
|
|
140
|
-
while (step.done !== true) {
|
|
141
|
-
if (signal?.aborted === true) {
|
|
142
|
-
const abortError = new DOMException("Aborted", "AbortError");
|
|
143
|
-
try {
|
|
144
|
-
gen.throw(abortError);
|
|
145
|
-
} catch {
|
|
146
|
-
throw abortError;
|
|
147
|
-
}
|
|
148
|
-
throw abortError;
|
|
149
|
-
}
|
|
150
|
-
const op = step.value;
|
|
151
|
-
if (op.tag === "yield") {
|
|
152
|
-
await yieldStrategy();
|
|
153
|
-
step = gen.next({ tag: "yield" });
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (op.tag === "progress") {
|
|
157
|
-
if (onProgress !== void 0) {
|
|
158
|
-
const maybePromise = onProgress(op.stats);
|
|
159
|
-
if (maybePromise instanceof Promise) await maybePromise;
|
|
160
|
-
}
|
|
161
|
-
step = gen.next({ tag: "progress" });
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
let response;
|
|
165
|
-
try {
|
|
166
|
-
response = await resolveAsyncOp(graph, op);
|
|
167
|
-
} catch (error) {
|
|
168
|
-
step = gen.throw(error);
|
|
169
|
-
continue;
|
|
170
|
-
}
|
|
171
|
-
step = gen.next(response);
|
|
172
|
-
}
|
|
173
|
-
return step.value;
|
|
174
|
-
}
|
|
175
|
-
//#endregion
|
|
176
|
-
//#region src/async/ops.ts
|
|
177
|
-
function* opNeighbours(id, direction) {
|
|
178
|
-
const response = yield direction !== void 0 ? {
|
|
179
|
-
tag: "neighbours",
|
|
180
|
-
id,
|
|
181
|
-
direction
|
|
182
|
-
} : {
|
|
183
|
-
tag: "neighbours",
|
|
184
|
-
id
|
|
185
|
-
};
|
|
186
|
-
if (response.tag !== "neighbours") throw new TypeError(`Expected neighbours response, got ${response.tag}`);
|
|
187
|
-
return response.value;
|
|
188
|
-
}
|
|
189
|
-
function* opDegree(id, direction) {
|
|
190
|
-
const response = yield direction !== void 0 ? {
|
|
191
|
-
tag: "degree",
|
|
192
|
-
id,
|
|
193
|
-
direction
|
|
194
|
-
} : {
|
|
195
|
-
tag: "degree",
|
|
196
|
-
id
|
|
197
|
-
};
|
|
198
|
-
if (response.tag !== "degree") throw new TypeError(`Expected degree response, got ${response.tag}`);
|
|
199
|
-
return response.value;
|
|
200
|
-
}
|
|
201
|
-
//#endregion
|
|
9
|
+
const require_async = require("../async/index.cjs");
|
|
202
10
|
//#region src/expansion/base-helpers.ts
|
|
203
11
|
/**
|
|
204
12
|
* Check whether expansion should continue given current progress.
|
|
@@ -345,7 +153,7 @@ function* baseCore(graphMeta, seeds, config, graphRef) {
|
|
|
345
153
|
predecessors[i]?.set(seedNode, null);
|
|
346
154
|
combinedVisited.set(seedNode, i);
|
|
347
155
|
allVisited.add(seedNode);
|
|
348
|
-
const seedDegree = yield* opDegree(seedNode);
|
|
156
|
+
const seedDegree = yield* require_async.opDegree(seedNode);
|
|
349
157
|
const seedPriority = priority(seedNode, buildPriorityContext(seedNode, i, combinedVisited, allVisited, [], 0, seedDegree, graphRef));
|
|
350
158
|
queues[i]?.push({
|
|
351
159
|
nodeId: seedNode,
|
|
@@ -412,7 +220,7 @@ function* baseCore(graphMeta, seeds, config, graphRef) {
|
|
|
412
220
|
}
|
|
413
221
|
}
|
|
414
222
|
}
|
|
415
|
-
const neighbours = yield* opNeighbours(nodeId);
|
|
223
|
+
const neighbours = yield* require_async.opNeighbours(nodeId);
|
|
416
224
|
for (const neighbour of neighbours) {
|
|
417
225
|
edgesTraversed++;
|
|
418
226
|
const [s, t] = nodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];
|
|
@@ -424,7 +232,7 @@ function* baseCore(graphMeta, seeds, config, graphRef) {
|
|
|
424
232
|
targets.add(t);
|
|
425
233
|
const fv = visitedByFrontier[activeFrontier];
|
|
426
234
|
if (fv === void 0 || fv.has(neighbour)) continue;
|
|
427
|
-
const neighbourDegree = yield* opDegree(neighbour);
|
|
235
|
+
const neighbourDegree = yield* require_async.opDegree(neighbour);
|
|
428
236
|
const neighbourPriority = priority(neighbour, buildPriorityContext(neighbour, activeFrontier, combinedVisited, allVisited, discoveredPaths, iterations + 1, neighbourDegree, graphRef));
|
|
429
237
|
queue.push({
|
|
430
238
|
nodeId: neighbour,
|
|
@@ -520,7 +328,7 @@ function buildPriorityContext(_nodeId, frontierIndex, combinedVisited, allVisite
|
|
|
520
328
|
* @returns Expansion result with discovered paths
|
|
521
329
|
*/
|
|
522
330
|
function base(graph, seeds, config) {
|
|
523
|
-
return runSync(baseCore({
|
|
331
|
+
return require_async.runSync(baseCore({
|
|
524
332
|
directed: graph.directed,
|
|
525
333
|
nodeCount: graph.nodeCount,
|
|
526
334
|
edgeCount: graph.edgeCount
|
|
@@ -554,11 +362,23 @@ async function baseAsync(graph, seeds, config) {
|
|
|
554
362
|
if (config?.signal !== void 0) runnerOptions.signal = config.signal;
|
|
555
363
|
if (config?.onProgress !== void 0) runnerOptions.onProgress = config.onProgress;
|
|
556
364
|
if (config?.yieldStrategy !== void 0) runnerOptions.yieldStrategy = config.yieldStrategy;
|
|
557
|
-
return runAsync(gen, graph, runnerOptions);
|
|
365
|
+
return require_async.runAsync(gen, graph, runnerOptions);
|
|
558
366
|
}
|
|
559
367
|
//#endregion
|
|
560
368
|
//#region src/expansion/dome.ts
|
|
561
369
|
/**
|
|
370
|
+
* DOME priority: lower degree is expanded first.
|
|
371
|
+
*/
|
|
372
|
+
function domePriority(_nodeId, context) {
|
|
373
|
+
return context.degree;
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* DOME high-degree priority: negate degree to prioritise high-degree nodes.
|
|
377
|
+
*/
|
|
378
|
+
function domeHighDegreePriority(_nodeId, context) {
|
|
379
|
+
return -context.degree;
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
562
382
|
* Run DOME expansion (degree-ordered).
|
|
563
383
|
*
|
|
564
384
|
* @param graph - Source graph
|
|
@@ -567,24 +387,46 @@ async function baseAsync(graph, seeds, config) {
|
|
|
567
387
|
* @returns Expansion result with discovered paths
|
|
568
388
|
*/
|
|
569
389
|
function dome(graph, seeds, config) {
|
|
570
|
-
const domePriority = (nodeId, context) => {
|
|
571
|
-
return context.degree;
|
|
572
|
-
};
|
|
573
390
|
return base(graph, seeds, {
|
|
574
391
|
...config,
|
|
575
392
|
priority: domePriority
|
|
576
393
|
});
|
|
577
394
|
}
|
|
578
395
|
/**
|
|
396
|
+
* Run DOME expansion asynchronously (degree-ordered).
|
|
397
|
+
*
|
|
398
|
+
* @param graph - Async source graph
|
|
399
|
+
* @param seeds - Seed nodes for expansion
|
|
400
|
+
* @param config - Expansion and async runner configuration
|
|
401
|
+
* @returns Promise resolving to the expansion result
|
|
402
|
+
*/
|
|
403
|
+
async function domeAsync(graph, seeds, config) {
|
|
404
|
+
return baseAsync(graph, seeds, {
|
|
405
|
+
...config,
|
|
406
|
+
priority: domePriority
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
579
410
|
* DOME with reverse priority (high degree first).
|
|
580
411
|
*/
|
|
581
412
|
function domeHighDegree(graph, seeds, config) {
|
|
582
|
-
const domePriority = (nodeId, context) => {
|
|
583
|
-
return -context.degree;
|
|
584
|
-
};
|
|
585
413
|
return base(graph, seeds, {
|
|
586
414
|
...config,
|
|
587
|
-
priority:
|
|
415
|
+
priority: domeHighDegreePriority
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Run DOME high-degree expansion asynchronously (high degree first).
|
|
420
|
+
*
|
|
421
|
+
* @param graph - Async source graph
|
|
422
|
+
* @param seeds - Seed nodes for expansion
|
|
423
|
+
* @param config - Expansion and async runner configuration
|
|
424
|
+
* @returns Promise resolving to the expansion result
|
|
425
|
+
*/
|
|
426
|
+
async function domeHighDegreeAsync(graph, seeds, config) {
|
|
427
|
+
return baseAsync(graph, seeds, {
|
|
428
|
+
...config,
|
|
429
|
+
priority: domeHighDegreePriority
|
|
588
430
|
});
|
|
589
431
|
}
|
|
590
432
|
//#endregion
|
|
@@ -629,6 +471,26 @@ function hae(graph, seeds, config) {
|
|
|
629
471
|
priority: createHAEPriority(typeMapper)
|
|
630
472
|
});
|
|
631
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Run HAE expansion asynchronously.
|
|
476
|
+
*
|
|
477
|
+
* Note: the HAE priority function accesses `context.graph` to retrieve
|
|
478
|
+
* neighbour types. Full async equivalence requires PriorityContext
|
|
479
|
+
* refactoring (Phase 4b deferred). This export establishes the async API
|
|
480
|
+
* surface; use with a `wrapAsync`-wrapped sync graph for testing.
|
|
481
|
+
*
|
|
482
|
+
* @param graph - Async source graph
|
|
483
|
+
* @param seeds - Seed nodes for expansion
|
|
484
|
+
* @param config - HAE configuration combined with async runner options
|
|
485
|
+
* @returns Promise resolving to the expansion result
|
|
486
|
+
*/
|
|
487
|
+
async function haeAsync(graph, seeds, config) {
|
|
488
|
+
const typeMapper = config?.typeMapper ?? defaultTypeMapper$1;
|
|
489
|
+
return baseAsync(graph, seeds, {
|
|
490
|
+
...config,
|
|
491
|
+
priority: createHAEPriority(typeMapper)
|
|
492
|
+
});
|
|
493
|
+
}
|
|
632
494
|
//#endregion
|
|
633
495
|
//#region src/expansion/edge.ts
|
|
634
496
|
/** Default type mapper: reads `node.type`, falling back to "default". */
|
|
@@ -650,6 +512,27 @@ function edge(graph, seeds, config) {
|
|
|
650
512
|
typeMapper: defaultTypeMapper
|
|
651
513
|
});
|
|
652
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Run EDGE expansion asynchronously.
|
|
517
|
+
*
|
|
518
|
+
* Delegates to `haeAsync` with the default `node.type` mapper.
|
|
519
|
+
*
|
|
520
|
+
* Note: the HAE priority function accesses `context.graph` to retrieve
|
|
521
|
+
* neighbour types. Full async equivalence requires PriorityContext
|
|
522
|
+
* refactoring (Phase 4b deferred). This export establishes the async API
|
|
523
|
+
* surface; use with a `wrapAsync`-wrapped sync graph for testing.
|
|
524
|
+
*
|
|
525
|
+
* @param graph - Async source graph
|
|
526
|
+
* @param seeds - Seed nodes for expansion
|
|
527
|
+
* @param config - Expansion and async runner configuration
|
|
528
|
+
* @returns Promise resolving to the expansion result
|
|
529
|
+
*/
|
|
530
|
+
async function edgeAsync(graph, seeds, config) {
|
|
531
|
+
return haeAsync(graph, seeds, {
|
|
532
|
+
...config,
|
|
533
|
+
typeMapper: defaultTypeMapper
|
|
534
|
+
});
|
|
535
|
+
}
|
|
653
536
|
//#endregion
|
|
654
537
|
//#region src/expansion/pipe.ts
|
|
655
538
|
/**
|
|
@@ -685,6 +568,25 @@ function pipe(graph, seeds, config) {
|
|
|
685
568
|
priority: pipePriority
|
|
686
569
|
});
|
|
687
570
|
}
|
|
571
|
+
/**
|
|
572
|
+
* Run PIPE expansion asynchronously.
|
|
573
|
+
*
|
|
574
|
+
* Note: the PIPE priority function accesses `context.graph` to retrieve
|
|
575
|
+
* neighbour lists. Full async equivalence requires PriorityContext
|
|
576
|
+
* refactoring (Phase 4b deferred). This export establishes the async API
|
|
577
|
+
* surface; use with a `wrapAsync`-wrapped sync graph for testing.
|
|
578
|
+
*
|
|
579
|
+
* @param graph - Async source graph
|
|
580
|
+
* @param seeds - Seed nodes for expansion
|
|
581
|
+
* @param config - Expansion and async runner configuration
|
|
582
|
+
* @returns Promise resolving to the expansion result
|
|
583
|
+
*/
|
|
584
|
+
async function pipeAsync(graph, seeds, config) {
|
|
585
|
+
return baseAsync(graph, seeds, {
|
|
586
|
+
...config,
|
|
587
|
+
priority: pipePriority
|
|
588
|
+
});
|
|
589
|
+
}
|
|
688
590
|
//#endregion
|
|
689
591
|
//#region src/expansion/priority-helpers.ts
|
|
690
592
|
/**
|
|
@@ -781,6 +683,34 @@ function sage(graph, seeds, config) {
|
|
|
781
683
|
priority: sagePriority
|
|
782
684
|
});
|
|
783
685
|
}
|
|
686
|
+
/**
|
|
687
|
+
* Run SAGE expansion asynchronously.
|
|
688
|
+
*
|
|
689
|
+
* Creates fresh closure state (salienceCounts, phase tracking) for this
|
|
690
|
+
* invocation. The SAGE priority function does not access `context.graph`
|
|
691
|
+
* directly, so it is safe to use in async mode via `baseAsync`.
|
|
692
|
+
*
|
|
693
|
+
* @param graph - Async source graph
|
|
694
|
+
* @param seeds - Seed nodes for expansion
|
|
695
|
+
* @param config - Expansion and async runner configuration
|
|
696
|
+
* @returns Promise resolving to the expansion result
|
|
697
|
+
*/
|
|
698
|
+
async function sageAsync(graph, seeds, config) {
|
|
699
|
+
const salienceCounts = /* @__PURE__ */ new Map();
|
|
700
|
+
let inPhase2 = false;
|
|
701
|
+
let lastPathCount = 0;
|
|
702
|
+
function sagePriority(nodeId, context) {
|
|
703
|
+
const pathCount = context.discoveredPaths.length;
|
|
704
|
+
if (pathCount > 0 && !inPhase2) inPhase2 = true;
|
|
705
|
+
if (pathCount > lastPathCount) lastPathCount = updateSalienceCounts(salienceCounts, context.discoveredPaths, lastPathCount);
|
|
706
|
+
if (!inPhase2) return Math.log(context.degree + 1);
|
|
707
|
+
return -((salienceCounts.get(nodeId) ?? 0) * 1e3 - context.degree);
|
|
708
|
+
}
|
|
709
|
+
return baseAsync(graph, seeds, {
|
|
710
|
+
...config,
|
|
711
|
+
priority: sagePriority
|
|
712
|
+
});
|
|
713
|
+
}
|
|
784
714
|
//#endregion
|
|
785
715
|
//#region src/ranking/mi/jaccard.ts
|
|
786
716
|
/**
|
|
@@ -798,6 +728,23 @@ function jaccard(graph, source, target, config) {
|
|
|
798
728
|
if (sourceNeighbours.size === 0 && targetNeighbours.size === 0) return 0;
|
|
799
729
|
return Math.max(epsilon, jaccardScore);
|
|
800
730
|
}
|
|
731
|
+
/**
|
|
732
|
+
* Async variant of Jaccard similarity for use with async graph data sources.
|
|
733
|
+
*
|
|
734
|
+
* Fetches both neighbourhoods concurrently, then applies the same formula.
|
|
735
|
+
*/
|
|
736
|
+
async function jaccardAsync(graph, source, target, config) {
|
|
737
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
738
|
+
const [sourceNeighboursArr, targetNeighboursArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
739
|
+
const srcSet = new Set(sourceNeighboursArr.filter((n) => n !== target));
|
|
740
|
+
const tgtSet = new Set(targetNeighboursArr.filter((n) => n !== source));
|
|
741
|
+
if (srcSet.size === 0 && tgtSet.size === 0) return 0;
|
|
742
|
+
let intersection = 0;
|
|
743
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
744
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
745
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
746
|
+
return Math.max(epsilon, jaccardScore);
|
|
747
|
+
}
|
|
801
748
|
//#endregion
|
|
802
749
|
//#region src/expansion/reach.ts
|
|
803
750
|
/**
|
|
@@ -854,6 +801,40 @@ function reach(graph, seeds, config) {
|
|
|
854
801
|
priority: reachPriority
|
|
855
802
|
});
|
|
856
803
|
}
|
|
804
|
+
/**
|
|
805
|
+
* Run REACH expansion asynchronously.
|
|
806
|
+
*
|
|
807
|
+
* Creates fresh closure state (phase tracking, Jaccard cache) for this
|
|
808
|
+
* invocation. The REACH priority function uses `jaccard(context.graph, ...)`
|
|
809
|
+
* in Phase 2; in async mode `context.graph` is the sentinel and will throw.
|
|
810
|
+
* Full async equivalence requires PriorityContext refactoring (Phase 4b
|
|
811
|
+
* deferred). This export establishes the async API surface.
|
|
812
|
+
*
|
|
813
|
+
* @param graph - Async source graph
|
|
814
|
+
* @param seeds - Seed nodes for expansion
|
|
815
|
+
* @param config - Expansion and async runner configuration
|
|
816
|
+
* @returns Promise resolving to the expansion result
|
|
817
|
+
*/
|
|
818
|
+
async function reachAsync(graph, seeds, config) {
|
|
819
|
+
let inPhase2 = false;
|
|
820
|
+
function reachPriority(nodeId, context) {
|
|
821
|
+
if (context.discoveredPaths.length > 0 && !inPhase2) inPhase2 = true;
|
|
822
|
+
if (!inPhase2) return Math.log(context.degree + 1);
|
|
823
|
+
let totalMI = 0;
|
|
824
|
+
let endpointCount = 0;
|
|
825
|
+
for (const path of context.discoveredPaths) {
|
|
826
|
+
totalMI += jaccard(context.graph, nodeId, path.fromSeed.id);
|
|
827
|
+
totalMI += jaccard(context.graph, nodeId, path.toSeed.id);
|
|
828
|
+
endpointCount += 2;
|
|
829
|
+
}
|
|
830
|
+
const miHat = endpointCount > 0 ? totalMI / endpointCount : 0;
|
|
831
|
+
return Math.log(context.degree + 1) * (1 - miHat);
|
|
832
|
+
}
|
|
833
|
+
return baseAsync(graph, seeds, {
|
|
834
|
+
...config,
|
|
835
|
+
priority: reachPriority
|
|
836
|
+
});
|
|
837
|
+
}
|
|
857
838
|
//#endregion
|
|
858
839
|
//#region src/expansion/maze.ts
|
|
859
840
|
/** Default threshold for switching to phase 2 (after M paths) */
|
|
@@ -900,6 +881,46 @@ function maze(graph, seeds, config) {
|
|
|
900
881
|
priority: mazePriority
|
|
901
882
|
});
|
|
902
883
|
}
|
|
884
|
+
/**
|
|
885
|
+
* Run MAZE expansion asynchronously.
|
|
886
|
+
*
|
|
887
|
+
* Creates fresh closure state (salienceCounts, phase tracking) for this
|
|
888
|
+
* invocation. The MAZE priority function accesses `context.graph` to
|
|
889
|
+
* retrieve neighbour lists for path potential computation. Full async
|
|
890
|
+
* equivalence requires PriorityContext refactoring (Phase 4b deferred).
|
|
891
|
+
* This export establishes the async API surface.
|
|
892
|
+
*
|
|
893
|
+
* @param graph - Async source graph
|
|
894
|
+
* @param seeds - Seed nodes for expansion
|
|
895
|
+
* @param config - Expansion and async runner configuration
|
|
896
|
+
* @returns Promise resolving to the expansion result
|
|
897
|
+
*/
|
|
898
|
+
async function mazeAsync(graph, seeds, config) {
|
|
899
|
+
const salienceCounts = /* @__PURE__ */ new Map();
|
|
900
|
+
let inPhase2 = false;
|
|
901
|
+
let lastPathCount = 0;
|
|
902
|
+
function mazePriority(nodeId, context) {
|
|
903
|
+
const pathCount = context.discoveredPaths.length;
|
|
904
|
+
if (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {
|
|
905
|
+
inPhase2 = true;
|
|
906
|
+
updateSalienceCounts(salienceCounts, context.discoveredPaths, 0);
|
|
907
|
+
}
|
|
908
|
+
if (inPhase2 && pathCount > lastPathCount) lastPathCount = updateSalienceCounts(salienceCounts, context.discoveredPaths, lastPathCount);
|
|
909
|
+
const nodeNeighbours = context.graph.neighbours(nodeId);
|
|
910
|
+
let pathPotential = 0;
|
|
911
|
+
for (const neighbour of nodeNeighbours) {
|
|
912
|
+
const visitedBy = context.visitedByFrontier.get(neighbour);
|
|
913
|
+
if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
|
|
914
|
+
}
|
|
915
|
+
if (!inPhase2) return context.degree / (1 + pathPotential);
|
|
916
|
+
const salience = salienceCounts.get(nodeId) ?? 0;
|
|
917
|
+
return context.degree / (1 + pathPotential) * (1 / (1 + SALIENCE_WEIGHT * salience));
|
|
918
|
+
}
|
|
919
|
+
return baseAsync(graph, seeds, {
|
|
920
|
+
...config,
|
|
921
|
+
priority: mazePriority
|
|
922
|
+
});
|
|
923
|
+
}
|
|
903
924
|
//#endregion
|
|
904
925
|
//#region src/expansion/tide.ts
|
|
905
926
|
/**
|
|
@@ -931,6 +952,25 @@ function tide(graph, seeds, config) {
|
|
|
931
952
|
priority: tidePriority
|
|
932
953
|
});
|
|
933
954
|
}
|
|
955
|
+
/**
|
|
956
|
+
* Run TIDE expansion asynchronously.
|
|
957
|
+
*
|
|
958
|
+
* Note: the TIDE priority function accesses `context.graph` to retrieve
|
|
959
|
+
* neighbour lists and per-neighbour degrees. Full async equivalence
|
|
960
|
+
* requires PriorityContext refactoring (Phase 4b deferred). This export
|
|
961
|
+
* establishes the async API surface.
|
|
962
|
+
*
|
|
963
|
+
* @param graph - Async source graph
|
|
964
|
+
* @param seeds - Seed nodes for expansion
|
|
965
|
+
* @param config - Expansion and async runner configuration
|
|
966
|
+
* @returns Promise resolving to the expansion result
|
|
967
|
+
*/
|
|
968
|
+
async function tideAsync(graph, seeds, config) {
|
|
969
|
+
return baseAsync(graph, seeds, {
|
|
970
|
+
...config,
|
|
971
|
+
priority: tidePriority
|
|
972
|
+
});
|
|
973
|
+
}
|
|
934
974
|
//#endregion
|
|
935
975
|
//#region src/expansion/lace.ts
|
|
936
976
|
/**
|
|
@@ -961,6 +1001,27 @@ function lace(graph, seeds, config) {
|
|
|
961
1001
|
priority
|
|
962
1002
|
});
|
|
963
1003
|
}
|
|
1004
|
+
/**
|
|
1005
|
+
* Run LACE expansion asynchronously.
|
|
1006
|
+
*
|
|
1007
|
+
* Note: the LACE priority function accesses `context.graph` via
|
|
1008
|
+
* `avgFrontierMI`. Full async equivalence requires PriorityContext
|
|
1009
|
+
* refactoring (Phase 4b deferred). This export establishes the async
|
|
1010
|
+
* API surface.
|
|
1011
|
+
*
|
|
1012
|
+
* @param graph - Async source graph
|
|
1013
|
+
* @param seeds - Seed nodes for expansion
|
|
1014
|
+
* @param config - LACE configuration combined with async runner options
|
|
1015
|
+
* @returns Promise resolving to the expansion result
|
|
1016
|
+
*/
|
|
1017
|
+
async function laceAsync(graph, seeds, config) {
|
|
1018
|
+
const { mi = jaccard, ...restConfig } = config ?? {};
|
|
1019
|
+
const priority = (nodeId, context) => lacePriority(nodeId, context, mi);
|
|
1020
|
+
return baseAsync(graph, seeds, {
|
|
1021
|
+
...restConfig,
|
|
1022
|
+
priority
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
964
1025
|
//#endregion
|
|
965
1026
|
//#region src/expansion/warp.ts
|
|
966
1027
|
/**
|
|
@@ -993,6 +1054,25 @@ function warp(graph, seeds, config) {
|
|
|
993
1054
|
priority: warpPriority
|
|
994
1055
|
});
|
|
995
1056
|
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Run WARP expansion asynchronously.
|
|
1059
|
+
*
|
|
1060
|
+
* Note: the WARP priority function accesses `context.graph` via
|
|
1061
|
+
* `countCrossFrontierNeighbours`. Full async equivalence requires
|
|
1062
|
+
* PriorityContext refactoring (Phase 4b deferred). This export
|
|
1063
|
+
* establishes the async API surface.
|
|
1064
|
+
*
|
|
1065
|
+
* @param graph - Async source graph
|
|
1066
|
+
* @param seeds - Seed nodes for expansion
|
|
1067
|
+
* @param config - Expansion and async runner configuration
|
|
1068
|
+
* @returns Promise resolving to the expansion result
|
|
1069
|
+
*/
|
|
1070
|
+
async function warpAsync(graph, seeds, config) {
|
|
1071
|
+
return baseAsync(graph, seeds, {
|
|
1072
|
+
...config,
|
|
1073
|
+
priority: warpPriority
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
996
1076
|
//#endregion
|
|
997
1077
|
//#region src/expansion/fuse.ts
|
|
998
1078
|
/**
|
|
@@ -1025,6 +1105,27 @@ function fuse(graph, seeds, config) {
|
|
|
1025
1105
|
priority
|
|
1026
1106
|
});
|
|
1027
1107
|
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Run FUSE expansion asynchronously.
|
|
1110
|
+
*
|
|
1111
|
+
* Note: the FUSE priority function accesses `context.graph` via
|
|
1112
|
+
* `avgFrontierMI`. Full async equivalence requires PriorityContext
|
|
1113
|
+
* refactoring (Phase 4b deferred). This export establishes the async
|
|
1114
|
+
* API surface.
|
|
1115
|
+
*
|
|
1116
|
+
* @param graph - Async source graph
|
|
1117
|
+
* @param seeds - Seed nodes for expansion
|
|
1118
|
+
* @param config - FUSE configuration combined with async runner options
|
|
1119
|
+
* @returns Promise resolving to the expansion result
|
|
1120
|
+
*/
|
|
1121
|
+
async function fuseAsync(graph, seeds, config) {
|
|
1122
|
+
const { mi = jaccard, salienceWeight = .5, ...restConfig } = config ?? {};
|
|
1123
|
+
const priority = (nodeId, context) => fusePriority(nodeId, context, mi, salienceWeight);
|
|
1124
|
+
return baseAsync(graph, seeds, {
|
|
1125
|
+
...restConfig,
|
|
1126
|
+
priority
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1028
1129
|
//#endregion
|
|
1029
1130
|
//#region src/expansion/sift.ts
|
|
1030
1131
|
/**
|
|
@@ -1057,6 +1158,27 @@ function sift(graph, seeds, config) {
|
|
|
1057
1158
|
priority
|
|
1058
1159
|
});
|
|
1059
1160
|
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Run SIFT expansion asynchronously.
|
|
1163
|
+
*
|
|
1164
|
+
* Note: the SIFT priority function accesses `context.graph` via
|
|
1165
|
+
* `avgFrontierMI`. Full async equivalence requires PriorityContext
|
|
1166
|
+
* refactoring (Phase 4b deferred). This export establishes the async
|
|
1167
|
+
* API surface.
|
|
1168
|
+
*
|
|
1169
|
+
* @param graph - Async source graph
|
|
1170
|
+
* @param seeds - Seed nodes for expansion
|
|
1171
|
+
* @param config - SIFT (REACHConfig) configuration combined with async runner options
|
|
1172
|
+
* @returns Promise resolving to the expansion result
|
|
1173
|
+
*/
|
|
1174
|
+
async function siftAsync(graph, seeds, config) {
|
|
1175
|
+
const { mi = jaccard, miThreshold = .25, ...restConfig } = config ?? {};
|
|
1176
|
+
const priority = (nodeId, context) => siftPriority(nodeId, context, mi, miThreshold);
|
|
1177
|
+
return baseAsync(graph, seeds, {
|
|
1178
|
+
...restConfig,
|
|
1179
|
+
priority
|
|
1180
|
+
});
|
|
1181
|
+
}
|
|
1060
1182
|
//#endregion
|
|
1061
1183
|
//#region src/expansion/flux.ts
|
|
1062
1184
|
/**
|
|
@@ -1113,9 +1235,36 @@ function flux(graph, seeds, config) {
|
|
|
1113
1235
|
priority
|
|
1114
1236
|
});
|
|
1115
1237
|
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Run FLUX expansion asynchronously.
|
|
1240
|
+
*
|
|
1241
|
+
* Note: the FLUX priority function accesses `context.graph` to compute
|
|
1242
|
+
* local density and cross-frontier bridge scores. Full async equivalence
|
|
1243
|
+
* requires PriorityContext refactoring (Phase 4b deferred). This export
|
|
1244
|
+
* establishes the async API surface.
|
|
1245
|
+
*
|
|
1246
|
+
* @param graph - Async source graph
|
|
1247
|
+
* @param seeds - Seed nodes for expansion
|
|
1248
|
+
* @param config - FLUX (MAZEConfig) configuration combined with async runner options
|
|
1249
|
+
* @returns Promise resolving to the expansion result
|
|
1250
|
+
*/
|
|
1251
|
+
async function fluxAsync(graph, seeds, config) {
|
|
1252
|
+
const { densityThreshold = .5, bridgeThreshold = .3, ...restConfig } = config ?? {};
|
|
1253
|
+
const priority = (nodeId, context) => fluxPriority(nodeId, context, densityThreshold, bridgeThreshold);
|
|
1254
|
+
return baseAsync(graph, seeds, {
|
|
1255
|
+
...restConfig,
|
|
1256
|
+
priority
|
|
1257
|
+
});
|
|
1258
|
+
}
|
|
1116
1259
|
//#endregion
|
|
1117
1260
|
//#region src/expansion/standard-bfs.ts
|
|
1118
1261
|
/**
|
|
1262
|
+
* BFS priority: discovery iteration order (FIFO).
|
|
1263
|
+
*/
|
|
1264
|
+
function bfsPriority(_nodeId, context) {
|
|
1265
|
+
return context.iteration;
|
|
1266
|
+
}
|
|
1267
|
+
/**
|
|
1119
1268
|
* Run standard BFS expansion (FIFO discovery order).
|
|
1120
1269
|
*
|
|
1121
1270
|
* @param graph - Source graph
|
|
@@ -1124,17 +1273,35 @@ function flux(graph, seeds, config) {
|
|
|
1124
1273
|
* @returns Expansion result with discovered paths
|
|
1125
1274
|
*/
|
|
1126
1275
|
function standardBfs(graph, seeds, config) {
|
|
1127
|
-
const bfsPriority = (_nodeId, context) => {
|
|
1128
|
-
return context.iteration;
|
|
1129
|
-
};
|
|
1130
1276
|
return base(graph, seeds, {
|
|
1131
1277
|
...config,
|
|
1132
1278
|
priority: bfsPriority
|
|
1133
1279
|
});
|
|
1134
1280
|
}
|
|
1281
|
+
/**
|
|
1282
|
+
* Run standard BFS expansion asynchronously (FIFO discovery order).
|
|
1283
|
+
*
|
|
1284
|
+
* @param graph - Async source graph
|
|
1285
|
+
* @param seeds - Seed nodes for expansion
|
|
1286
|
+
* @param config - Expansion and async runner configuration
|
|
1287
|
+
* @returns Promise resolving to the expansion result
|
|
1288
|
+
*/
|
|
1289
|
+
async function standardBfsAsync(graph, seeds, config) {
|
|
1290
|
+
return baseAsync(graph, seeds, {
|
|
1291
|
+
...config,
|
|
1292
|
+
priority: bfsPriority
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1135
1295
|
//#endregion
|
|
1136
1296
|
//#region src/expansion/frontier-balanced.ts
|
|
1137
1297
|
/**
|
|
1298
|
+
* Frontier-balanced priority: frontier index dominates, then discovery iteration.
|
|
1299
|
+
* Scales frontier index by 1e9 to ensure round-robin ordering across frontiers.
|
|
1300
|
+
*/
|
|
1301
|
+
function balancedPriority(_nodeId, context) {
|
|
1302
|
+
return context.frontierIndex * 1e9 + context.iteration;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1138
1305
|
* Run frontier-balanced expansion (round-robin across frontiers).
|
|
1139
1306
|
*
|
|
1140
1307
|
* @param graph - Source graph
|
|
@@ -1143,14 +1310,25 @@ function standardBfs(graph, seeds, config) {
|
|
|
1143
1310
|
* @returns Expansion result with discovered paths
|
|
1144
1311
|
*/
|
|
1145
1312
|
function frontierBalanced(graph, seeds, config) {
|
|
1146
|
-
const balancedPriority = (_nodeId, context) => {
|
|
1147
|
-
return context.frontierIndex * 1e9 + context.iteration;
|
|
1148
|
-
};
|
|
1149
1313
|
return base(graph, seeds, {
|
|
1150
1314
|
...config,
|
|
1151
1315
|
priority: balancedPriority
|
|
1152
1316
|
});
|
|
1153
1317
|
}
|
|
1318
|
+
/**
|
|
1319
|
+
* Run frontier-balanced expansion asynchronously (round-robin across frontiers).
|
|
1320
|
+
*
|
|
1321
|
+
* @param graph - Async source graph
|
|
1322
|
+
* @param seeds - Seed nodes for expansion
|
|
1323
|
+
* @param config - Expansion and async runner configuration
|
|
1324
|
+
* @returns Promise resolving to the expansion result
|
|
1325
|
+
*/
|
|
1326
|
+
async function frontierBalancedAsync(graph, seeds, config) {
|
|
1327
|
+
return baseAsync(graph, seeds, {
|
|
1328
|
+
...config,
|
|
1329
|
+
priority: balancedPriority
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1154
1332
|
//#endregion
|
|
1155
1333
|
//#region src/expansion/random-priority.ts
|
|
1156
1334
|
/**
|
|
@@ -1170,6 +1348,12 @@ function seededRandom$1(input, seed = 0) {
|
|
|
1170
1348
|
return (h >>> 0) / 4294967295;
|
|
1171
1349
|
}
|
|
1172
1350
|
/**
|
|
1351
|
+
* Build a seeded random priority function for a given seed value.
|
|
1352
|
+
*/
|
|
1353
|
+
function makeRandomPriorityFn(seed) {
|
|
1354
|
+
return (nodeId) => seededRandom$1(nodeId, seed);
|
|
1355
|
+
}
|
|
1356
|
+
/**
|
|
1173
1357
|
* Run random-priority expansion (null hypothesis baseline).
|
|
1174
1358
|
*
|
|
1175
1359
|
* @param graph - Source graph
|
|
@@ -1179,12 +1363,24 @@ function seededRandom$1(input, seed = 0) {
|
|
|
1179
1363
|
*/
|
|
1180
1364
|
function randomPriority(graph, seeds, config) {
|
|
1181
1365
|
const { seed = 0 } = config ?? {};
|
|
1182
|
-
const randomPriorityFn = (nodeId, context) => {
|
|
1183
|
-
return seededRandom$1(nodeId, seed);
|
|
1184
|
-
};
|
|
1185
1366
|
return base(graph, seeds, {
|
|
1186
1367
|
...config,
|
|
1187
|
-
priority:
|
|
1368
|
+
priority: makeRandomPriorityFn(seed)
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Run random-priority expansion asynchronously (null hypothesis baseline).
|
|
1373
|
+
*
|
|
1374
|
+
* @param graph - Async source graph
|
|
1375
|
+
* @param seeds - Seed nodes for expansion
|
|
1376
|
+
* @param config - Expansion and async runner configuration
|
|
1377
|
+
* @returns Promise resolving to the expansion result
|
|
1378
|
+
*/
|
|
1379
|
+
async function randomPriorityAsync(graph, seeds, config) {
|
|
1380
|
+
const { seed = 0 } = config ?? {};
|
|
1381
|
+
return baseAsync(graph, seeds, {
|
|
1382
|
+
...config,
|
|
1383
|
+
priority: makeRandomPriorityFn(seed)
|
|
1188
1384
|
});
|
|
1189
1385
|
}
|
|
1190
1386
|
//#endregion
|
|
@@ -1216,6 +1412,20 @@ function dfsPriority(graph, seeds, config) {
|
|
|
1216
1412
|
priority: dfsPriorityFn
|
|
1217
1413
|
});
|
|
1218
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Run DFS-priority expansion asynchronously (LIFO discovery order).
|
|
1417
|
+
*
|
|
1418
|
+
* @param graph - Async source graph
|
|
1419
|
+
* @param seeds - Seed nodes for expansion
|
|
1420
|
+
* @param config - Expansion and async runner configuration
|
|
1421
|
+
* @returns Promise resolving to the expansion result
|
|
1422
|
+
*/
|
|
1423
|
+
async function dfsPriorityAsync(graph, seeds, config) {
|
|
1424
|
+
return baseAsync(graph, seeds, {
|
|
1425
|
+
...config,
|
|
1426
|
+
priority: dfsPriorityFn
|
|
1427
|
+
});
|
|
1428
|
+
}
|
|
1219
1429
|
//#endregion
|
|
1220
1430
|
//#region src/expansion/k-hop.ts
|
|
1221
1431
|
/**
|
|
@@ -1573,6 +1783,74 @@ function parse(graph, paths, config) {
|
|
|
1573
1783
|
};
|
|
1574
1784
|
}
|
|
1575
1785
|
/**
|
|
1786
|
+
* Rank paths using async PARSE (Path-Aware Ranking via Salience Estimation).
|
|
1787
|
+
*
|
|
1788
|
+
* Async variant suitable for use with remote or lazy graph data sources.
|
|
1789
|
+
* Computes geometric mean of edge MI scores for each path using Promise.all
|
|
1790
|
+
* for parallelism, then sorts by salience (highest first).
|
|
1791
|
+
*
|
|
1792
|
+
* @param graph - Async source graph
|
|
1793
|
+
* @param paths - Paths to rank
|
|
1794
|
+
* @param config - Configuration options
|
|
1795
|
+
* @returns Ranked paths with statistics
|
|
1796
|
+
*/
|
|
1797
|
+
async function parseAsync(graph, paths, config) {
|
|
1798
|
+
const startTime = performance.now();
|
|
1799
|
+
const { mi = jaccardAsync, epsilon = 1e-10 } = config ?? {};
|
|
1800
|
+
const rankedPaths = [];
|
|
1801
|
+
for (const path of paths) {
|
|
1802
|
+
const salience = await computePathSalienceAsync(graph, path, mi, epsilon);
|
|
1803
|
+
rankedPaths.push({
|
|
1804
|
+
...path,
|
|
1805
|
+
salience
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
rankedPaths.sort((a, b) => b.salience - a.salience);
|
|
1809
|
+
const endTime = performance.now();
|
|
1810
|
+
const saliences = rankedPaths.map((p) => p.salience);
|
|
1811
|
+
const meanSalience = saliences.length > 0 ? saliences.reduce((a, b) => a + b, 0) / saliences.length : 0;
|
|
1812
|
+
const sortedSaliences = [...saliences].sort((a, b) => a - b);
|
|
1813
|
+
const mid = Math.floor(sortedSaliences.length / 2);
|
|
1814
|
+
const medianSalience = sortedSaliences.length > 0 ? sortedSaliences.length % 2 !== 0 ? sortedSaliences[mid] ?? 0 : ((sortedSaliences[mid - 1] ?? 0) + (sortedSaliences[mid] ?? 0)) / 2 : 0;
|
|
1815
|
+
const maxSalience = sortedSaliences.length > 0 ? sortedSaliences[sortedSaliences.length - 1] ?? 0 : 0;
|
|
1816
|
+
const minSalience = sortedSaliences.length > 0 ? sortedSaliences[0] ?? 0 : 0;
|
|
1817
|
+
return {
|
|
1818
|
+
paths: rankedPaths,
|
|
1819
|
+
stats: {
|
|
1820
|
+
pathsRanked: rankedPaths.length,
|
|
1821
|
+
meanSalience,
|
|
1822
|
+
medianSalience,
|
|
1823
|
+
maxSalience,
|
|
1824
|
+
minSalience,
|
|
1825
|
+
durationMs: endTime - startTime
|
|
1826
|
+
}
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
/**
|
|
1830
|
+
* Compute salience for a single path asynchronously.
|
|
1831
|
+
*
|
|
1832
|
+
* Uses geometric mean of edge MI scores for length-unbiased ranking.
|
|
1833
|
+
* Edge MI values are computed in parallel via Promise.all.
|
|
1834
|
+
*/
|
|
1835
|
+
async function computePathSalienceAsync(graph, path, mi, epsilon) {
|
|
1836
|
+
const nodes = path.nodes;
|
|
1837
|
+
if (nodes.length < 2) return epsilon;
|
|
1838
|
+
const edgeMIs = await Promise.all(nodes.slice(0, -1).map((source, i) => {
|
|
1839
|
+
const target = nodes[i + 1];
|
|
1840
|
+
if (target !== void 0) return mi(graph, source, target);
|
|
1841
|
+
return Promise.resolve(epsilon);
|
|
1842
|
+
}));
|
|
1843
|
+
let productMi = 1;
|
|
1844
|
+
let edgeCount = 0;
|
|
1845
|
+
for (const edgeMi of edgeMIs) {
|
|
1846
|
+
productMi *= Math.max(epsilon, edgeMi);
|
|
1847
|
+
edgeCount++;
|
|
1848
|
+
}
|
|
1849
|
+
if (edgeCount === 0) return epsilon;
|
|
1850
|
+
const salience = Math.pow(productMi, 1 / edgeCount);
|
|
1851
|
+
return Math.max(epsilon, Math.min(1, salience));
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1576
1854
|
* Compute salience for a single path.
|
|
1577
1855
|
*
|
|
1578
1856
|
* Uses geometric mean of edge MI scores for length-unbiased ranking.
|
|
@@ -1620,6 +1898,29 @@ function adamicAdar(graph, source, target, config) {
|
|
|
1620
1898
|
}
|
|
1621
1899
|
return Math.max(epsilon, score);
|
|
1622
1900
|
}
|
|
1901
|
+
/**
|
|
1902
|
+
* Async variant of Adamic-Adar index for use with async graph data sources.
|
|
1903
|
+
*
|
|
1904
|
+
* Fetches both neighbourhoods concurrently, then fetches degree for each common
|
|
1905
|
+
* neighbour to compute the inverse-log-degree weighted sum.
|
|
1906
|
+
*/
|
|
1907
|
+
async function adamicAdarAsync(graph, source, target, config) {
|
|
1908
|
+
const { epsilon = 1e-10, normalise = true } = config ?? {};
|
|
1909
|
+
const [sourceArr, targetArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
1910
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
1911
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
1912
|
+
const commonNeighbours = [];
|
|
1913
|
+
for (const n of srcSet) if (tgtSet.has(n)) commonNeighbours.push(n);
|
|
1914
|
+
if (commonNeighbours.length === 0) return epsilon;
|
|
1915
|
+
const degrees = await Promise.all(commonNeighbours.map((n) => graph.degree(n)));
|
|
1916
|
+
let score = 0;
|
|
1917
|
+
for (const degree of degrees) score += 1 / Math.log(degree + 1);
|
|
1918
|
+
if (normalise) {
|
|
1919
|
+
const maxScore = commonNeighbours.length / Math.log(2);
|
|
1920
|
+
score = score / maxScore;
|
|
1921
|
+
}
|
|
1922
|
+
return Math.max(epsilon, score);
|
|
1923
|
+
}
|
|
1623
1924
|
//#endregion
|
|
1624
1925
|
//#region src/ranking/mi/cosine.ts
|
|
1625
1926
|
/**
|
|
@@ -1641,6 +1942,23 @@ function cosine(graph, source, target, config) {
|
|
|
1641
1942
|
const score = intersection / denominator;
|
|
1642
1943
|
return Math.max(epsilon, score);
|
|
1643
1944
|
}
|
|
1945
|
+
/**
|
|
1946
|
+
* Async variant of cosine similarity for use with async graph data sources.
|
|
1947
|
+
*
|
|
1948
|
+
* Fetches both neighbourhoods concurrently, then applies the same formula.
|
|
1949
|
+
*/
|
|
1950
|
+
async function cosineAsync(graph, source, target, config) {
|
|
1951
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
1952
|
+
const [sourceArr, targetArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
1953
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
1954
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
1955
|
+
let intersection = 0;
|
|
1956
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
1957
|
+
const denominator = Math.sqrt(srcSet.size) * Math.sqrt(tgtSet.size);
|
|
1958
|
+
if (denominator === 0) return 0;
|
|
1959
|
+
const score = intersection / denominator;
|
|
1960
|
+
return Math.max(epsilon, score);
|
|
1961
|
+
}
|
|
1644
1962
|
//#endregion
|
|
1645
1963
|
//#region src/ranking/mi/sorensen.ts
|
|
1646
1964
|
/**
|
|
@@ -1662,6 +1980,23 @@ function sorensen(graph, source, target, config) {
|
|
|
1662
1980
|
const score = 2 * intersection / denominator;
|
|
1663
1981
|
return Math.max(epsilon, score);
|
|
1664
1982
|
}
|
|
1983
|
+
/**
|
|
1984
|
+
* Async variant of Sorensen-Dice coefficient for use with async graph data sources.
|
|
1985
|
+
*
|
|
1986
|
+
* Fetches both neighbourhoods concurrently, then applies the same formula.
|
|
1987
|
+
*/
|
|
1988
|
+
async function sorensenAsync(graph, source, target, config) {
|
|
1989
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
1990
|
+
const [sourceArr, targetArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
1991
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
1992
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
1993
|
+
let intersection = 0;
|
|
1994
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
1995
|
+
const denominator = srcSet.size + tgtSet.size;
|
|
1996
|
+
if (denominator === 0) return 0;
|
|
1997
|
+
const score = 2 * intersection / denominator;
|
|
1998
|
+
return Math.max(epsilon, score);
|
|
1999
|
+
}
|
|
1665
2000
|
//#endregion
|
|
1666
2001
|
//#region src/ranking/mi/resource-allocation.ts
|
|
1667
2002
|
/**
|
|
@@ -1687,6 +2022,29 @@ function resourceAllocation(graph, source, target, config) {
|
|
|
1687
2022
|
}
|
|
1688
2023
|
return Math.max(epsilon, score);
|
|
1689
2024
|
}
|
|
2025
|
+
/**
|
|
2026
|
+
* Async variant of Resource Allocation index for use with async graph data sources.
|
|
2027
|
+
*
|
|
2028
|
+
* Fetches both neighbourhoods concurrently, then fetches degree for each common
|
|
2029
|
+
* neighbour to compute the inverse-degree weighted sum.
|
|
2030
|
+
*/
|
|
2031
|
+
async function resourceAllocationAsync(graph, source, target, config) {
|
|
2032
|
+
const { epsilon = 1e-10, normalise = true } = config ?? {};
|
|
2033
|
+
const [sourceArr, targetArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
2034
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2035
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2036
|
+
const commonNeighbours = [];
|
|
2037
|
+
for (const n of srcSet) if (tgtSet.has(n)) commonNeighbours.push(n);
|
|
2038
|
+
if (commonNeighbours.length === 0) return epsilon;
|
|
2039
|
+
const degrees = await Promise.all(commonNeighbours.map((n) => graph.degree(n)));
|
|
2040
|
+
let score = 0;
|
|
2041
|
+
for (const degree of degrees) if (degree > 0) score += 1 / degree;
|
|
2042
|
+
if (normalise) {
|
|
2043
|
+
const maxScore = commonNeighbours.length;
|
|
2044
|
+
score = score / maxScore;
|
|
2045
|
+
}
|
|
2046
|
+
return Math.max(epsilon, score);
|
|
2047
|
+
}
|
|
1690
2048
|
//#endregion
|
|
1691
2049
|
//#region src/ranking/mi/overlap-coefficient.ts
|
|
1692
2050
|
/**
|
|
@@ -1708,6 +2066,23 @@ function overlapCoefficient(graph, source, target, config) {
|
|
|
1708
2066
|
const score = intersection / denominator;
|
|
1709
2067
|
return Math.max(epsilon, score);
|
|
1710
2068
|
}
|
|
2069
|
+
/**
|
|
2070
|
+
* Async variant of Overlap Coefficient for use with async graph data sources.
|
|
2071
|
+
*
|
|
2072
|
+
* Fetches both neighbourhoods concurrently, then applies the same formula.
|
|
2073
|
+
*/
|
|
2074
|
+
async function overlapCoefficientAsync(graph, source, target, config) {
|
|
2075
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2076
|
+
const [sourceArr, targetArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
2077
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2078
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2079
|
+
let intersection = 0;
|
|
2080
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2081
|
+
const denominator = Math.min(srcSet.size, tgtSet.size);
|
|
2082
|
+
if (denominator === 0) return 0;
|
|
2083
|
+
const score = intersection / denominator;
|
|
2084
|
+
return Math.max(epsilon, score);
|
|
2085
|
+
}
|
|
1711
2086
|
//#endregion
|
|
1712
2087
|
//#region src/ranking/mi/hub-promoted.ts
|
|
1713
2088
|
/**
|
|
@@ -1729,6 +2104,28 @@ function hubPromoted(graph, source, target, config) {
|
|
|
1729
2104
|
const score = intersection / denominator;
|
|
1730
2105
|
return Math.max(epsilon, score);
|
|
1731
2106
|
}
|
|
2107
|
+
/**
|
|
2108
|
+
* Async variant of Hub Promoted index for use with async graph data sources.
|
|
2109
|
+
*
|
|
2110
|
+
* Fetches both neighbourhoods and degrees concurrently, then applies the same formula.
|
|
2111
|
+
*/
|
|
2112
|
+
async function hubPromotedAsync(graph, source, target, config) {
|
|
2113
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2114
|
+
const [sourceArr, targetArr, sourceDegree, targetDegree] = await Promise.all([
|
|
2115
|
+
require_async.collectAsyncIterable(graph.neighbours(source)),
|
|
2116
|
+
require_async.collectAsyncIterable(graph.neighbours(target)),
|
|
2117
|
+
graph.degree(source),
|
|
2118
|
+
graph.degree(target)
|
|
2119
|
+
]);
|
|
2120
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2121
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2122
|
+
let intersection = 0;
|
|
2123
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2124
|
+
const denominator = Math.min(sourceDegree, targetDegree);
|
|
2125
|
+
if (denominator === 0) return 0;
|
|
2126
|
+
const score = intersection / denominator;
|
|
2127
|
+
return Math.max(epsilon, score);
|
|
2128
|
+
}
|
|
1732
2129
|
//#endregion
|
|
1733
2130
|
//#region src/ranking/mi/scale.ts
|
|
1734
2131
|
/**
|
|
@@ -1745,6 +2142,31 @@ function scale(graph, source, target, config) {
|
|
|
1745
2142
|
const score = jaccardScore / density;
|
|
1746
2143
|
return Math.max(epsilon, score);
|
|
1747
2144
|
}
|
|
2145
|
+
/**
|
|
2146
|
+
* Async variant of SCALE MI for use with async graph data sources.
|
|
2147
|
+
*
|
|
2148
|
+
* Fetches both neighbourhoods, node count, and edge count concurrently.
|
|
2149
|
+
*/
|
|
2150
|
+
async function scaleAsync(graph, source, target, config) {
|
|
2151
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2152
|
+
const [sourceArr, targetArr, n, m] = await Promise.all([
|
|
2153
|
+
require_async.collectAsyncIterable(graph.neighbours(source)),
|
|
2154
|
+
require_async.collectAsyncIterable(graph.neighbours(target)),
|
|
2155
|
+
graph.nodeCount,
|
|
2156
|
+
graph.edgeCount
|
|
2157
|
+
]);
|
|
2158
|
+
const srcSet = new Set(sourceArr.filter((node) => node !== target));
|
|
2159
|
+
const tgtSet = new Set(targetArr.filter((node) => node !== source));
|
|
2160
|
+
let intersection = 0;
|
|
2161
|
+
for (const node of srcSet) if (tgtSet.has(node)) intersection++;
|
|
2162
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
2163
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
2164
|
+
const possibleEdges = n * (n - 1);
|
|
2165
|
+
const density = possibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;
|
|
2166
|
+
if (density === 0) return epsilon;
|
|
2167
|
+
const score = jaccardScore / density;
|
|
2168
|
+
return Math.max(epsilon, score);
|
|
2169
|
+
}
|
|
1748
2170
|
//#endregion
|
|
1749
2171
|
//#region src/ranking/mi/skew.ts
|
|
1750
2172
|
/**
|
|
@@ -1761,6 +2183,31 @@ function skew(graph, source, target, config) {
|
|
|
1761
2183
|
const score = jaccardScore * sourceIdf * targetIdf;
|
|
1762
2184
|
return Math.max(epsilon, score);
|
|
1763
2185
|
}
|
|
2186
|
+
/**
|
|
2187
|
+
* Async variant of SKEW MI for use with async graph data sources.
|
|
2188
|
+
*
|
|
2189
|
+
* Fetches both neighbourhoods, degrees, and node count concurrently.
|
|
2190
|
+
*/
|
|
2191
|
+
async function skewAsync(graph, source, target, config) {
|
|
2192
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2193
|
+
const [sourceArr, targetArr, N, sourceDegree, targetDegree] = await Promise.all([
|
|
2194
|
+
require_async.collectAsyncIterable(graph.neighbours(source)),
|
|
2195
|
+
require_async.collectAsyncIterable(graph.neighbours(target)),
|
|
2196
|
+
graph.nodeCount,
|
|
2197
|
+
graph.degree(source),
|
|
2198
|
+
graph.degree(target)
|
|
2199
|
+
]);
|
|
2200
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2201
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2202
|
+
let intersection = 0;
|
|
2203
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2204
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
2205
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
2206
|
+
const sourceIdf = Math.log(N / (sourceDegree + 1));
|
|
2207
|
+
const targetIdf = Math.log(N / (targetDegree + 1));
|
|
2208
|
+
const score = jaccardScore * sourceIdf * targetIdf;
|
|
2209
|
+
return Math.max(epsilon, score);
|
|
2210
|
+
}
|
|
1764
2211
|
//#endregion
|
|
1765
2212
|
//#region src/ranking/mi/span.ts
|
|
1766
2213
|
/**
|
|
@@ -1774,6 +2221,40 @@ function span(graph, source, target, config) {
|
|
|
1774
2221
|
const score = jaccardScore * (1 - Math.max(sourceCc, targetCc));
|
|
1775
2222
|
return Math.max(epsilon, score);
|
|
1776
2223
|
}
|
|
2224
|
+
/**
|
|
2225
|
+
* Async variant of SPAN MI for use with async graph data sources.
|
|
2226
|
+
*
|
|
2227
|
+
* Fetches both neighbourhoods concurrently, then computes the clustering
|
|
2228
|
+
* coefficient for each endpoint from the collected neighbour arrays.
|
|
2229
|
+
*/
|
|
2230
|
+
async function spanAsync(graph, source, target, config) {
|
|
2231
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2232
|
+
const [sourceArr, targetArr] = await Promise.all([require_async.collectAsyncIterable(graph.neighbours(source)), require_async.collectAsyncIterable(graph.neighbours(target))]);
|
|
2233
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2234
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2235
|
+
let intersection = 0;
|
|
2236
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2237
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
2238
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
2239
|
+
const computeClusteringCoefficient = async (nodeId, neighbourArr) => {
|
|
2240
|
+
const degree = neighbourArr.length;
|
|
2241
|
+
if (degree < 2) return 0;
|
|
2242
|
+
const pairs = [];
|
|
2243
|
+
for (let i = 0; i < neighbourArr.length; i++) for (let j = i + 1; j < neighbourArr.length; j++) {
|
|
2244
|
+
const u = neighbourArr[i];
|
|
2245
|
+
const v = neighbourArr[j];
|
|
2246
|
+
if (u !== void 0 && v !== void 0) pairs.push([u, v]);
|
|
2247
|
+
}
|
|
2248
|
+
const edgeResults = await Promise.all(pairs.flatMap(([u, v]) => [graph.getEdge(u, v), graph.getEdge(v, u)]));
|
|
2249
|
+
let triangleCount = 0;
|
|
2250
|
+
for (let i = 0; i < pairs.length; i++) if (edgeResults[2 * i] !== void 0 || edgeResults[2 * i + 1] !== void 0) triangleCount++;
|
|
2251
|
+
const possibleTriangles = degree * (degree - 1) / 2;
|
|
2252
|
+
return triangleCount / possibleTriangles;
|
|
2253
|
+
};
|
|
2254
|
+
const [sourceCc, targetCc] = await Promise.all([computeClusteringCoefficient(source, sourceArr), computeClusteringCoefficient(target, targetArr)]);
|
|
2255
|
+
const score = jaccardScore * (1 - Math.max(sourceCc, targetCc));
|
|
2256
|
+
return Math.max(epsilon, score);
|
|
2257
|
+
}
|
|
1777
2258
|
//#endregion
|
|
1778
2259
|
//#region src/ranking/mi/etch.ts
|
|
1779
2260
|
/**
|
|
@@ -1789,6 +2270,37 @@ function etch(graph, source, target, config) {
|
|
|
1789
2270
|
const score = jaccardScore * Math.log(graph.edgeCount / edgeTypeCount);
|
|
1790
2271
|
return Math.max(epsilon, score);
|
|
1791
2272
|
}
|
|
2273
|
+
/**
|
|
2274
|
+
* Async variant of ETCH MI for use with async graph data sources.
|
|
2275
|
+
*
|
|
2276
|
+
* Fetches both neighbourhoods and edge data concurrently, then counts
|
|
2277
|
+
* edges of the same type by iterating the async edge stream.
|
|
2278
|
+
*/
|
|
2279
|
+
async function etchAsync(graph, source, target, config) {
|
|
2280
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2281
|
+
const [sourceArr, targetArr, edge] = await Promise.all([
|
|
2282
|
+
require_async.collectAsyncIterable(graph.neighbours(source)),
|
|
2283
|
+
require_async.collectAsyncIterable(graph.neighbours(target)),
|
|
2284
|
+
graph.getEdge(source, target)
|
|
2285
|
+
]);
|
|
2286
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2287
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2288
|
+
let intersection = 0;
|
|
2289
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2290
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
2291
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
2292
|
+
if (edge?.type === void 0) return Math.max(epsilon, jaccardScore);
|
|
2293
|
+
const edgeType = edge.type;
|
|
2294
|
+
let edgeTypeCount = 0;
|
|
2295
|
+
let totalEdges = 0;
|
|
2296
|
+
for await (const e of graph.edges()) {
|
|
2297
|
+
totalEdges++;
|
|
2298
|
+
if (e.type === edgeType) edgeTypeCount++;
|
|
2299
|
+
}
|
|
2300
|
+
if (edgeTypeCount === 0) return Math.max(epsilon, jaccardScore);
|
|
2301
|
+
const score = jaccardScore * Math.log(totalEdges / edgeTypeCount);
|
|
2302
|
+
return Math.max(epsilon, score);
|
|
2303
|
+
}
|
|
1792
2304
|
//#endregion
|
|
1793
2305
|
//#region src/ranking/mi/notch.ts
|
|
1794
2306
|
/**
|
|
@@ -1808,6 +2320,44 @@ function notch(graph, source, target, config) {
|
|
|
1808
2320
|
const score = jaccardScore * sourceRarity * targetRarity;
|
|
1809
2321
|
return Math.max(epsilon, score);
|
|
1810
2322
|
}
|
|
2323
|
+
/**
|
|
2324
|
+
* Async variant of NOTCH MI for use with async graph data sources.
|
|
2325
|
+
*
|
|
2326
|
+
* Fetches both neighbourhoods and node data concurrently, then counts
|
|
2327
|
+
* nodes of each type by iterating the async node stream.
|
|
2328
|
+
*/
|
|
2329
|
+
async function notchAsync(graph, source, target, config) {
|
|
2330
|
+
const { epsilon = 1e-10 } = config ?? {};
|
|
2331
|
+
const [sourceArr, targetArr, sourceNode, targetNode] = await Promise.all([
|
|
2332
|
+
require_async.collectAsyncIterable(graph.neighbours(source)),
|
|
2333
|
+
require_async.collectAsyncIterable(graph.neighbours(target)),
|
|
2334
|
+
graph.getNode(source),
|
|
2335
|
+
graph.getNode(target)
|
|
2336
|
+
]);
|
|
2337
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2338
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2339
|
+
let intersection = 0;
|
|
2340
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2341
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
2342
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
2343
|
+
if (sourceNode?.type === void 0 || targetNode?.type === void 0) return Math.max(epsilon, jaccardScore);
|
|
2344
|
+
const sourceType = sourceNode.type;
|
|
2345
|
+
const targetType = targetNode.type;
|
|
2346
|
+
let totalNodes = 0;
|
|
2347
|
+
let sourceTypeCount = 0;
|
|
2348
|
+
let targetTypeCount = 0;
|
|
2349
|
+
for await (const nodeId of graph.nodeIds()) {
|
|
2350
|
+
totalNodes++;
|
|
2351
|
+
const node = await graph.getNode(nodeId);
|
|
2352
|
+
if (node?.type === sourceType) sourceTypeCount++;
|
|
2353
|
+
if (node?.type === targetType) targetTypeCount++;
|
|
2354
|
+
}
|
|
2355
|
+
if (sourceTypeCount === 0 || targetTypeCount === 0) return Math.max(epsilon, jaccardScore);
|
|
2356
|
+
const sourceRarity = Math.log(totalNodes / sourceTypeCount);
|
|
2357
|
+
const targetRarity = Math.log(totalNodes / targetTypeCount);
|
|
2358
|
+
const score = jaccardScore * sourceRarity * targetRarity;
|
|
2359
|
+
return Math.max(epsilon, score);
|
|
2360
|
+
}
|
|
1811
2361
|
//#endregion
|
|
1812
2362
|
//#region src/ranking/mi/adaptive.ts
|
|
1813
2363
|
/**
|
|
@@ -1840,6 +2390,38 @@ function adaptive(graph, source, target, config) {
|
|
|
1840
2390
|
const score = (structuralWeight * structural + degreeWeight * degreeComponent + overlapWeight * overlap) / totalWeight;
|
|
1841
2391
|
return Math.max(epsilon, Math.min(1, score));
|
|
1842
2392
|
}
|
|
2393
|
+
/**
|
|
2394
|
+
* Async variant of Adaptive MI for use with async graph data sources.
|
|
2395
|
+
*
|
|
2396
|
+
* Fetches both neighbourhoods concurrently, then delegates degree-weighted
|
|
2397
|
+
* component to the async Adamic-Adar variant.
|
|
2398
|
+
*/
|
|
2399
|
+
async function adaptiveAsync(graph, source, target, config) {
|
|
2400
|
+
const { epsilon = 1e-10, structuralWeight = .4, degreeWeight = .3, overlapWeight = .3 } = config ?? {};
|
|
2401
|
+
const [sourceArr, targetArr, degreeComponent] = await Promise.all([
|
|
2402
|
+
require_async.collectAsyncIterable(graph.neighbours(source)),
|
|
2403
|
+
require_async.collectAsyncIterable(graph.neighbours(target)),
|
|
2404
|
+
adamicAdarAsync(graph, source, target, {
|
|
2405
|
+
epsilon,
|
|
2406
|
+
normalise: true
|
|
2407
|
+
})
|
|
2408
|
+
]);
|
|
2409
|
+
const srcSet = new Set(sourceArr.filter((n) => n !== target));
|
|
2410
|
+
const tgtSet = new Set(targetArr.filter((n) => n !== source));
|
|
2411
|
+
let intersection = 0;
|
|
2412
|
+
for (const n of srcSet) if (tgtSet.has(n)) intersection++;
|
|
2413
|
+
const union = srcSet.size + tgtSet.size - intersection;
|
|
2414
|
+
const jaccardScore = union > 0 ? intersection / union : 0;
|
|
2415
|
+
const structural = srcSet.size === 0 && tgtSet.size === 0 ? 0 : Math.max(epsilon, jaccardScore);
|
|
2416
|
+
let overlap;
|
|
2417
|
+
if (srcSet.size > 0 && tgtSet.size > 0) {
|
|
2418
|
+
const minDegree = Math.min(srcSet.size, tgtSet.size);
|
|
2419
|
+
overlap = minDegree > 0 ? intersection / minDegree : epsilon;
|
|
2420
|
+
} else overlap = epsilon;
|
|
2421
|
+
const totalWeight = structuralWeight + degreeWeight + overlapWeight;
|
|
2422
|
+
const score = (structuralWeight * structural + degreeWeight * degreeComponent + overlapWeight * overlap) / totalWeight;
|
|
2423
|
+
return Math.max(epsilon, Math.min(1, score));
|
|
2424
|
+
}
|
|
1843
2425
|
//#endregion
|
|
1844
2426
|
//#region src/ranking/baselines/utils.ts
|
|
1845
2427
|
/**
|
|
@@ -3340,7 +3922,9 @@ exports.GPUNotAvailableError = require_gpu.GPUNotAvailableError;
|
|
|
3340
3922
|
exports.PriorityQueue = require_structures.PriorityQueue;
|
|
3341
3923
|
exports._computeMean = require_kmeans._computeMean;
|
|
3342
3924
|
exports.adamicAdar = adamicAdar;
|
|
3925
|
+
exports.adamicAdarAsync = adamicAdarAsync;
|
|
3343
3926
|
exports.adaptive = adaptive;
|
|
3927
|
+
exports.adaptiveAsync = adaptiveAsync;
|
|
3344
3928
|
exports.approximateClusteringCoefficient = require_utils.approximateClusteringCoefficient;
|
|
3345
3929
|
exports.assertWebGPUAvailable = require_gpu.assertWebGPUAvailable;
|
|
3346
3930
|
exports.base = base;
|
|
@@ -3349,52 +3933,68 @@ exports.batchClusteringCoefficients = require_utils.batchClusteringCoefficients;
|
|
|
3349
3933
|
exports.betweenness = betweenness;
|
|
3350
3934
|
exports.bfs = require_traversal.bfs;
|
|
3351
3935
|
exports.bfsWithPath = require_traversal.bfsWithPath;
|
|
3936
|
+
exports.collectAsyncIterable = require_async.collectAsyncIterable;
|
|
3352
3937
|
exports.communicability = communicability;
|
|
3353
3938
|
exports.computeJaccard = require_utils.computeJaccard;
|
|
3354
3939
|
exports.computeTrussNumbers = computeTrussNumbers;
|
|
3355
3940
|
exports.cosine = cosine;
|
|
3941
|
+
exports.cosineAsync = cosineAsync;
|
|
3356
3942
|
exports.countEdgesOfType = require_utils.countEdgesOfType;
|
|
3357
3943
|
exports.countNodesOfType = require_utils.countNodesOfType;
|
|
3358
3944
|
exports.createGPUContext = require_gpu.createGPUContext;
|
|
3359
3945
|
exports.createResultBuffer = require_gpu.createResultBuffer;
|
|
3360
3946
|
exports.csrToGPUBuffers = require_gpu.csrToGPUBuffers;
|
|
3947
|
+
exports.defaultYieldStrategy = require_async.defaultYieldStrategy;
|
|
3361
3948
|
exports.degreeSum = degreeSum;
|
|
3362
3949
|
exports.detectWebGPU = require_gpu.detectWebGPU;
|
|
3363
3950
|
exports.dfs = require_traversal.dfs;
|
|
3364
3951
|
exports.dfsPriority = dfsPriority;
|
|
3952
|
+
exports.dfsPriorityAsync = dfsPriorityAsync;
|
|
3365
3953
|
exports.dfsPriorityFn = dfsPriorityFn;
|
|
3366
3954
|
exports.dfsWithPath = require_traversal.dfsWithPath;
|
|
3367
3955
|
exports.dome = dome;
|
|
3956
|
+
exports.domeAsync = domeAsync;
|
|
3368
3957
|
exports.domeHighDegree = domeHighDegree;
|
|
3958
|
+
exports.domeHighDegreeAsync = domeHighDegreeAsync;
|
|
3369
3959
|
exports.edge = edge;
|
|
3960
|
+
exports.edgeAsync = edgeAsync;
|
|
3370
3961
|
exports.entropyFromCounts = require_utils.entropyFromCounts;
|
|
3371
3962
|
exports.enumerateMotifs = enumerateMotifs;
|
|
3372
3963
|
exports.enumerateMotifsWithInstances = enumerateMotifsWithInstances;
|
|
3373
3964
|
exports.etch = etch;
|
|
3965
|
+
exports.etchAsync = etchAsync;
|
|
3374
3966
|
exports.extractEgoNetwork = extractEgoNetwork;
|
|
3375
3967
|
exports.extractInducedSubgraph = extractInducedSubgraph;
|
|
3376
3968
|
exports.extractKCore = extractKCore;
|
|
3377
3969
|
exports.extractKTruss = extractKTruss;
|
|
3378
3970
|
exports.filterSubgraph = filterSubgraph;
|
|
3379
3971
|
exports.flux = flux;
|
|
3972
|
+
exports.fluxAsync = fluxAsync;
|
|
3380
3973
|
exports.frontierBalanced = frontierBalanced;
|
|
3974
|
+
exports.frontierBalancedAsync = frontierBalancedAsync;
|
|
3381
3975
|
exports.fuse = fuse;
|
|
3976
|
+
exports.fuseAsync = fuseAsync;
|
|
3382
3977
|
exports.getGPUContext = require_gpu.getGPUContext;
|
|
3383
3978
|
exports.getMotifName = getMotifName;
|
|
3384
3979
|
exports.graphToCSR = require_gpu.graphToCSR;
|
|
3385
3980
|
exports.grasp = require_seeds.grasp;
|
|
3386
3981
|
exports.hae = hae;
|
|
3982
|
+
exports.haeAsync = haeAsync;
|
|
3387
3983
|
exports.hittingTime = hittingTime;
|
|
3388
3984
|
exports.hubPromoted = hubPromoted;
|
|
3985
|
+
exports.hubPromotedAsync = hubPromotedAsync;
|
|
3389
3986
|
exports.isWebGPUAvailable = require_gpu.isWebGPUAvailable;
|
|
3390
3987
|
exports.jaccard = jaccard;
|
|
3391
3988
|
exports.jaccardArithmetic = jaccardArithmetic;
|
|
3989
|
+
exports.jaccardAsync = jaccardAsync;
|
|
3392
3990
|
exports.kHop = kHop;
|
|
3393
3991
|
exports.katz = katz;
|
|
3394
3992
|
exports.lace = lace;
|
|
3993
|
+
exports.laceAsync = laceAsync;
|
|
3395
3994
|
exports.localClusteringCoefficient = require_utils.localClusteringCoefficient;
|
|
3396
3995
|
exports.localTypeEntropy = require_utils.localTypeEntropy;
|
|
3397
3996
|
exports.maze = maze;
|
|
3997
|
+
exports.mazeAsync = mazeAsync;
|
|
3398
3998
|
exports.miniBatchKMeans = require_kmeans.miniBatchKMeans;
|
|
3399
3999
|
exports.neighbourIntersection = require_utils.neighbourIntersection;
|
|
3400
4000
|
exports.neighbourOverlap = require_utils.neighbourOverlap;
|
|
@@ -3403,29 +4003,56 @@ exports.normaliseFeatures = require_kmeans.normaliseFeatures;
|
|
|
3403
4003
|
exports.zScoreNormalise = require_kmeans.normaliseFeatures;
|
|
3404
4004
|
exports.normalisedEntropy = require_utils.normalisedEntropy;
|
|
3405
4005
|
exports.notch = notch;
|
|
4006
|
+
exports.notchAsync = notchAsync;
|
|
4007
|
+
exports.opDegree = require_async.opDegree;
|
|
4008
|
+
exports.opGetEdge = require_async.opGetEdge;
|
|
4009
|
+
exports.opGetNode = require_async.opGetNode;
|
|
4010
|
+
exports.opHasNode = require_async.opHasNode;
|
|
4011
|
+
exports.opNeighbours = require_async.opNeighbours;
|
|
4012
|
+
exports.opProgress = require_async.opProgress;
|
|
4013
|
+
exports.opYield = require_async.opYield;
|
|
3406
4014
|
exports.overlapCoefficient = overlapCoefficient;
|
|
4015
|
+
exports.overlapCoefficientAsync = overlapCoefficientAsync;
|
|
3407
4016
|
exports.pagerank = pagerank;
|
|
3408
4017
|
exports.parse = parse;
|
|
4018
|
+
exports.parseAsync = parseAsync;
|
|
3409
4019
|
exports.pipe = pipe;
|
|
4020
|
+
exports.pipeAsync = pipeAsync;
|
|
3410
4021
|
exports.randomPriority = randomPriority;
|
|
4022
|
+
exports.randomPriorityAsync = randomPriorityAsync;
|
|
3411
4023
|
exports.randomRanking = randomRanking;
|
|
3412
4024
|
exports.randomWalk = randomWalk;
|
|
3413
4025
|
exports.reach = reach;
|
|
4026
|
+
exports.reachAsync = reachAsync;
|
|
3414
4027
|
exports.readBufferToCPU = require_gpu.readBufferToCPU;
|
|
3415
4028
|
exports.resistanceDistance = resistanceDistance;
|
|
4029
|
+
exports.resolveAsyncOp = require_async.resolveAsyncOp;
|
|
4030
|
+
exports.resolveSyncOp = require_async.resolveSyncOp;
|
|
3416
4031
|
exports.resourceAllocation = resourceAllocation;
|
|
4032
|
+
exports.resourceAllocationAsync = resourceAllocationAsync;
|
|
4033
|
+
exports.runAsync = require_async.runAsync;
|
|
4034
|
+
exports.runSync = require_async.runSync;
|
|
3417
4035
|
exports.sage = sage;
|
|
4036
|
+
exports.sageAsync = sageAsync;
|
|
3418
4037
|
exports.scale = scale;
|
|
4038
|
+
exports.scaleAsync = scaleAsync;
|
|
3419
4039
|
exports.shannonEntropy = require_utils.shannonEntropy;
|
|
3420
4040
|
exports.shortest = shortest;
|
|
3421
4041
|
exports.sift = sift;
|
|
4042
|
+
exports.siftAsync = siftAsync;
|
|
3422
4043
|
exports.skew = skew;
|
|
4044
|
+
exports.skewAsync = skewAsync;
|
|
3423
4045
|
exports.sorensen = sorensen;
|
|
4046
|
+
exports.sorensenAsync = sorensenAsync;
|
|
3424
4047
|
exports.span = span;
|
|
4048
|
+
exports.spanAsync = spanAsync;
|
|
3425
4049
|
exports.standardBfs = standardBfs;
|
|
4050
|
+
exports.standardBfsAsync = standardBfsAsync;
|
|
3426
4051
|
exports.stratified = require_seeds.stratified;
|
|
3427
4052
|
exports.tide = tide;
|
|
4053
|
+
exports.tideAsync = tideAsync;
|
|
3428
4054
|
exports.warp = warp;
|
|
4055
|
+
exports.warpAsync = warpAsync;
|
|
3429
4056
|
exports.widestPath = widestPath;
|
|
3430
4057
|
|
|
3431
4058
|
//# sourceMappingURL=index.cjs.map
|