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.
Files changed (124) hide show
  1. package/README.md +71 -1
  2. package/dist/__test__/fixtures/graphs/city-suburban-village.d.ts +3 -0
  3. package/dist/__test__/fixtures/graphs/city-suburban-village.d.ts.map +1 -0
  4. package/dist/__test__/fixtures/graphs/city-village.d.ts +3 -0
  5. package/dist/__test__/fixtures/graphs/city-village.d.ts.map +1 -0
  6. package/dist/__test__/fixtures/graphs/index.d.ts +13 -0
  7. package/dist/__test__/fixtures/graphs/index.d.ts.map +1 -0
  8. package/dist/__test__/fixtures/graphs/quality-vs-popularity.d.ts +3 -0
  9. package/dist/__test__/fixtures/graphs/quality-vs-popularity.d.ts.map +1 -0
  10. package/dist/__test__/fixtures/graphs/social-hub.d.ts +3 -0
  11. package/dist/__test__/fixtures/graphs/social-hub.d.ts.map +1 -0
  12. package/dist/__test__/fixtures/graphs/three-community.d.ts +3 -0
  13. package/dist/__test__/fixtures/graphs/three-community.d.ts.map +1 -0
  14. package/dist/__test__/fixtures/graphs/two-department.d.ts +3 -0
  15. package/dist/__test__/fixtures/graphs/two-department.d.ts.map +1 -0
  16. package/dist/__test__/fixtures/graphs/typed-entity.d.ts +3 -0
  17. package/dist/__test__/fixtures/graphs/typed-entity.d.ts.map +1 -0
  18. package/dist/__test__/fixtures/helpers.d.ts +66 -0
  19. package/dist/__test__/fixtures/helpers.d.ts.map +1 -0
  20. package/dist/__test__/fixtures/helpers.unit.test.d.ts +7 -0
  21. package/dist/__test__/fixtures/helpers.unit.test.d.ts.map +1 -0
  22. package/dist/__test__/fixtures/index.d.ts +10 -0
  23. package/dist/__test__/fixtures/index.d.ts.map +1 -0
  24. package/dist/__test__/fixtures/types.d.ts +35 -0
  25. package/dist/__test__/fixtures/types.d.ts.map +1 -0
  26. package/dist/expansion/base.d.ts.map +1 -1
  27. package/dist/expansion/dome.d.ts.map +1 -1
  28. package/dist/expansion/dome.integration.test.d.ts +18 -0
  29. package/dist/expansion/dome.integration.test.d.ts.map +1 -0
  30. package/dist/expansion/edge.d.ts +3 -3
  31. package/dist/expansion/edge.d.ts.map +1 -1
  32. package/dist/expansion/edge.integration.test.d.ts +11 -0
  33. package/dist/expansion/edge.integration.test.d.ts.map +1 -0
  34. package/dist/expansion/flux.d.ts +25 -0
  35. package/dist/expansion/flux.d.ts.map +1 -0
  36. package/dist/expansion/flux.integration.test.d.ts +14 -0
  37. package/dist/expansion/flux.integration.test.d.ts.map +1 -0
  38. package/dist/expansion/flux.unit.test.d.ts +2 -0
  39. package/dist/expansion/flux.unit.test.d.ts.map +1 -0
  40. package/dist/expansion/fuse.d.ts +28 -0
  41. package/dist/expansion/fuse.d.ts.map +1 -0
  42. package/dist/expansion/fuse.integration.test.d.ts +15 -0
  43. package/dist/expansion/fuse.integration.test.d.ts.map +1 -0
  44. package/dist/expansion/fuse.unit.test.d.ts +2 -0
  45. package/dist/expansion/fuse.unit.test.d.ts.map +1 -0
  46. package/dist/expansion/hae.d.ts +7 -7
  47. package/dist/expansion/hae.d.ts.map +1 -1
  48. package/dist/expansion/hae.integration.test.d.ts +11 -0
  49. package/dist/expansion/hae.integration.test.d.ts.map +1 -0
  50. package/dist/expansion/index.d.ts +6 -0
  51. package/dist/expansion/index.d.ts.map +1 -1
  52. package/dist/expansion/lace.d.ts +22 -0
  53. package/dist/expansion/lace.d.ts.map +1 -0
  54. package/dist/expansion/lace.integration.test.d.ts +14 -0
  55. package/dist/expansion/lace.integration.test.d.ts.map +1 -0
  56. package/dist/expansion/lace.unit.test.d.ts +2 -0
  57. package/dist/expansion/lace.unit.test.d.ts.map +1 -0
  58. package/dist/expansion/maze.d.ts +3 -13
  59. package/dist/expansion/maze.d.ts.map +1 -1
  60. package/dist/expansion/maze.integration.test.d.ts +11 -0
  61. package/dist/expansion/maze.integration.test.d.ts.map +1 -0
  62. package/dist/expansion/pipe.d.ts +3 -3
  63. package/dist/expansion/pipe.d.ts.map +1 -1
  64. package/dist/expansion/pipe.integration.test.d.ts +12 -0
  65. package/dist/expansion/pipe.integration.test.d.ts.map +1 -0
  66. package/dist/expansion/reach.d.ts +4 -14
  67. package/dist/expansion/reach.d.ts.map +1 -1
  68. package/dist/expansion/reach.integration.test.d.ts +9 -0
  69. package/dist/expansion/reach.integration.test.d.ts.map +1 -0
  70. package/dist/expansion/sage.d.ts +5 -13
  71. package/dist/expansion/sage.d.ts.map +1 -1
  72. package/dist/expansion/sage.integration.test.d.ts +9 -0
  73. package/dist/expansion/sage.integration.test.d.ts.map +1 -0
  74. package/dist/expansion/sift.d.ts +26 -0
  75. package/dist/expansion/sift.d.ts.map +1 -0
  76. package/dist/expansion/sift.integration.test.d.ts +13 -0
  77. package/dist/expansion/sift.integration.test.d.ts.map +1 -0
  78. package/dist/expansion/sift.unit.test.d.ts +2 -0
  79. package/dist/expansion/sift.unit.test.d.ts.map +1 -0
  80. package/dist/expansion/tide.d.ts +15 -0
  81. package/dist/expansion/tide.d.ts.map +1 -0
  82. package/dist/expansion/tide.integration.test.d.ts +14 -0
  83. package/dist/expansion/tide.integration.test.d.ts.map +1 -0
  84. package/dist/expansion/tide.unit.test.d.ts +2 -0
  85. package/dist/expansion/tide.unit.test.d.ts.map +1 -0
  86. package/dist/expansion/warp.d.ts +15 -0
  87. package/dist/expansion/warp.d.ts.map +1 -0
  88. package/dist/expansion/warp.integration.test.d.ts +13 -0
  89. package/dist/expansion/warp.integration.test.d.ts.map +1 -0
  90. package/dist/expansion/warp.unit.test.d.ts +2 -0
  91. package/dist/expansion/warp.unit.test.d.ts.map +1 -0
  92. package/dist/graph/adjacency-map.d.ts.map +1 -1
  93. package/dist/graph/index.cjs +7 -0
  94. package/dist/graph/index.cjs.map +1 -1
  95. package/dist/graph/index.js +7 -0
  96. package/dist/graph/index.js.map +1 -1
  97. package/dist/index/index.cjs +323 -60
  98. package/dist/index/index.cjs.map +1 -1
  99. package/dist/index/index.js +318 -61
  100. package/dist/index/index.js.map +1 -1
  101. package/dist/ranking/mi/etch.integration.test.d.ts +2 -0
  102. package/dist/ranking/mi/etch.integration.test.d.ts.map +1 -0
  103. package/dist/ranking/mi/notch.integration.test.d.ts +2 -0
  104. package/dist/ranking/mi/notch.integration.test.d.ts.map +1 -0
  105. package/dist/ranking/mi/scale.d.ts.map +1 -1
  106. package/dist/ranking/mi/scale.integration.test.d.ts +2 -0
  107. package/dist/ranking/mi/scale.integration.test.d.ts.map +1 -0
  108. package/dist/ranking/mi/skew.integration.test.d.ts +2 -0
  109. package/dist/ranking/mi/skew.integration.test.d.ts.map +1 -0
  110. package/dist/ranking/mi/span.integration.test.d.ts +2 -0
  111. package/dist/ranking/mi/span.integration.test.d.ts.map +1 -0
  112. package/dist/ranking/parse.integration.test.d.ts +10 -0
  113. package/dist/ranking/parse.integration.test.d.ts.map +1 -0
  114. package/dist/structures/index.cjs +12 -2
  115. package/dist/structures/index.cjs.map +1 -1
  116. package/dist/structures/index.js +12 -2
  117. package/dist/structures/index.js.map +1 -1
  118. package/dist/structures/priority-queue.d.ts +4 -2
  119. package/dist/structures/priority-queue.d.ts.map +1 -1
  120. package/dist/utils/index.cjs +76 -77
  121. package/dist/utils/index.cjs.map +1 -1
  122. package/dist/utils/index.js +76 -77
  123. package/dist/utils/index.js.map +1 -1
  124. package/package.json +1 -1
