graphwise 1.3.3 → 1.4.1
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 +71 -1
- package/dist/__test__/fixtures/graphs/city-suburban-village.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/city-suburban-village.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/city-village.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/city-village.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/index.d.ts +13 -0
- package/dist/__test__/fixtures/graphs/index.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/quality-vs-popularity.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/quality-vs-popularity.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/social-hub.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/social-hub.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/three-community.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/three-community.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/two-department.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/two-department.d.ts.map +1 -0
- package/dist/__test__/fixtures/graphs/typed-entity.d.ts +3 -0
- package/dist/__test__/fixtures/graphs/typed-entity.d.ts.map +1 -0
- package/dist/__test__/fixtures/helpers.d.ts +66 -0
- package/dist/__test__/fixtures/helpers.d.ts.map +1 -0
- package/dist/__test__/fixtures/helpers.unit.test.d.ts +7 -0
- package/dist/__test__/fixtures/helpers.unit.test.d.ts.map +1 -0
- package/dist/__test__/fixtures/index.d.ts +10 -0
- package/dist/__test__/fixtures/index.d.ts.map +1 -0
- package/dist/__test__/fixtures/types.d.ts +35 -0
- package/dist/__test__/fixtures/types.d.ts.map +1 -0
- package/dist/expansion/base.d.ts.map +1 -1
- package/dist/expansion/dome.d.ts.map +1 -1
- package/dist/expansion/dome.integration.test.d.ts +18 -0
- package/dist/expansion/dome.integration.test.d.ts.map +1 -0
- package/dist/expansion/edge.d.ts +3 -3
- package/dist/expansion/edge.d.ts.map +1 -1
- package/dist/expansion/edge.integration.test.d.ts +11 -0
- package/dist/expansion/edge.integration.test.d.ts.map +1 -0
- package/dist/expansion/flux.d.ts +25 -0
- package/dist/expansion/flux.d.ts.map +1 -0
- package/dist/expansion/flux.integration.test.d.ts +14 -0
- package/dist/expansion/flux.integration.test.d.ts.map +1 -0
- package/dist/expansion/flux.unit.test.d.ts +2 -0
- package/dist/expansion/flux.unit.test.d.ts.map +1 -0
- package/dist/expansion/fuse.d.ts +28 -0
- package/dist/expansion/fuse.d.ts.map +1 -0
- package/dist/expansion/fuse.integration.test.d.ts +15 -0
- package/dist/expansion/fuse.integration.test.d.ts.map +1 -0
- package/dist/expansion/fuse.unit.test.d.ts +2 -0
- package/dist/expansion/fuse.unit.test.d.ts.map +1 -0
- package/dist/expansion/hae.d.ts +7 -7
- package/dist/expansion/hae.d.ts.map +1 -1
- package/dist/expansion/hae.integration.test.d.ts +11 -0
- package/dist/expansion/hae.integration.test.d.ts.map +1 -0
- package/dist/expansion/index.d.ts +6 -0
- package/dist/expansion/index.d.ts.map +1 -1
- package/dist/expansion/lace.d.ts +22 -0
- package/dist/expansion/lace.d.ts.map +1 -0
- package/dist/expansion/lace.integration.test.d.ts +14 -0
- package/dist/expansion/lace.integration.test.d.ts.map +1 -0
- package/dist/expansion/lace.unit.test.d.ts +2 -0
- package/dist/expansion/lace.unit.test.d.ts.map +1 -0
- package/dist/expansion/maze.d.ts +3 -13
- package/dist/expansion/maze.d.ts.map +1 -1
- package/dist/expansion/maze.integration.test.d.ts +11 -0
- package/dist/expansion/maze.integration.test.d.ts.map +1 -0
- package/dist/expansion/pipe.d.ts +3 -3
- package/dist/expansion/pipe.d.ts.map +1 -1
- package/dist/expansion/pipe.integration.test.d.ts +12 -0
- package/dist/expansion/pipe.integration.test.d.ts.map +1 -0
- package/dist/expansion/reach.d.ts +4 -14
- package/dist/expansion/reach.d.ts.map +1 -1
- package/dist/expansion/reach.integration.test.d.ts +9 -0
- package/dist/expansion/reach.integration.test.d.ts.map +1 -0
- package/dist/expansion/sage.d.ts +5 -13
- package/dist/expansion/sage.d.ts.map +1 -1
- package/dist/expansion/sage.integration.test.d.ts +9 -0
- package/dist/expansion/sage.integration.test.d.ts.map +1 -0
- package/dist/expansion/sift.d.ts +26 -0
- package/dist/expansion/sift.d.ts.map +1 -0
- package/dist/expansion/sift.integration.test.d.ts +13 -0
- package/dist/expansion/sift.integration.test.d.ts.map +1 -0
- package/dist/expansion/sift.unit.test.d.ts +2 -0
- package/dist/expansion/sift.unit.test.d.ts.map +1 -0
- package/dist/expansion/tide.d.ts +15 -0
- package/dist/expansion/tide.d.ts.map +1 -0
- package/dist/expansion/tide.integration.test.d.ts +14 -0
- package/dist/expansion/tide.integration.test.d.ts.map +1 -0
- package/dist/expansion/tide.unit.test.d.ts +2 -0
- package/dist/expansion/tide.unit.test.d.ts.map +1 -0
- package/dist/expansion/warp.d.ts +15 -0
- package/dist/expansion/warp.d.ts.map +1 -0
- package/dist/expansion/warp.integration.test.d.ts +13 -0
- package/dist/expansion/warp.integration.test.d.ts.map +1 -0
- package/dist/expansion/warp.unit.test.d.ts +2 -0
- package/dist/expansion/warp.unit.test.d.ts.map +1 -0
- package/dist/graph/adjacency-map.d.ts.map +1 -1
- package/dist/graph/index.cjs +7 -0
- package/dist/graph/index.cjs.map +1 -1
- package/dist/graph/index.js +7 -0
- package/dist/graph/index.js.map +1 -1
- package/dist/index/index.cjs +323 -60
- package/dist/index/index.cjs.map +1 -1
- package/dist/index/index.js +318 -61
- package/dist/index/index.js.map +1 -1
- package/dist/ranking/mi/etch.integration.test.d.ts +2 -0
- package/dist/ranking/mi/etch.integration.test.d.ts.map +1 -0
- package/dist/ranking/mi/notch.integration.test.d.ts +2 -0
- package/dist/ranking/mi/notch.integration.test.d.ts.map +1 -0
- package/dist/ranking/mi/scale.d.ts.map +1 -1
- package/dist/ranking/mi/scale.integration.test.d.ts +2 -0
- package/dist/ranking/mi/scale.integration.test.d.ts.map +1 -0
- package/dist/ranking/mi/skew.integration.test.d.ts +2 -0
- package/dist/ranking/mi/skew.integration.test.d.ts.map +1 -0
- package/dist/ranking/mi/span.integration.test.d.ts +2 -0
- package/dist/ranking/mi/span.integration.test.d.ts.map +1 -0
- package/dist/ranking/parse.integration.test.d.ts +10 -0
- package/dist/ranking/parse.integration.test.d.ts.map +1 -0
- package/dist/structures/index.cjs +12 -2
- package/dist/structures/index.cjs.map +1 -1
- package/dist/structures/index.js +12 -2
- package/dist/structures/index.js.map +1 -1
- package/dist/structures/priority-queue.d.ts +4 -2
- package/dist/structures/priority-queue.d.ts.map +1 -1
- package/dist/utils/index.cjs +76 -77
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.js +76 -77
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index/index.cjs
CHANGED
|
@@ -26,6 +26,8 @@ function base(graph, seeds, config) {
|
|
|
26
26
|
const { maxNodes = 0, maxIterations = 0, maxPaths = 0, priority = degreePriority, debug = false } = config ?? {};
|
|
27
27
|
if (seeds.length === 0) return emptyResult("base", startTime);
|
|
28
28
|
const numFrontiers = seeds.length;
|
|
29
|
+
const allVisited = /* @__PURE__ */ new Set();
|
|
30
|
+
const combinedVisited = /* @__PURE__ */ new Map();
|
|
29
31
|
const visitedByFrontier = [];
|
|
30
32
|
const predecessors = [];
|
|
31
33
|
const queues = [];
|
|
@@ -37,15 +39,16 @@ function base(graph, seeds, config) {
|
|
|
37
39
|
if (seed === void 0) continue;
|
|
38
40
|
const seedNode = seed.id;
|
|
39
41
|
predecessors[i]?.set(seedNode, null);
|
|
40
|
-
|
|
42
|
+
combinedVisited.set(seedNode, i);
|
|
43
|
+
allVisited.add(seedNode);
|
|
44
|
+
const seedPriority = priority(seedNode, createPriorityContext(graph, seedNode, i, combinedVisited, allVisited, [], 0));
|
|
41
45
|
queues[i]?.push({
|
|
42
46
|
nodeId: seedNode,
|
|
43
47
|
frontierIndex: i,
|
|
44
48
|
predecessor: null
|
|
45
49
|
}, seedPriority);
|
|
46
50
|
}
|
|
47
|
-
const
|
|
48
|
-
const sampledEdges = /* @__PURE__ */ new Set();
|
|
51
|
+
const sampledEdgeMap = /* @__PURE__ */ new Map();
|
|
49
52
|
const discoveredPaths = [];
|
|
50
53
|
let iterations = 0;
|
|
51
54
|
let edgesTraversed = 0;
|
|
@@ -90,6 +93,7 @@ function base(graph, seeds, config) {
|
|
|
90
93
|
const frontierVisited = visitedByFrontier[activeFrontier];
|
|
91
94
|
if (frontierVisited === void 0 || frontierVisited.has(nodeId)) continue;
|
|
92
95
|
frontierVisited.set(nodeId, activeFrontier);
|
|
96
|
+
combinedVisited.set(nodeId, activeFrontier);
|
|
93
97
|
if (predecessor !== null) {
|
|
94
98
|
const predMap = predecessors[activeFrontier];
|
|
95
99
|
if (predMap !== void 0) predMap.set(nodeId, predecessor);
|
|
@@ -111,11 +115,16 @@ function base(graph, seeds, config) {
|
|
|
111
115
|
const neighbours = graph.neighbours(nodeId);
|
|
112
116
|
for (const neighbour of neighbours) {
|
|
113
117
|
edgesTraversed++;
|
|
114
|
-
const
|
|
115
|
-
|
|
118
|
+
const [s, t] = nodeId < neighbour ? [nodeId, neighbour] : [neighbour, nodeId];
|
|
119
|
+
let targets = sampledEdgeMap.get(s);
|
|
120
|
+
if (targets === void 0) {
|
|
121
|
+
targets = /* @__PURE__ */ new Set();
|
|
122
|
+
sampledEdgeMap.set(s, targets);
|
|
123
|
+
}
|
|
124
|
+
targets.add(t);
|
|
116
125
|
const frontierVisited = visitedByFrontier[activeFrontier];
|
|
117
126
|
if (frontierVisited === void 0 || frontierVisited.has(neighbour)) continue;
|
|
118
|
-
const neighbourPriority = priority(neighbour, createPriorityContext(graph, neighbour, activeFrontier,
|
|
127
|
+
const neighbourPriority = priority(neighbour, createPriorityContext(graph, neighbour, activeFrontier, combinedVisited, allVisited, discoveredPaths, iterations + 1));
|
|
119
128
|
queue.push({
|
|
120
129
|
nodeId: neighbour,
|
|
121
130
|
frontierIndex: activeFrontier,
|
|
@@ -127,14 +136,7 @@ function base(graph, seeds, config) {
|
|
|
127
136
|
const endTime = performance.now();
|
|
128
137
|
const visitedPerFrontier = visitedByFrontier.map((m) => new Set(m.keys()));
|
|
129
138
|
const edgeTuples = /* @__PURE__ */ new Set();
|
|
130
|
-
for (const
|
|
131
|
-
const parts = edgeKey.split("::");
|
|
132
|
-
if (parts.length === 2) {
|
|
133
|
-
const source = parts[0];
|
|
134
|
-
const target = parts[1];
|
|
135
|
-
if (source !== void 0 && target !== void 0) edgeTuples.add([source, target]);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
139
|
+
for (const [source, targets] of sampledEdgeMap) for (const target of targets) edgeTuples.add([source, target]);
|
|
138
140
|
return {
|
|
139
141
|
paths: discoveredPaths,
|
|
140
142
|
sampledNodes: allVisited,
|
|
@@ -154,10 +156,7 @@ function base(graph, seeds, config) {
|
|
|
154
156
|
/**
|
|
155
157
|
* Create priority context for a node.
|
|
156
158
|
*/
|
|
157
|
-
function createPriorityContext(graph, nodeId, frontierIndex,
|
|
158
|
-
const combinedVisited = /* @__PURE__ */ new Map();
|
|
159
|
-
for (const frontierMap of visitedByFrontier) for (const [id, idx] of frontierMap) combinedVisited.set(id, idx);
|
|
160
|
-
const allVisited = new Set(combinedVisited.keys());
|
|
159
|
+
function createPriorityContext(graph, nodeId, frontierIndex, combinedVisited, allVisited, discoveredPaths, iteration) {
|
|
161
160
|
return {
|
|
162
161
|
graph,
|
|
163
162
|
degree: graph.degree(nodeId),
|
|
@@ -236,7 +235,7 @@ function emptyResult(algorithm, startTime) {
|
|
|
236
235
|
*/
|
|
237
236
|
function dome(graph, seeds, config) {
|
|
238
237
|
const domePriority = (nodeId, context) => {
|
|
239
|
-
return
|
|
238
|
+
return context.degree;
|
|
240
239
|
};
|
|
241
240
|
return base(graph, seeds, {
|
|
242
241
|
...config,
|
|
@@ -248,7 +247,7 @@ function dome(graph, seeds, config) {
|
|
|
248
247
|
*/
|
|
249
248
|
function domeHighDegree(graph, seeds, config) {
|
|
250
249
|
const domePriority = (nodeId, context) => {
|
|
251
|
-
return -
|
|
250
|
+
return -context.degree;
|
|
252
251
|
};
|
|
253
252
|
return base(graph, seeds, {
|
|
254
253
|
...config,
|
|
@@ -257,23 +256,26 @@ function domeHighDegree(graph, seeds, config) {
|
|
|
257
256
|
}
|
|
258
257
|
//#endregion
|
|
259
258
|
//#region src/expansion/edge.ts
|
|
259
|
+
var EPSILON$1 = 1e-10;
|
|
260
260
|
/**
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
* Priority = degree(source) + degree(target)
|
|
264
|
-
* Lower values = higher priority (explored first)
|
|
261
|
+
* Priority function using local type entropy.
|
|
262
|
+
* Lower values = higher priority (expanded first).
|
|
265
263
|
*/
|
|
266
264
|
function edgePriority(nodeId, context) {
|
|
267
265
|
const graph = context.graph;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
266
|
+
const neighbours = graph.neighbours(nodeId);
|
|
267
|
+
const neighbourTypes = [];
|
|
268
|
+
for (const neighbour of neighbours) {
|
|
269
|
+
const node = graph.getNode(neighbour);
|
|
270
|
+
neighbourTypes.push(node?.type ?? "default");
|
|
271
|
+
}
|
|
272
|
+
return 1 / (require_utils.localTypeEntropy(neighbourTypes) + EPSILON$1) * Math.log(context.degree + 1);
|
|
271
273
|
}
|
|
272
274
|
/**
|
|
273
|
-
* Run EDGE expansion
|
|
275
|
+
* Run EDGE expansion (Entropy-Driven Graph Expansion).
|
|
274
276
|
*
|
|
275
|
-
*
|
|
276
|
-
*
|
|
277
|
+
* Discovers paths by prioritising nodes with diverse neighbour types,
|
|
278
|
+
* deferring nodes with homogeneous neighbourhoods.
|
|
277
279
|
*
|
|
278
280
|
* @param graph - Source graph
|
|
279
281
|
* @param seeds - Seed nodes for expansion
|
|
@@ -287,6 +289,122 @@ function edge(graph, seeds, config) {
|
|
|
287
289
|
});
|
|
288
290
|
}
|
|
289
291
|
//#endregion
|
|
292
|
+
//#region src/expansion/hae.ts
|
|
293
|
+
var EPSILON = 1e-10;
|
|
294
|
+
/**
|
|
295
|
+
* Default type mapper - uses node.type property.
|
|
296
|
+
*/
|
|
297
|
+
function defaultTypeMapper(node) {
|
|
298
|
+
return node.type ?? "default";
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Create a priority function using the given type mapper.
|
|
302
|
+
*/
|
|
303
|
+
function createHAEPriority(typeMapper) {
|
|
304
|
+
return function haePriority(nodeId, context) {
|
|
305
|
+
const graph = context.graph;
|
|
306
|
+
const neighbours = graph.neighbours(nodeId);
|
|
307
|
+
const neighbourTypes = [];
|
|
308
|
+
for (const neighbour of neighbours) {
|
|
309
|
+
const node = graph.getNode(neighbour);
|
|
310
|
+
if (node !== void 0) neighbourTypes.push(typeMapper(node));
|
|
311
|
+
}
|
|
312
|
+
return 1 / (require_utils.localTypeEntropy(neighbourTypes) + EPSILON) * Math.log(context.degree + 1);
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Run HAE expansion (Heterogeneity-Aware Expansion).
|
|
317
|
+
*
|
|
318
|
+
* Discovers paths by prioritising nodes with diverse neighbour types,
|
|
319
|
+
* using a custom type mapper for flexible type extraction.
|
|
320
|
+
*
|
|
321
|
+
* @param graph - Source graph
|
|
322
|
+
* @param seeds - Seed nodes for expansion
|
|
323
|
+
* @param config - HAE configuration with optional typeMapper
|
|
324
|
+
* @returns Expansion result with discovered paths
|
|
325
|
+
*/
|
|
326
|
+
function hae(graph, seeds, config) {
|
|
327
|
+
const typeMapper = config?.typeMapper ?? defaultTypeMapper;
|
|
328
|
+
return base(graph, seeds, {
|
|
329
|
+
...config,
|
|
330
|
+
priority: createHAEPriority(typeMapper)
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
//#endregion
|
|
334
|
+
//#region src/expansion/pipe.ts
|
|
335
|
+
/**
|
|
336
|
+
* Priority function using path potential.
|
|
337
|
+
* Lower values = higher priority (expanded first).
|
|
338
|
+
*
|
|
339
|
+
* Path potential measures how many of a node's neighbours have been
|
|
340
|
+
* visited by OTHER frontiers (not the current frontier).
|
|
341
|
+
*/
|
|
342
|
+
function pipePriority(nodeId, context) {
|
|
343
|
+
const neighbours = context.graph.neighbours(nodeId);
|
|
344
|
+
let pathPotential = 0;
|
|
345
|
+
for (const neighbour of neighbours) {
|
|
346
|
+
const visitedBy = context.visitedByFrontier.get(neighbour);
|
|
347
|
+
if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
|
|
348
|
+
}
|
|
349
|
+
return context.degree / (1 + pathPotential);
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Run PIPE expansion (Path-Potential Informed Priority Expansion).
|
|
353
|
+
*
|
|
354
|
+
* Discovers paths by prioritising nodes that bridge multiple frontiers,
|
|
355
|
+
* identifying connecting points between seed regions.
|
|
356
|
+
*
|
|
357
|
+
* @param graph - Source graph
|
|
358
|
+
* @param seeds - Seed nodes for expansion
|
|
359
|
+
* @param config - Expansion configuration
|
|
360
|
+
* @returns Expansion result with discovered paths
|
|
361
|
+
*/
|
|
362
|
+
function pipe(graph, seeds, config) {
|
|
363
|
+
return base(graph, seeds, {
|
|
364
|
+
...config,
|
|
365
|
+
priority: pipePriority
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/expansion/sage.ts
|
|
370
|
+
/**
|
|
371
|
+
* Run SAGE expansion algorithm.
|
|
372
|
+
*
|
|
373
|
+
* Salience-aware multi-frontier expansion with two phases:
|
|
374
|
+
* - Phase 1: Degree-based priority (early exploration)
|
|
375
|
+
* - Phase 2: Salience feedback (path-aware frontier steering)
|
|
376
|
+
*
|
|
377
|
+
* @param graph - Source graph
|
|
378
|
+
* @param seeds - Seed nodes for expansion
|
|
379
|
+
* @param config - Expansion configuration
|
|
380
|
+
* @returns Expansion result with discovered paths
|
|
381
|
+
*/
|
|
382
|
+
function sage(graph, seeds, config) {
|
|
383
|
+
const salienceCounts = /* @__PURE__ */ new Map();
|
|
384
|
+
let inPhase2 = false;
|
|
385
|
+
let lastPathCount = 0;
|
|
386
|
+
/**
|
|
387
|
+
* SAGE priority function with phase transition logic.
|
|
388
|
+
*/
|
|
389
|
+
function sagePriority(nodeId, context) {
|
|
390
|
+
const pathCount = context.discoveredPaths.length;
|
|
391
|
+
if (pathCount > 0 && !inPhase2) inPhase2 = true;
|
|
392
|
+
if (pathCount > lastPathCount) {
|
|
393
|
+
for (let i = lastPathCount; i < pathCount; i++) {
|
|
394
|
+
const path = context.discoveredPaths[i];
|
|
395
|
+
if (path !== void 0) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
396
|
+
}
|
|
397
|
+
lastPathCount = pathCount;
|
|
398
|
+
}
|
|
399
|
+
if (!inPhase2) return Math.log(context.degree + 1);
|
|
400
|
+
return -((salienceCounts.get(nodeId) ?? 0) * 1e3 - context.degree);
|
|
401
|
+
}
|
|
402
|
+
return base(graph, seeds, {
|
|
403
|
+
...config,
|
|
404
|
+
priority: sagePriority
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
290
408
|
//#region src/ranking/mi/jaccard.ts
|
|
291
409
|
/**
|
|
292
410
|
* Compute Jaccard similarity between neighbourhoods of two nodes.
|
|
@@ -305,14 +423,153 @@ function jaccard(graph, source, target, config) {
|
|
|
305
423
|
return Math.max(epsilon, score);
|
|
306
424
|
}
|
|
307
425
|
//#endregion
|
|
308
|
-
//#region src/expansion/
|
|
426
|
+
//#region src/expansion/reach.ts
|
|
309
427
|
/**
|
|
310
|
-
*
|
|
428
|
+
* Run REACH expansion algorithm.
|
|
429
|
+
*
|
|
430
|
+
* Mutual information-aware multi-frontier expansion with two phases:
|
|
431
|
+
* - Phase 1: Degree-based priority (early exploration)
|
|
432
|
+
* - Phase 2: Structural similarity feedback (MI-guided frontier steering)
|
|
433
|
+
*
|
|
434
|
+
* @param graph - Source graph
|
|
435
|
+
* @param seeds - Seed nodes for expansion
|
|
436
|
+
* @param config - Expansion configuration
|
|
437
|
+
* @returns Expansion result with discovered paths
|
|
438
|
+
*/
|
|
439
|
+
function reach(graph, seeds, config) {
|
|
440
|
+
let inPhase2 = false;
|
|
441
|
+
const jaccardCache = /* @__PURE__ */ new Map();
|
|
442
|
+
/**
|
|
443
|
+
* Compute Jaccard similarity with caching.
|
|
444
|
+
*
|
|
445
|
+
* Exploits symmetry of Jaccard (J(A,B) = J(B,A)) to reduce
|
|
446
|
+
* duplicate computations when the same pair appears in multiple
|
|
447
|
+
* discovered paths. Key format ensures consistent ordering.
|
|
448
|
+
*/
|
|
449
|
+
function cachedJaccard(source, target) {
|
|
450
|
+
const key = source < target ? `${source}::${target}` : `${target}::${source}`;
|
|
451
|
+
let score = jaccardCache.get(key);
|
|
452
|
+
if (score === void 0) {
|
|
453
|
+
score = jaccard(graph, source, target);
|
|
454
|
+
jaccardCache.set(key, score);
|
|
455
|
+
}
|
|
456
|
+
return score;
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* REACH priority function with MI estimation.
|
|
460
|
+
*/
|
|
461
|
+
function reachPriority(nodeId, context) {
|
|
462
|
+
if (context.discoveredPaths.length > 0 && !inPhase2) inPhase2 = true;
|
|
463
|
+
if (!inPhase2) return Math.log(context.degree + 1);
|
|
464
|
+
let totalMI = 0;
|
|
465
|
+
let endpointCount = 0;
|
|
466
|
+
for (const path of context.discoveredPaths) {
|
|
467
|
+
const fromNodeId = path.fromSeed.id;
|
|
468
|
+
const toNodeId = path.toSeed.id;
|
|
469
|
+
totalMI += cachedJaccard(nodeId, fromNodeId);
|
|
470
|
+
totalMI += cachedJaccard(nodeId, toNodeId);
|
|
471
|
+
endpointCount += 2;
|
|
472
|
+
}
|
|
473
|
+
const miHat = endpointCount > 0 ? totalMI / endpointCount : 0;
|
|
474
|
+
return Math.log(context.degree + 1) * (1 - miHat);
|
|
475
|
+
}
|
|
476
|
+
return base(graph, seeds, {
|
|
477
|
+
...config,
|
|
478
|
+
priority: reachPriority
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
//#endregion
|
|
482
|
+
//#region src/expansion/maze.ts
|
|
483
|
+
/** Default threshold for switching to phase 2 (after M paths) */
|
|
484
|
+
var DEFAULT_PHASE2_THRESHOLD = 1;
|
|
485
|
+
/** Salience weighting factor */
|
|
486
|
+
var SALIENCE_WEIGHT = 1e3;
|
|
487
|
+
/**
|
|
488
|
+
* Run MAZE expansion algorithm.
|
|
489
|
+
*
|
|
490
|
+
* Multi-phase expansion combining path potential and salience with
|
|
491
|
+
* adaptive frontier steering.
|
|
492
|
+
*
|
|
493
|
+
* @param graph - Source graph
|
|
494
|
+
* @param seeds - Seed nodes for expansion
|
|
495
|
+
* @param config - Expansion configuration
|
|
496
|
+
* @returns Expansion result with discovered paths
|
|
497
|
+
*/
|
|
498
|
+
function maze(graph, seeds, config) {
|
|
499
|
+
const salienceCounts = /* @__PURE__ */ new Map();
|
|
500
|
+
let inPhase2 = false;
|
|
501
|
+
let lastPathCount = 0;
|
|
502
|
+
/**
|
|
503
|
+
* MAZE priority function with path potential and salience feedback.
|
|
504
|
+
*/
|
|
505
|
+
function mazePriority(nodeId, context) {
|
|
506
|
+
const pathCount = context.discoveredPaths.length;
|
|
507
|
+
if (pathCount >= DEFAULT_PHASE2_THRESHOLD && !inPhase2) {
|
|
508
|
+
inPhase2 = true;
|
|
509
|
+
for (const path of context.discoveredPaths) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
510
|
+
}
|
|
511
|
+
if (inPhase2 && pathCount > lastPathCount) {
|
|
512
|
+
for (let i = lastPathCount; i < pathCount; i++) {
|
|
513
|
+
const path = context.discoveredPaths[i];
|
|
514
|
+
if (path !== void 0) for (const node of path.nodes) salienceCounts.set(node, (salienceCounts.get(node) ?? 0) + 1);
|
|
515
|
+
}
|
|
516
|
+
lastPathCount = pathCount;
|
|
517
|
+
}
|
|
518
|
+
const nodeNeighbours = graph.neighbours(nodeId);
|
|
519
|
+
let pathPotential = 0;
|
|
520
|
+
for (const neighbour of nodeNeighbours) {
|
|
521
|
+
const visitedBy = context.visitedByFrontier.get(neighbour);
|
|
522
|
+
if (visitedBy !== void 0 && visitedBy !== context.frontierIndex) pathPotential++;
|
|
523
|
+
}
|
|
524
|
+
if (!inPhase2) return context.degree / (1 + pathPotential);
|
|
525
|
+
const salience = salienceCounts.get(nodeId) ?? 0;
|
|
526
|
+
return context.degree / (1 + pathPotential) * (1 / (1 + SALIENCE_WEIGHT * salience));
|
|
527
|
+
}
|
|
528
|
+
return base(graph, seeds, {
|
|
529
|
+
...config,
|
|
530
|
+
priority: mazePriority
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region src/expansion/tide.ts
|
|
535
|
+
/**
|
|
536
|
+
* TIDE priority function.
|
|
537
|
+
*
|
|
538
|
+
* Priority = degree(source) + degree(target)
|
|
539
|
+
* Lower values = higher priority (explored first)
|
|
540
|
+
*/
|
|
541
|
+
function tidePriority(nodeId, context) {
|
|
542
|
+
const graph = context.graph;
|
|
543
|
+
let totalDegree = context.degree;
|
|
544
|
+
for (const neighbour of graph.neighbours(nodeId)) totalDegree += graph.degree(neighbour);
|
|
545
|
+
return totalDegree;
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* Run TIDE expansion algorithm.
|
|
549
|
+
*
|
|
550
|
+
* Expands from seeds prioritising low-degree edges first.
|
|
551
|
+
* Useful for avoiding hubs and exploring sparse regions.
|
|
552
|
+
*
|
|
553
|
+
* @param graph - Source graph
|
|
554
|
+
* @param seeds - Seed nodes for expansion
|
|
555
|
+
* @param config - Expansion configuration
|
|
556
|
+
* @returns Expansion result with discovered paths
|
|
557
|
+
*/
|
|
558
|
+
function tide(graph, seeds, config) {
|
|
559
|
+
return base(graph, seeds, {
|
|
560
|
+
...config,
|
|
561
|
+
priority: tidePriority
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
//#endregion
|
|
565
|
+
//#region src/expansion/lace.ts
|
|
566
|
+
/**
|
|
567
|
+
* LACE priority function.
|
|
311
568
|
*
|
|
312
569
|
* Priority = 1 - MI(source, neighbour)
|
|
313
570
|
* Higher MI = lower priority value = explored first
|
|
314
571
|
*/
|
|
315
|
-
function
|
|
572
|
+
function lacePriority(nodeId, context, mi) {
|
|
316
573
|
const graph = context.graph;
|
|
317
574
|
const frontierIndex = context.frontierIndex;
|
|
318
575
|
let maxMi = 0;
|
|
@@ -327,7 +584,7 @@ function haePriority(nodeId, context, mi) {
|
|
|
327
584
|
return 1 - (count > 0 ? totalMi / count : 0);
|
|
328
585
|
}
|
|
329
586
|
/**
|
|
330
|
-
* Run
|
|
587
|
+
* Run LACE expansion algorithm.
|
|
331
588
|
*
|
|
332
589
|
* Expands from seeds prioritising high-MI edges.
|
|
333
590
|
* Useful for finding paths with strong semantic associations.
|
|
@@ -337,16 +594,16 @@ function haePriority(nodeId, context, mi) {
|
|
|
337
594
|
* @param config - Expansion configuration with MI function
|
|
338
595
|
* @returns Expansion result with discovered paths
|
|
339
596
|
*/
|
|
340
|
-
function
|
|
597
|
+
function lace(graph, seeds, config) {
|
|
341
598
|
const { mi = jaccard, ...restConfig } = config ?? {};
|
|
342
|
-
const priority = (nodeId, context) =>
|
|
599
|
+
const priority = (nodeId, context) => lacePriority(nodeId, context, mi);
|
|
343
600
|
return base(graph, seeds, {
|
|
344
601
|
...restConfig,
|
|
345
602
|
priority
|
|
346
603
|
});
|
|
347
604
|
}
|
|
348
605
|
//#endregion
|
|
349
|
-
//#region src/expansion/
|
|
606
|
+
//#region src/expansion/warp.ts
|
|
350
607
|
/**
|
|
351
608
|
* PIPE priority function.
|
|
352
609
|
*
|
|
@@ -354,7 +611,7 @@ function hae(graph, seeds, config) {
|
|
|
354
611
|
* Bridge score = neighbourhood overlap with other frontiers
|
|
355
612
|
* Higher bridge score = more likely to be on paths = explored first
|
|
356
613
|
*/
|
|
357
|
-
function
|
|
614
|
+
function warpPriority(nodeId, context) {
|
|
358
615
|
const graph = context.graph;
|
|
359
616
|
const currentFrontier = context.frontierIndex;
|
|
360
617
|
const nodeNeighbours = new Set(graph.neighbours(nodeId));
|
|
@@ -364,7 +621,7 @@ function pipePriority(nodeId, context) {
|
|
|
364
621
|
return 1 / (1 + bridgeScore);
|
|
365
622
|
}
|
|
366
623
|
/**
|
|
367
|
-
* Run
|
|
624
|
+
* Run WARP expansion algorithm.
|
|
368
625
|
*
|
|
369
626
|
* Expands from seeds prioritising bridge nodes.
|
|
370
627
|
* Useful for finding paths through structurally important nodes.
|
|
@@ -374,14 +631,14 @@ function pipePriority(nodeId, context) {
|
|
|
374
631
|
* @param config - Expansion configuration
|
|
375
632
|
* @returns Expansion result with discovered paths
|
|
376
633
|
*/
|
|
377
|
-
function
|
|
634
|
+
function warp(graph, seeds, config) {
|
|
378
635
|
return base(graph, seeds, {
|
|
379
636
|
...config,
|
|
380
|
-
priority:
|
|
637
|
+
priority: warpPriority
|
|
381
638
|
});
|
|
382
639
|
}
|
|
383
640
|
//#endregion
|
|
384
|
-
//#region src/expansion/
|
|
641
|
+
//#region src/expansion/fuse.ts
|
|
385
642
|
/**
|
|
386
643
|
* SAGE priority function.
|
|
387
644
|
*
|
|
@@ -389,7 +646,7 @@ function pipe(graph, seeds, config) {
|
|
|
389
646
|
* Priority = (1 - w) * degree + w * (1 - avg_salience)
|
|
390
647
|
* Lower values = higher priority
|
|
391
648
|
*/
|
|
392
|
-
function
|
|
649
|
+
function fusePriority(nodeId, context, mi, salienceWeight) {
|
|
393
650
|
const graph = context.graph;
|
|
394
651
|
const degree = context.degree;
|
|
395
652
|
const frontierIndex = context.frontierIndex;
|
|
@@ -403,7 +660,7 @@ function sagePriority(nodeId, context, mi, salienceWeight) {
|
|
|
403
660
|
return (1 - salienceWeight) * degree + salienceWeight * (1 - avgSalience);
|
|
404
661
|
}
|
|
405
662
|
/**
|
|
406
|
-
* Run
|
|
663
|
+
* Run FUSE expansion algorithm.
|
|
407
664
|
*
|
|
408
665
|
* Combines structural exploration with semantic salience.
|
|
409
666
|
* Useful for finding paths that are both short and semantically meaningful.
|
|
@@ -413,22 +670,22 @@ function sagePriority(nodeId, context, mi, salienceWeight) {
|
|
|
413
670
|
* @param config - Expansion configuration with MI function
|
|
414
671
|
* @returns Expansion result with discovered paths
|
|
415
672
|
*/
|
|
416
|
-
function
|
|
673
|
+
function fuse(graph, seeds, config) {
|
|
417
674
|
const { mi = jaccard, salienceWeight = .5, ...restConfig } = config ?? {};
|
|
418
|
-
const priority = (nodeId, context) =>
|
|
675
|
+
const priority = (nodeId, context) => fusePriority(nodeId, context, mi, salienceWeight);
|
|
419
676
|
return base(graph, seeds, {
|
|
420
677
|
...restConfig,
|
|
421
678
|
priority
|
|
422
679
|
});
|
|
423
680
|
}
|
|
424
681
|
//#endregion
|
|
425
|
-
//#region src/expansion/
|
|
682
|
+
//#region src/expansion/sift.ts
|
|
426
683
|
/**
|
|
427
684
|
* REACH priority function (phase 2).
|
|
428
685
|
*
|
|
429
686
|
* Uses learned MI threshold to prioritise high-MI edges.
|
|
430
687
|
*/
|
|
431
|
-
function
|
|
688
|
+
function siftPriority(nodeId, context, mi, miThreshold) {
|
|
432
689
|
const graph = context.graph;
|
|
433
690
|
const frontierIndex = context.frontierIndex;
|
|
434
691
|
let totalMi = 0;
|
|
@@ -442,7 +699,7 @@ function reachPriority(nodeId, context, mi, miThreshold) {
|
|
|
442
699
|
else return context.degree + 100;
|
|
443
700
|
}
|
|
444
701
|
/**
|
|
445
|
-
* Run
|
|
702
|
+
* Run SIFT expansion algorithm.
|
|
446
703
|
*
|
|
447
704
|
* Two-phase adaptive expansion that learns MI thresholds
|
|
448
705
|
* from initial sampling, then uses them for guided expansion.
|
|
@@ -452,16 +709,16 @@ function reachPriority(nodeId, context, mi, miThreshold) {
|
|
|
452
709
|
* @param config - Expansion configuration
|
|
453
710
|
* @returns Expansion result with discovered paths
|
|
454
711
|
*/
|
|
455
|
-
function
|
|
712
|
+
function sift(graph, seeds, config) {
|
|
456
713
|
const { mi = jaccard, miThreshold = .25, ...restConfig } = config ?? {};
|
|
457
|
-
const priority = (nodeId, context) =>
|
|
714
|
+
const priority = (nodeId, context) => siftPriority(nodeId, context, mi, miThreshold);
|
|
458
715
|
return base(graph, seeds, {
|
|
459
716
|
...restConfig,
|
|
460
717
|
priority
|
|
461
718
|
});
|
|
462
719
|
}
|
|
463
720
|
//#endregion
|
|
464
|
-
//#region src/expansion/
|
|
721
|
+
//#region src/expansion/flux.ts
|
|
465
722
|
/**
|
|
466
723
|
* Compute local density around a node.
|
|
467
724
|
*/
|
|
@@ -496,7 +753,7 @@ function bridgeScore(nodeId, context) {
|
|
|
496
753
|
* - Low density + low bridge: DOME mode
|
|
497
754
|
* - High bridge score: PIPE mode
|
|
498
755
|
*/
|
|
499
|
-
function
|
|
756
|
+
function fluxPriority(nodeId, context, densityThreshold, bridgeThreshold) {
|
|
500
757
|
const graph = context.graph;
|
|
501
758
|
const degree = context.degree;
|
|
502
759
|
const density = localDensity(graph, nodeId);
|
|
@@ -507,7 +764,7 @@ function mazePriority(nodeId, context, densityThreshold, bridgeThreshold) {
|
|
|
507
764
|
else return degree;
|
|
508
765
|
}
|
|
509
766
|
/**
|
|
510
|
-
* Run
|
|
767
|
+
* Run FLUX expansion algorithm.
|
|
511
768
|
*
|
|
512
769
|
* Adaptively switches between expansion strategies based on
|
|
513
770
|
* local graph structure. Useful for heterogeneous graphs
|
|
@@ -518,9 +775,9 @@ function mazePriority(nodeId, context, densityThreshold, bridgeThreshold) {
|
|
|
518
775
|
* @param config - Expansion configuration
|
|
519
776
|
* @returns Expansion result with discovered paths
|
|
520
777
|
*/
|
|
521
|
-
function
|
|
778
|
+
function flux(graph, seeds, config) {
|
|
522
779
|
const { densityThreshold = .5, bridgeThreshold = .3, ...restConfig } = config ?? {};
|
|
523
|
-
const priority = (nodeId, context) =>
|
|
780
|
+
const priority = (nodeId, context) => fluxPriority(nodeId, context, densityThreshold, bridgeThreshold);
|
|
524
781
|
return base(graph, seeds, {
|
|
525
782
|
...restConfig,
|
|
526
783
|
priority
|
|
@@ -704,8 +961,8 @@ function scale(graph, source, target, config) {
|
|
|
704
961
|
const jaccard = union > 0 ? intersection / union : 0;
|
|
705
962
|
const n = graph.nodeCount;
|
|
706
963
|
const m = graph.edgeCount;
|
|
707
|
-
const
|
|
708
|
-
const density =
|
|
964
|
+
const possibleEdges = n * (n - 1);
|
|
965
|
+
const density = possibleEdges > 0 ? (graph.directed ? m : 2 * m) / possibleEdges : 0;
|
|
709
966
|
if (density === 0) return epsilon;
|
|
710
967
|
const score = jaccard / density;
|
|
711
968
|
return Math.max(epsilon, score);
|
|
@@ -876,7 +1133,7 @@ function degreeSum(graph, paths, config) {
|
|
|
876
1133
|
return {
|
|
877
1134
|
paths: scored.map(({ path, score }) => ({
|
|
878
1135
|
...path,
|
|
879
|
-
score: includeScores ? score / maxScore : score
|
|
1136
|
+
score: includeScores ? score / maxScore : score
|
|
880
1137
|
})).sort((a, b) => b.score - a.score),
|
|
881
1138
|
method: "degree-sum"
|
|
882
1139
|
};
|
|
@@ -978,7 +1235,7 @@ function jaccardArithmetic(graph, paths, config) {
|
|
|
978
1235
|
return {
|
|
979
1236
|
paths: scored.map(({ path, score }) => ({
|
|
980
1237
|
...path,
|
|
981
|
-
score: includeScores ? score / maxScore : score
|
|
1238
|
+
score: includeScores ? score / maxScore : score
|
|
982
1239
|
})).sort((a, b) => b.score - a.score),
|
|
983
1240
|
method: "jaccard-arithmetic"
|
|
984
1241
|
};
|
|
@@ -2202,7 +2459,9 @@ exports.extractInducedSubgraph = extractInducedSubgraph;
|
|
|
2202
2459
|
exports.extractKCore = extractKCore;
|
|
2203
2460
|
exports.extractKTruss = extractKTruss;
|
|
2204
2461
|
exports.filterSubgraph = filterSubgraph;
|
|
2462
|
+
exports.flux = flux;
|
|
2205
2463
|
exports.frontierBalanced = frontierBalanced;
|
|
2464
|
+
exports.fuse = fuse;
|
|
2206
2465
|
exports.getGPUContext = require_gpu.getGPUContext;
|
|
2207
2466
|
exports.getMotifName = getMotifName;
|
|
2208
2467
|
exports.graphToCSR = require_gpu.graphToCSR;
|
|
@@ -2212,6 +2471,7 @@ exports.isWebGPUAvailable = require_gpu.isWebGPUAvailable;
|
|
|
2212
2471
|
exports.jaccard = jaccard;
|
|
2213
2472
|
exports.jaccardArithmetic = jaccardArithmetic;
|
|
2214
2473
|
exports.katz = katz;
|
|
2474
|
+
exports.lace = lace;
|
|
2215
2475
|
exports.localClusteringCoefficient = require_utils.localClusteringCoefficient;
|
|
2216
2476
|
exports.localTypeEntropy = require_utils.localTypeEntropy;
|
|
2217
2477
|
exports.maze = maze;
|
|
@@ -2235,10 +2495,13 @@ exports.sage = sage;
|
|
|
2235
2495
|
exports.scale = scale;
|
|
2236
2496
|
exports.shannonEntropy = require_utils.shannonEntropy;
|
|
2237
2497
|
exports.shortest = shortest;
|
|
2498
|
+
exports.sift = sift;
|
|
2238
2499
|
exports.skew = skew;
|
|
2239
2500
|
exports.span = span;
|
|
2240
2501
|
exports.standardBfs = standardBfs;
|
|
2241
2502
|
exports.stratified = require_seeds.stratified;
|
|
2503
|
+
exports.tide = tide;
|
|
2504
|
+
exports.warp = warp;
|
|
2242
2505
|
exports.widestPath = widestPath;
|
|
2243
2506
|
|
|
2244
2507
|
//# sourceMappingURL=index.cjs.map
|