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