@@ -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
- const seedPriority = priority(seedNode, createPriorityContext(graph, seedNode, i, visitedByFrontier, [], 0));
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 allVisited = /* @__PURE__ */ new Set();
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 edgeKey = nodeId < neighbour ? `${nodeId}::${neighbour}` : `${neighbour}::${nodeId}`;
115
- sampledEdges.add(edgeKey);
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, visitedByFrontier, discoveredPaths, iterations + 1));
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 edgeKey of sampledEdges) {
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, visitedByFrontier, discoveredPaths, iteration) {
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 graph.degree(nodeId);
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 -graph.degree(nodeId);
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
- * EDGE priority function.
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
- let totalDegree = context.degree;
269
- for (const neighbour of graph.neighbours(nodeId)) totalDegree += graph.degree(neighbour);
270
- return totalDegree;
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 algorithm.
275
+ * Run EDGE expansion (Entropy-Driven Graph Expansion).
274
276
  *
275
- * Expands from seeds prioritising low-degree edges first.
276
- * Useful for avoiding hubs and exploring sparse regions.
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/hae.ts
426
+ //#region src/expansion/reach.ts
309
427
  /**
310
- * HAE priority function.
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 haePriority(nodeId, context, mi) {
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 HAE expansion algorithm.
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 hae(graph, seeds, config) {
597
+ function lace(graph, seeds, config) {
341
598
  const { mi = jaccard, ...restConfig } = config ?? {};
342
- const priority = (nodeId, context) => haePriority(nodeId, context, mi);
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/pipe.ts
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 pipePriority(nodeId, context) {
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 PIPE expansion algorithm.
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 pipe(graph, seeds, config) {
634
+ function warp(graph, seeds, config) {
378
635
  return base(graph, seeds, {
379
636
  ...config,
380
- priority: pipePriority
637
+ priority: warpPriority
381
638
  });
382
639
  }
383
640
  //#endregion
384
- //#region src/expansion/sage.ts
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 sagePriority(nodeId, context, mi, salienceWeight) {
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 SAGE expansion algorithm.
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 sage(graph, seeds, config) {
673
+ function fuse(graph, seeds, config) {
417
674
  const { mi = jaccard, salienceWeight = .5, ...restConfig } = config ?? {};
418
- const priority = (nodeId, context) => sagePriority(nodeId, context, mi, salienceWeight);
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/reach.ts
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 reachPriority(nodeId, context, mi, miThreshold) {
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 REACH expansion algorithm.
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 reach(graph, seeds, config) {
712
+ function sift(graph, seeds, config) {
456
713
  const { mi = jaccard, miThreshold = .25, ...restConfig } = config ?? {};
457
- const priority = (nodeId, context) => reachPriority(nodeId, context, mi, miThreshold);
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/maze.ts
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 mazePriority(nodeId, context, densityThreshold, bridgeThreshold) {
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 MAZE expansion algorithm.
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 maze(graph, seeds, config) {
778
+ function flux(graph, seeds, config) {
522
779
  const { densityThreshold = .5, bridgeThreshold = .3, ...restConfig } = config ?? {};
523
- const priority = (nodeId, context) => mazePriority(nodeId, context, densityThreshold, bridgeThreshold);
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 densityNormaliser = graph.directed ? n * (n - 1) : 2 * n * (n - 1);
708
- const density = densityNormaliser > 0 ? m / densityNormaliser : 0;
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 / maxScore
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 / maxScore
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